function toId(text) { text = text || ''; if (typeof text !== 'string') return ''; //??? return text.replace(/ /g, ''); } function clone(object) { var newObj = (object instanceof Array) ? [] : {}; for (var i in object) { if (object[i] && typeof object[i] == "object") { newObj[i] = clone(object[i]); } else newObj[i] = object[i] } return newObj; }; function shuffle(array) { var tmp, current, top = array.length; if(top) while(--top) { current = Math.floor(Math.random() * (top + 1)); tmp = array[current]; array[current] = array[top]; array[top] = tmp; } return array; } function objectKeys(object) { var keys = []; for (var prop in object) { if (object.hasOwnProperty(prop)) { keys.push(prop); } } return keys; } exports.BattleScripts = { runMove: function(move, pokemon, target) { move = this.getMove(move); this.setActiveMove(move, pokemon, target); if (pokemon.movedThisTurn || pokemon.runBeforeMove(target, move)) { this.debug(''+pokemon.id+' move interrupted; movedThisTurn: '+pokemon.movedThisTurn); this.clearActiveMove(true); return; } if (move.beforeMoveCallback) { if (move.beforeMoveCallback.call(this, pokemon, target, move)) { this.clearActiveMove(true); return; } } pokemon.lastDamage = 0; this.useMove(move, pokemon, target); pokemon.deductPP(move); }, useMove: function(move, pokemon, target) { this.setActiveMove(move, pokemon, target); var damage = 0; var canTargetFainted = { all: 1, foeSide: 1 }; move = this.getMove(move); move = clone(move); move = this.runEvent('ModifyMove',pokemon,target,move,move); if (!move) return false; var attrs = ''; var moveRoll = Math.random()*100; var missed = false; if (pokemon.fainted) { return false; } var boostTable = [1, 4/3, 5/3, 2, 7/3, 8/3, 3]; var accuracy = move.accuracy; if (accuracy !== true) { if (pokemon.boosts.accuracy > 0) { accuracy *= boostTable[pokemon.boosts.accuracy]; } else { accuracy /= boostTable[-pokemon.boosts.accuracy]; } if (target.boosts.evasion > 0) { accuracy /= boostTable[target.boosts.evasion]; } else { accuracy *= boostTable[-target.boosts.evasion]; } } if (move.ohko) accuracy = 30; // bypasses accuracy modifiers if (move.alwaysHit) accuracy = true; // bypasses ohko accuracy modifiers if (accuracy !== true && moveRoll >= accuracy) { missed = true; attrs = ' miss'; } if (target.fainted && !canTargetFainted[move.target]) { attrs = ' no-target'; } this.add('move '+pokemon.id+' '+move.id+' ??'+attrs); if (missed) { this.add('r-miss '+pokemon.id); this.singleEvent('MoveFail', move, null, target, pokemon, move); if (move.selfdestruct) { this.faint(pokemon, pokemon, move); } return true; } if (target.fainted && !canTargetFainted[move.target]) { this.add('r-no-target'); this.singleEvent('MoveFail', move, null, target, pokemon, move); if (move.selfdestruct) { this.faint(pokemon, pokemon, move); } return true; } if (move.id === 'Return' || move.id === 'Frustration') { move.basePower = 102; } if (typeof move.affectedByImmunities === 'undefined') { move.affectedByImmunities = (move.category !== 'Status'); } if (move.affectedByImmunities) { var type = move.type; if (move.typeCallback) { if (typeof move.typeCallback === 'string') { type = move.typeCallback; } else { type = move.typeCallback.call(this, pokemon, target); } move.type = type; } if (!target.runImmunity(type, true)) { this.singleEvent('MoveFail', move, null, target, pokemon, move); if (move.selfdestruct) { this.faint(pokemon, pokemon, move); } return true; } } pokemon.lastDamage = 0; if (!move.multihit) { damage = BattleScripts.moveHit.call(this, target, pokemon, move); } else { var hits = move.multihit; if (hits.length) { // yes, it's hardcoded... meh if (hits[0] === 2 && hits[1] === 5) { var roll = parseInt(Math.random()*20); if (roll < 7) hits = 2; else if (roll < 14) hits = 3; else if (roll < 17) hits = 4; else hits = 5; } else { hits = hits[0] + (Math.random()*(hits[1]-hits[0]+1)); } } hits = Math.floor(hits); for (var i=0; i= target.hp) { damage = target.hp - 1; } if (damage && !target.fainted) { damage = this.damage(damage, target, pokemon, move); if (!damage) return false; } else if (damage === false && typeof hitResult === 'undefined') { this.add('r-failed '+target.id); } if (moveData.boosts && !target.fainted) { this.boost(moveData.boosts, target, pokemon, move); } if (moveData.heal && !target.fainted) { var d = target.heal(target.maxhp * moveData.heal[0] / moveData.heal[1]); this.add('r-heal '+target.id+' '+target.hpPercent(d)+target.getHealth()); } if (moveData.status) { if (!target.status) { target.setStatus(moveData.status, pokemon, move); } else if (!isSecondary) { // already-status this.add('r-status '+target.id+' '+target.status); } } if (moveData.forceStatus) { target.setStatus(moveData.forceStatus, pokemon, move); } if (moveData.volatileStatus) { target.addVolatile(moveData.volatileStatus, pokemon, move); } if (moveData.sideCondition) { target.side.addSideCondition(moveData.sideCondition, pokemon, move); } if (moveData.weather) { this.setWeather(moveData.weather, pokemon, move); } if (moveData.pseudoWeather) { this.addPseudoWeather(moveData.pseudoWeather, pokemon, move); } } if (moveData.self) { BattleScripts.moveHit.call(this, pokemon, pokemon, move, moveData.self, isSecondary, true); } if (moveData.secondary && target) { var secondaryRoll = Math.random()*100; if (typeof moveData.secondary.chance === 'undefined' || secondaryRoll < moveData.secondary.chance) { BattleScripts.moveHit.call(this, target, pokemon, move, moveData.secondary, true, isSelf); } } if (target) { if (moveData.forceSwitch && this.runEvent('DragOut', target, pokemon, move)) { this.dragIn(target.side); } } if (move.selfSwitch || move.batonPass) { pokemon.switchFlag = true; } return damage; }, getTeam: function(side) { if (side.battle.getFormat().team === 'random') { return BattleScripts.randomTeam.call(this, side); } else if (side.user && side.user.team && side.user.team !== 'random') { return side.user.team; } else { return BattleScripts.randomTeam.call(this,side); } }, randomTeam: function(side) { var battle = this; var keys = []; var pokemonLeft = 0; var pokemon = []; for (var prop in BattlePokedex) { if (BattlePokedex.hasOwnProperty(prop) && BattlePokedex[prop].viable) { keys.push(prop); } } keys = shuffle(keys); var PotD = this.getEffect('PotD'); var ruleset = this.getFormat().ruleset; for (var i=0; i1) continue; if (i===1 && ruleset && ruleset[0]==='PotD' && PotD && PotD.onPotD) { template = this.getTemplate(PotD.onPotD); } if (template.species === 'Magikarp') { template.viablemoves = ["Magikarp'sRevenge", "Splash", "Bounce"]; } var moveKeys = shuffle(objectKeys(template.viablemoves)); var moves = []; var ability = ''; var item = ''; var evs = { hp: 85, atk: 85, def: 85, spa: 85, spd: 85, spe: 85 }; var ivs = { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 }; var hasType = {}; hasType[template.types[0]] = true; if (template.types[1]) hasType[template.types[1]] = true; var hasMove = {}; var counter = {}; var setupType = ''; var j=0; do { while (moves.length<4 && j 1) rejected = true; isSetup = true; break; case 'NastyPlot': case 'TailGlow': case 'QuiverDance': case 'CalmMind': if (!counter['Special'] && !hasMove['BatonPass']) rejected = true; if (setupType !== 'Special' || counter['specialSetup'] > 1) rejected = true; isSetup = true; break; case 'ShellSmash': case 'Growth': case 'WorkUp': if (!counter['Special'] && !counter['Physical'] && !hasMove['BatonPass']) rejected = true; if (setupType !== 'Mixed' || counter['mixedSetup'] > 1) rejected = true; isSetup = true; break; // bad after setup case 'SeismicToss': case 'NightShade': case 'SuperFang': if (setupType) rejected = true; break; case 'KnockOff': case 'Protect': case 'PerishSong': case 'MagicCoat': if (setupType) rejected = true; break; // bit redundant to have both case 'FireBlast': if (hasMove['Eruption'] || hasMove['Overheat']) rejected = true; break; case 'Flamethrower': if (hasMove['LavaPlume'] || hasMove['FireBlast'] || hasMove['Overheat']) rejected = true; break; case 'IceBeam': if (hasMove['Blizzard']) rejected = true; break; case 'Surf': if (hasMove['Scald'] || hasMove['HydroPump']) rejected = true; break; case 'EnergyBall': case 'GrassKnot': case 'PetalDance': if (hasMove['GigaDrain']) rejected = true; break; case 'SeedBomb': if (hasMove['NeedleArm']) rejected = true; break; case 'FlareBlitz': if (hasMove['FirePunch']) rejected = true; break; case 'Thunderbolt': if (hasMove['Discharge'] || hasMove['VoltSwitch'] || hasMove['Thunder']) rejected = true; break; case 'Discharge': if (hasMove['VoltSwitch'] || hasMove['Thunder']) rejected = true; break; case 'RockSlide': if (hasMove['StoneEdge']) rejected = true; break; case 'DragonClaw': if (hasMove['Outrage'] || hasMove['DragonTail']) rejected = true; break; case 'AncientPower': if (hasMove['PaleoWave']) rejected = true; break; case 'DragonPulse': if (hasMove['DracoMeteor']) rejected = true; break; case 'Return': if (hasMove['BodySlam']) rejected = true; if (hasMove['Flail']) rejected = true; if (hasMove['Facade']) rejected = true; break; case 'Flail': if (hasMove['Facade']) rejected = true; break; case 'PoisonJab': if (hasMove['GunkShot']) rejected = true; break; case 'Psychic': if (hasMove['Psyshock']) rejected = true; break; case 'Yawn': if (hasMove['GrassWhistle']) rejected = true; break; case 'Rest': if (hasMove['MorningSun']) rejected = true; break; case 'Softboiled': if (hasMove['Wish']) rejected = true; break; case 'PerishSong': if (hasMove['Roar'] || hasMove['Whirlwind'] || hasMove['Haze']) rejected = true; break; case 'Whirlwind': // outclasses Roar because Soundproof if (hasMove['Roar'] || hasMove['Haze']) rejected = true; break; case 'Roost': if (hasMove['Recover']) rejected = true; break; } if (k===3) { if (counter['Status']>=4) { // taunt bait, not okay rejected = true; } } var SetupException = { Overheat:1, DracoMeteor:1, LeafStorm:1, VoltSwitch:1, 'U-turn':1, SuckerPunch:1, ExtremeSpeed:1 }; if (move.category === 'Special' && setupType === 'Physical' && !SetupException[move.id]) { rejected = true; } if (move.category === 'Physical' && setupType === 'Special' && !SetupException[move.id]) { rejected = true; } if (setupType === 'Physical' && move.category !== 'Physical' && counter['Physical'] < 2) { rejected = true; } if (setupType === 'Special' && move.category !== 'Special' && counter['Special'] < 2) { rejected = true; } var todoMoves = { BeatUp:1, Disable:1 }; if (todoMoves[move.id]) rejected = true; if (rejected && j= 3 && (hasMove['Trick'] || hasMove['Switcheroo'])) { item = 'ChoiceBand'; } else if (counter.Special >= 3 && (hasMove['Trick'] || hasMove['Switcheroo'])) { item = 'ChoiceSpecs'; } else if (counter.Status <= 1 && (hasMove['Trick'] || hasMove['Switcheroo'])) { item = 'ChoiceScarf'; } else if (hasMove['Rest'] && !hasMove['SleepTalk']) { item = 'ChestoBerry'; } else if (hasMove['NaturalGift']) { item = 'LiechiBerry'; } else if (template.species === 'Cubone' || template.species === 'Marowak') { item = 'ThickClub'; } else if (template.species === 'Pikachu') { item = 'LightBall'; } else if (template.species === 'Clamperl') { item = 'DeepSeaTooth'; } else if (hasMove['Reflect'] && hasMove['LightScreen']) { item = 'LightClay'; } else if (hasMove['Acrobatics']) { item = 'FlyingGem'; } else if (hasMove['ShellSmash']) { item = 'WhiteHerb'; } else if (ability === 'PoisonHeal') { item = 'ToxicOrb'; } else if (hasMove['RainDance']) { item = 'DampRock'; } else if (hasMove['SunnyDay']) { item = 'HeatRock'; } else if (hasMove['Sandstorm']) // lol { item = 'SmoothRock'; } else if (hasMove['Hail']) // lol { item = 'IcyRock'; } else if (ability === 'MagicGuard' && hasMove['PsychoShift']) { item = 'FlameOrb'; } else if (ability === 'SheerForce' || ability === 'MagicGuard') { item = 'LifeOrb'; } else if (hasMove['Trick'] || hasMove['Switcheroo']) { item = 'ChoiceScarf'; } else if (ability === 'Guts') { if (hasMove['DrainPunch']) { item = 'FlameOrb'; } else { item = 'ToxicOrb'; } if ((hasMove['Return'] || hasMove['HyperFang']) && !hasMove['Facade']) { // lol no for (var j=0; j= 4 && !hasMove['FakeOut'] && !hasMove['SuckerPunch']) { if (Math.random()*3 > 1) { item = 'ChoiceBand'; } else { item = 'ExpertBelt'; } } else if (counter.Special >= 4) { if (Math.random()*3 > 1) { item = 'ChoiceSpecs'; } else { item = 'ExpertBelt'; } } else if (this.getEffectiveness('Ground', template) >= 2 && ability !== 'Levitate') { item = 'AirBalloon'; } else if (hasMove['Eruption'] || hasMove['WaterSpout']) { item = 'ChoiceScarf'; } else if (hasMove['Substitute'] || hasMove['Detect'] || hasMove['Protect']) { item = 'Leftovers'; } else if ((hasMove['Flail'] || hasMove['Reversal']) && !hasMove['Endure']) { item = 'FocusSash'; } else if (ability === 'IronBarbs') { // only Iron Barbs for now item = 'RockyHelmet'; } else if ((template.baseStats.hp+75)*(template.baseStats.def+template.baseStats.spd+175) > 60000 || template.species === 'Skarmory' || template.species === 'Forretress') { // skarmory and forretress get exceptions for their typing item = 'Leftovers'; } else if (counter.Physical + counter.Special >= 3 && setupType) { item = 'LifeOrb'; } else if (counter.Special >= 3 && setupType) { item = 'LifeOrb'; } else if (counter.Physical + counter.Special >= 4) { item = 'ExpertBelt'; } else if (i===0) { item = 'FocusSash'; } // this is the "REALLY can't think of a good item" cutoff // why not always Leftovers? Because it's boring. :P else if (hasType['Flying'] || ability === 'Levitate') { item = 'Leftovers'; } else if (this.getEffectiveness('Ground', template) >= 1 && ability !== 'Levitate') { item = 'AirBalloon'; } else if (hasType['Poison']) { item = 'BlackSludge'; } else if (counter.Status <= 1) { item = 'LifeOrb'; } /* else if ((template.baseStats.hp+75)*(template.baseStats.def+template.baseStats.spd+175) > 50000) { item = 'Leftovers'; } else if (this.getEffectiveness('Ground', template) >= 0) { item = 'AirBalloon'; } */ else { item = 'Leftovers'; } if (item === 'Leftovers' && hasType['Poison']) { item = 'BlackSludge'; } } var levelScale = { LC: 95, Limbo: 95, 'LC Uber': 90, NU: 90, RU: 85, BL2: 83, UU: 80, BL: 78, OU: 75, CAP: 74, Unreleased: 75, Uber: 70 }; var customScale = { Meloetta: 78, Caterpie: 99, Metapod: 99, Weedle: 99, Kakuna: 99, Hoppip: 99, Wurmple: 99, Silcoon: 99, Cascoon: 99, Feebas: 99, Magikarp: 99 }; var level = levelScale[template.tier] || 90; if (customScale[template.name]) level = customScale[template.name]; if (template.name === 'Chandelure' && ability === 'ShadowTag') level = 70; if (template.name === 'Serperior' && ability === 'Contrary') level = 75; if (template.name === 'Rotom-S' && item === 'Balloon') level = 95; if (template.name === 'Magikarp' && hasMove['Magikarp\'sRevenge']) level = 85; pokemon.push({ name: template.name, moves: moves, ability: ability, evs: evs, item: item, level: level }); pokemonLeft++; } return pokemon; }, getNature: function(nature) { if (typeof nature === 'string') nature = BattleNatures[nature]; if (!nature) nature = {}; return nature; }, natureModify: function(stats, nature) { if (typeof nature === 'string') nature = BattleNatures[nature]; if (!nature) return stats; if (nature.plus) stats[nature.plus] *= 1.1; if (nature.minus) stats[nature.minus] *= 0.9; return stats; } }; var BattleNatures = { Adamant: {plus:'atk', minus:'spa'}, Bashful: {}, Bold: {plus:'def', minus:'atk'}, Brave: {plus:'atk', minus:'spe'}, Calm: {plus:'spd', minus:'atk'}, Careful: {plus:'spd', minus:'spa'}, Docile: {}, Gentle: {plus:'spd', minus:'def'}, Hardy: {}, Hasty: {plus:'spe', minus:'def'}, Impish: {plus:'def', minus:'spa'}, Jolly: {plus:'spe', minus:'spa'}, Lax: {plus:'def', minus:'spd'}, Lonely: {plus:'atk', minus:'def'}, Mild: {plus:'spa', minus:'def'}, Modest: {plus:'spa', minus:'atk'}, Naive: {plus:'spe', minus:'spd'}, Naughty: {plus:'atk', minus:'spd'}, Quiet: {plus:'spa', minus:'spe'}, Quirky: {}, Rash: {plus:'spa', minus:'spd'}, Relaxed: {plus:'def', minus:'spe'}, Sassy: {plus:'spd', minus:'spe'}, Serious: {}, Timid: {plus:'spe', minus:'atk'}, }; var BattleScripts = exports.BattleScripts;