/** * Simulator process * Pokemon Showdown - http://pokemonshowdown.com/ * * This file is where the battle simulation itself happens. * * The most important part of the simulation happens in runEvent - * see that function's definition for details. * * @license MIT license */ require('sugar'); global.fs = require('fs'); if (!('existsSync' in fs)) { // for compatibility with ancient versions of node fs.existsSync = require('path').existsSync; } global.config = require('./config/config.js'); if (config.crashguard) { // graceful crash - allow current battles to finish before restarting process.on('uncaughtException', function (err) { require('./crashlogger.js')(err, 'A simulator process'); /* var stack = (""+err.stack).split("\n").slice(0,2).join("
"); if (Rooms.lobby) { Rooms.lobby.addRaw('
THE SERVER HAS CRASHED: '+stack+'
Please restart the server.
'); Rooms.lobby.addRaw('
You will not be able to talk in the lobby or start new battles until the server restarts.
'); } config.modchat = 'crash'; Rooms.global.lockdown = true; */ }); } /** * Converts anything to an ID. An ID must have only lowercase alphanumeric * characters. * If a string is passed, it will be converted to lowercase and * non-alphanumeric characters will be stripped. * If an object with an ID is passed, its ID will be returned. * Otherwise, an empty string will be returned. */ global.toId = function(text) { if (text && text.id) text = text.id; else if (text && text.userid) text = text.userid; return string(text).toLowerCase().replace(/[^a-z0-9]+/g, ''); }; global.toUserid = toId; /** * Validates a username or Pokemon nickname */ global.toName = function(name) { name = string(name); name = name.replace(/[\|\s\[\]\,]+/g, ' ').trim(); if (name.length > 18) name = name.substr(0,18).trim(); return name; }; /** * Escapes a string for HTML * If strEscape is true, escapes it for JavaScript, too */ global.sanitize = function(str, strEscape) { str = (''+(str||'')); str = str.escapeHTML(); if (strEscape) str = str.replace(/'/g, '\\\''); return str; }; /** * Safely ensures the passed variable is a string * Simply doing ''+str can crash if str.toString crashes or isn't a function * If we're expecting a string and being given anything that isn't a string * or a number, it's safe to assume it's an error, and return '' */ global.string = function(str) { if (typeof str === 'string' || typeof str === 'number') return ''+str; return ''; }; /** * Converts any variable to an integer (numbers get floored, non-numbers * become 0). Then clamps it between min and (optionally) max. */ clampIntRange = function(num, min, max) { if (typeof num !== 'number') num = 0; num = Math.floor(num); if (num < min) num = min; if (max !== undefined && num > max) num = max; return num; }; global.Tools = require('./tools.js'); var Battles = {}; // Receive and process a message sent using Simulator.prototype.send in // another process. process.on('message', function(message) { //console.log('CHILD MESSAGE RECV: "'+message+'"'); var nlIndex = message.indexOf("\n"); var more = ''; if (nlIndex > 0) { more = message.substr(nlIndex+1); message = message.substr(0, nlIndex); } var data = message.split('|'); if (data[1] === 'init') { if (!Battles[data[0]]) { try { Battles[data[0]] = Battle.construct(data[0], data[2], data[3]); } catch (err) { var stack = err.stack + '\n\n' + 'Additional information:\n' + 'message = ' + message; var fakeErr = {stack: stack}; if (!require('./crashlogger.js')(fakeErr, 'A battle')) { var ministack = (""+err.stack).split("\n").slice(0,2).join("
"); process.send(data[0]+'\nupdate\n|html|
A BATTLE PROCESS HAS CRASHED: '+ministack+'
'); } else { process.send(data[0]+'\nupdate\n|html|
The battle crashed!
Don\'t worry, we\'re working on fixing it.
'); } } } } else if (data[1] === 'dealloc') { if (Battles[data[0]]) Battles[data[0]].destroy(); delete Battles[data[0]]; } else { var battle = Battles[data[0]]; if (battle) { var prevRequest = battle.currentRequest; try { battle.receive(data, more); } catch (err) { var stack = err.stack + '\n\n' + 'Additional information:\n' + 'message = ' + message + '\n' + 'currentRequest = ' + prevRequest + '\n\n' + 'Log:\n' + battle.log.join('\n'); var fakeErr = {stack: stack}; require('./crashlogger.js')(fakeErr, 'A battle'); var logPos = battle.log.length; battle.add('html', '
The battle crashed
You can keep playing but it might crash again.
'); var nestedError; try { battle.makeRequest(prevRequest); } catch (e) { nestedError = e; } battle.sendUpdates(logPos); if (nestedError) { throw nestedError; } } } else if (data[1] === 'eval') { try { eval(data[2]); } catch (e) {} } } }); var BattlePokemon = (function() { function BattlePokemon(set, side) { this.side = side; this.battle = side.battle; if (typeof set === 'string') set = {name: set}; // "pre-bound" functions for nicer syntax (avoids repeated use of `bind`) this.getHealth = BattlePokemon.getHealth.bind(this); this.getDetails = BattlePokemon.getDetails.bind(this); this.set = set; this.baseTemplate = this.battle.getTemplate(set.species || set.name); if (!this.baseTemplate.exists) { this.battle.debug('Unidentified species: '+this.species); this.baseTemplate = this.battle.getTemplate('Unown'); } this.species = this.baseTemplate.species; if (set.name === set.species || !set.name || !set.species) { set.name = this.species; } this.name = (set.name || set.species || 'Bulbasaur').substr(0,20); this.speciesid = toId(this.species); this.template = this.baseTemplate; this.moves = []; this.baseMoves = this.moves; this.movepp = {}; this.moveset = []; this.baseMoveset = []; this.level = clampIntRange(set.forcedLevel || set.level || 100, 1, 1000); var genders = {M:'M',F:'F'}; this.gender = this.template.gender || genders[set.gender] || (Math.random()*2<1?'M':'F'); if (this.gender === 'N') this.gender = ''; this.happiness = typeof set.happiness === 'number' ? clampIntRange(set.happiness, 0, 255) : 255; this.pokeball = this.set.pokeball || 'pokeball'; this.fullname = this.side.id + ': ' + this.name; this.details = this.species + (this.level==100?'':', L'+this.level) + (this.gender===''?'':', '+this.gender) + (this.set.shiny?', shiny':''); this.id = this.fullname; // shouldn't really be used anywhere this.statusData = {}; this.volatiles = {}; this.negateImmunity = {}; this.height = this.template.height; this.heightm = this.template.heightm; this.weight = this.template.weight; this.weightkg = this.template.weightkg; this.ignore = {}; this.baseAbility = toId(set.ability); this.ability = this.baseAbility; this.item = toId(set.item); this.canMegaEvo = (this.battle.getItem(this.item).megaEvolves === this.species); this.abilityData = {id: this.ability}; this.itemData = {id: this.item}; this.speciesData = {id: this.speciesid}; this.types = this.baseTemplate.types; this.typesData = []; for (var i=0, l=this.types.length; i