exports.BattleScripts = { gen: 6, runMove: function (move, pokemon, target, sourceEffect) { if (!sourceEffect && toId(move) !== 'struggle') { var changedMove = this.runEvent('OverrideDecision', pokemon, target, move); if (changedMove && changedMove !== true) { move = changedMove; target = null; } } move = this.getMove(move); if (!target && target !== false) target = this.resolveTarget(pokemon, move); this.setActiveMove(move, pokemon, target); if (pokemon.moveThisTurn) { // THIS IS PURELY A SANITY CHECK // DO NOT TAKE ADVANTAGE OF THIS TO PREVENT A POKEMON FROM MOVING; // USE this.cancelMove INSTEAD this.debug('' + pokemon.id + ' INCONSISTENT STATE, ALREADY MOVED: ' + pokemon.moveThisTurn); this.clearActiveMove(true); return; } if (!this.runEvent('BeforeMove', pokemon, target, move)) { // Prevent invulnerability from persisting until the turn ends pokemon.removeVolatile('twoturnmove'); this.clearActiveMove(true); return; } if (move.beforeMoveCallback) { if (move.beforeMoveCallback.call(this, pokemon, target, move)) { this.clearActiveMove(true); return; } } pokemon.lastDamage = 0; var lockedMove = this.runEvent('LockMove', pokemon); if (lockedMove === true) lockedMove = false; if (!lockedMove) { if (!pokemon.deductPP(move, null, target) && (move.id !== 'struggle')) { this.add('cant', pokemon, 'nopp', move); this.clearActiveMove(true); return; } } pokemon.moveUsed(move); this.useMove(move, pokemon, target, sourceEffect); this.singleEvent('AfterMove', move, null, pokemon, target, move); }, useMove: function (move, pokemon, target, sourceEffect) { if (!sourceEffect && this.effect.id) sourceEffect = this.effect; move = this.getMoveCopy(move); if (this.activeMove) move.priority = this.activeMove.priority; var baseTarget = move.target; if (!target && target !== false) target = this.resolveTarget(pokemon, move); if (move.target === 'self' || move.target === 'allies') { target = pokemon; } if (sourceEffect) move.sourceEffect = sourceEffect.id; this.setActiveMove(move, pokemon, target); this.singleEvent('ModifyMove', move, null, pokemon, target, move, move); if (baseTarget !== move.target) { // Target changed in ModifyMove, so we must adjust it here // Adjust before the next event so the correct target is passed to the // event target = this.resolveTarget(pokemon, move); } move = this.runEvent('ModifyMove', pokemon, target, move, move); if (baseTarget !== move.target) { // Adjust again target = this.resolveTarget(pokemon, move); } if (!move) return false; var attrs = ''; var missed = false; if (pokemon.fainted) { return false; } if (move.isTwoTurnMove && !pokemon.volatiles[move.id]) { attrs = '|[still]'; // suppress the default move animation } var movename = move.name; if (move.id === 'hiddenpower') movename = 'Hidden Power'; if (sourceEffect) attrs += '|[from]' + this.getEffect(sourceEffect); this.addMove('move', pokemon, movename, target + attrs); if (target === false) { this.attrLastMove('[notarget]'); this.add('-notarget'); return true; } if (!this.runEvent('TryMove', pokemon, target, move)) { return true; } if (typeof move.affectedByImmunities === 'undefined') { move.affectedByImmunities = (move.category !== 'Status'); } var damage = false; if (move.target === 'all' || move.target === 'foeSide' || move.target === 'allySide' || move.target === 'allyTeam') { damage = this.tryMoveHit(target, pokemon, move); } else if (move.target === 'allAdjacent' || move.target === 'allAdjacentFoes') { var targets = []; if (move.target === 'allAdjacent') { var allyActive = pokemon.side.active; for (var i = 0; i < allyActive.length; i++) { if (allyActive[i] && Math.abs(i - pokemon.position) <= 1 && i !== pokemon.position && !allyActive[i].fainted) { targets.push(allyActive[i]); } } } var foeActive = pokemon.side.foe.active; var foePosition = foeActive.length - pokemon.position - 1; for (var i = 0; i < foeActive.length; i++) { if (foeActive[i] && Math.abs(i - foePosition) <= 1 && !foeActive[i].fainted) { targets.push(foeActive[i]); } } if (move.selfdestruct) { this.faint(pokemon, pokemon, move); } if (!targets.length) { this.attrLastMove('[notarget]'); this.add('-notarget'); return true; } if (targets.length > 1) move.spreadHit = true; damage = 0; for (var i = 0; i < targets.length; i++) { damage += (this.tryMoveHit(targets[i], pokemon, move, true) || 0); } if (!pokemon.hp) pokemon.faint(); } else { if (target.fainted && target.side !== pokemon.side) { // if a targeted foe faints, the move is retargeted target = this.resolveTarget(pokemon, move); } var lacksTarget = target.fainted; if (!lacksTarget) { if (move.target === 'adjacentFoe' || move.target === 'adjacentAlly' || move.target === 'normal' || move.target === 'randomNormal') { lacksTarget = !this.isAdjacent(target, pokemon); } } if (lacksTarget) { this.attrLastMove('[notarget]'); this.add('-notarget'); return true; } if (target.side.active.length > 1) { target = this.runEvent('RedirectTarget', pokemon, pokemon, move, target); } damage = this.tryMoveHit(target, pokemon, move); } if (!pokemon.hp) { this.faint(pokemon, pokemon, move); } if (!damage && damage !== 0 && damage !== undefined) { this.singleEvent('MoveFail', move, null, target, pokemon, move); return true; } if (move.selfdestruct) { this.faint(pokemon, pokemon, move); } if (!move.negateSecondary) { this.singleEvent('AfterMoveSecondarySelf', move, null, pokemon, target, move); this.runEvent('AfterMoveSecondarySelf', pokemon, target, move); } return true; }, tryMoveHit: function (target, pokemon, move, spreadHit) { if (move.selfdestruct && spreadHit) pokemon.hp = 0; this.setActiveMove(move, pokemon, target); var hitResult = true; hitResult = this.singleEvent('PrepareHit', move, {}, target, pokemon, move); if (!hitResult) { if (hitResult === false) this.add('-fail', target); return false; } this.runEvent('PrepareHit', pokemon, target, move); if (!this.singleEvent('Try', move, null, pokemon, target, move)) { return false; } if (move.target === 'all' || move.target === 'foeSide' || move.target === 'allySide' || move.target === 'allyTeam') { if (move.target === 'all') { hitResult = this.runEvent('TryHitField', target, pokemon, move); } else { hitResult = this.runEvent('TryHitSide', target, pokemon, move); } if (!hitResult) { if (hitResult === false) this.add('-fail', target); return true; } return this.moveHit(target, pokemon, move); } if (move.affectedByImmunities && !target.runImmunity(move.type, true)) { return false; } if (typeof move.affectedByImmunities === 'undefined') { move.affectedByImmunities = (move.category !== 'Status'); } hitResult = this.runEvent('TryHit', target, pokemon, move); if (!hitResult) { if (hitResult === false) this.add('-fail', target); return false; } var boostTable = [1, 4 / 3, 5 / 3, 2, 7 / 3, 8 / 3, 3]; // calculate true accuracy var accuracy = move.accuracy; if (accuracy !== true) { var targetAbilityIgnoreAccuracy = !target.ignore['Ability'] && target.getAbility().ignoreAccuracy; if (!move.ignoreAccuracy && !targetAbilityIgnoreAccuracy) { if (pokemon.boosts.accuracy > 0) { accuracy *= boostTable[pokemon.boosts.accuracy]; } else { accuracy /= boostTable[-pokemon.boosts.accuracy]; } } var pokemonAbilityIgnoreEvasion = !pokemon.ignore['Ability'] && pokemon.getAbility().ignoreEvasion; if (!move.ignoreEvasion && !pokemonAbilityIgnoreEvasion) { if (target.boosts.evasion > 0 && !move.ignorePositiveEvasion) { accuracy /= boostTable[target.boosts.evasion]; } else if (target.boosts.evasion < 0) { accuracy *= boostTable[-target.boosts.evasion]; } } } if (move.ohko) { // bypasses accuracy modifiers if (!target.volatiles['bounce'] && !target.volatiles['dig'] && !target.volatiles['dive'] && !target.volatiles['fly'] && !target.volatiles['shadowforce'] && !target.volatiles['skydrop']) { accuracy = 30; if (pokemon.level > target.level) accuracy += (pokemon.level - target.level); } } if (move.alwaysHit) { accuracy = true; // bypasses ohko accuracy modifiers } else { accuracy = this.runEvent('Accuracy', target, pokemon, move, accuracy); } if (accuracy !== true && this.random(100) >= accuracy) { if (!spreadHit) this.attrLastMove('[miss]'); this.add('-miss', pokemon, target); return false; } var totalDamage = 0; var damage = 0; pokemon.lastDamage = 0; if (move.multihit) { var hits = move.multihit; if (hits.length) { // yes, it's hardcoded... meh if (hits[0] === 2 && hits[1] === 5) { if (this.gen >= 5) { hits = [2, 2, 3, 3, 4, 5][this.random(6)]; } else { hits = [2, 2, 2, 3, 3, 3, 4, 5][this.random(8)]; } } else { hits = this.random(hits[0], hits[1] + 1); } } hits = Math.floor(hits); var nullDamage = true; var moveDamage; // There is no need to recursively check the ´sleepUsable´ flag as Sleep Talk can only be used while asleep. var isSleepUsable = move.sleepUsable || this.getMove(move.sourceEffect).sleepUsable; var i; for (i = 0; i < hits && target.hp && pokemon.hp; i++) { if (pokemon.status === 'slp' && !isSleepUsable) break; moveDamage = this.moveHit(target, pokemon, move); if (moveDamage === false) break; if (nullDamage && (moveDamage || moveDamage === 0)) nullDamage = false; // Damage from each hit is individually counted for the // purposes of Counter, Metal Burst, and Mirror Coat. damage = (moveDamage || 0); // Total damage dealt is accumulated for the purposes of recoil (Parental Bond). totalDamage += damage; this.eachEvent('Update'); } if (i === 0) return true; if (nullDamage) damage = false; this.add('-hitcount', target, i); } else { damage = this.moveHit(target, pokemon, move); totalDamage = damage; } if (move.recoil) { this.damage(this.clampIntRange(Math.round(totalDamage * move.recoil[0] / move.recoil[1]), 1), pokemon, target, 'recoil'); } if (target && move.category !== 'Status') target.gotAttacked(move, damage, pokemon); if (!damage && damage !== 0) return damage; if (target && !move.negateSecondary) { this.singleEvent('AfterMoveSecondary', move, null, target, pokemon, move); this.runEvent('AfterMoveSecondary', target, pokemon, move); } return damage; }, moveHit: function (target, pokemon, move, moveData, isSecondary, isSelf) { var damage; move = this.getMoveCopy(move); if (!moveData) moveData = move; var hitResult = true; // TryHit events: // STEP 1: we see if the move will succeed at all: // - TryHit, TryHitSide, or TryHitField are run on the move, // depending on move target (these events happen in useMove // or tryMoveHit, not below) // == primary hit line == // Everything after this only happens on the primary hit (not on // secondary or self-hits) // STEP 2: we see if anything blocks the move from hitting: // - TryFieldHit is run on the target // STEP 3: we see if anything blocks the move from hitting the target: // - If the move's target is a pokemon, TryHit is run on that pokemon // Note: // If the move target is `foeSide`: // event target = pokemon 0 on the target side // If the move target is `allySide` or `all`: // event target = the move user // // This is because events can't accept actual sides or fields as // targets. Choosing these event targets ensures that the correct // side or field is hit. // // It is the `TryHitField` event handler's responsibility to never // use `target`. // It is the `TryFieldHit` event handler's responsibility to read // move.target and react accordingly. // An exception is `TryHitSide` as a single event (but not as a normal // event), which is passed the target side. if (move.target === 'all' && !isSelf) { hitResult = this.singleEvent('TryHitField', moveData, {}, target, pokemon, move); } else if ((move.target === 'foeSide' || move.target === 'allySide') && !isSelf) { hitResult = this.singleEvent('TryHitSide', moveData, {}, target.side, pokemon, move); } else if (target) { hitResult = this.singleEvent('TryHit', moveData, {}, target, pokemon, move); } if (!hitResult) { if (hitResult === false) this.add('-fail', target); return false; } if (target && !isSecondary && !isSelf) { hitResult = this.runEvent('TryPrimaryHit', target, pokemon, moveData); if (hitResult === 0) { // special Substitute flag hitResult = true; target = null; } } if (target && isSecondary && !moveData.self) { hitResult = true; } if (!hitResult) { return false; } if (target) { var didSomething = false; damage = this.getDamage(pokemon, target, moveData); // getDamage has several possible return values: // // a number: // means that much damage is dealt (0 damage still counts as dealing // damage for the purposes of things like Static) // false: // gives error message: "But it failed!" and move ends // null: // the move ends, with no message (usually, a custom fail message // was already output by an event handler) // undefined: // means no damage is dealt and the move continues // // basically, these values have the same meanings as they do for event // handlers. if ((damage || damage === 0) && !target.fainted) { if (move.noFaint && damage >= target.hp) { damage = target.hp - 1; } damage = this.damage(damage, target, pokemon, move); if (!(damage || damage === 0)) { this.debug('damage interrupted'); return false; } didSomething = true; } if (damage === false || damage === null) { if (damage === false) { this.add('-fail', target); } this.debug('damage calculation interrupted'); return false; } if (moveData.boosts && !target.fainted) { hitResult = this.boost(moveData.boosts, target, pokemon, move); didSomething = didSomething || hitResult; } if (moveData.heal && !target.fainted) { var d = target.heal((this.gen < 5 ? Math.floor : Math.round)(target.maxhp * moveData.heal[0] / moveData.heal[1])); if (!d && d !== 0) { this.add('-fail', target); this.debug('heal interrupted'); return false; } this.add('-heal', target, target.getHealth); didSomething = true; } if (moveData.status) { if (!target.status) { hitResult = target.setStatus(moveData.status, pokemon, move); didSomething = didSomething || hitResult; } else if (!isSecondary) { if (target.status === moveData.status) { this.add('-fail', target, target.status); } else { this.add('-fail', target); } return false; } } if (moveData.forceStatus) { hitResult = target.setStatus(moveData.forceStatus, pokemon, move); didSomething = didSomething || hitResult; } if (moveData.volatileStatus) { hitResult = target.addVolatile(moveData.volatileStatus, pokemon, move); didSomething = didSomething || hitResult; } if (moveData.sideCondition) { hitResult = target.side.addSideCondition(moveData.sideCondition, pokemon, move); didSomething = didSomething || hitResult; } if (moveData.weather) { hitResult = this.setWeather(moveData.weather, pokemon, move); didSomething = didSomething || hitResult; } if (moveData.terrain) { hitResult = this.setTerrain(moveData.terrain, pokemon, move); didSomething = didSomething || hitResult; } if (moveData.pseudoWeather) { hitResult = this.addPseudoWeather(moveData.pseudoWeather, pokemon, move); didSomething = didSomething || hitResult; } if (moveData.forceSwitch) { if (this.canSwitch(target.side)) didSomething = true; // at least defer the fail message to later } if (moveData.selfSwitch) { if (this.canSwitch(pokemon.side)) didSomething = true; // at least defer the fail message to later } // Hit events // These are like the TryHit events, except we don't need a FieldHit event. // Scroll up for the TryHit event documentation, and just ignore the "Try" part. ;) hitResult = null; if (move.target === 'all' && !isSelf) { if (moveData.onHitField) hitResult = this.singleEvent('HitField', moveData, {}, target, pokemon, move); } else if ((move.target === 'foeSide' || move.target === 'allySide') && !isSelf) { if (moveData.onHitSide) hitResult = this.singleEvent('HitSide', moveData, {}, target.side, pokemon, move); } else { if (moveData.onHit) hitResult = this.singleEvent('Hit', moveData, {}, target, pokemon, move); if (!isSelf && !isSecondary) { this.runEvent('Hit', target, pokemon, move); } if (moveData.onAfterHit) hitResult = this.singleEvent('AfterHit', moveData, {}, target, pokemon, move); } if (!hitResult && !didSomething && !moveData.self && !moveData.selfdestruct) { if (!isSelf && !isSecondary) { if (hitResult === false || didSomething === false) this.add('-fail', target); } this.debug('move failed because it did nothing'); return false; } } if (moveData.self) { var selfRoll; if (!isSecondary && moveData.self.boosts) selfRoll = this.random(100); // This is done solely to mimic in-game RNG behaviour. All self drops have a 100% chance of happening but still grab a random number. if (typeof moveData.self.chance === 'undefined' || selfRoll < moveData.self.chance) { this.moveHit(pokemon, pokemon, move, moveData.self, isSecondary, true); } } if (moveData.secondaries && this.runEvent('TrySecondaryHit', target, pokemon, moveData)) { var secondaryRoll; for (var i = 0; i < moveData.secondaries.length; i++) { secondaryRoll = this.random(100); if (typeof moveData.secondaries[i].chance === 'undefined' || secondaryRoll < moveData.secondaries[i].chance) { this.moveHit(target, pokemon, move, moveData.secondaries[i], true, isSelf); } } } if (target && target.hp > 0 && pokemon.hp > 0 && moveData.forceSwitch && this.canSwitch(target.side)) { hitResult = this.runEvent('DragOut', target, pokemon, move); if (hitResult) { target.forceSwitchFlag = true; } else if (hitResult === false) { this.add('-fail', target); } } if (move.selfSwitch && pokemon.hp) { pokemon.switchFlag = move.selfSwitch; } return damage; }, canMegaEvo: function (pokemon) { var altForme = pokemon.baseTemplate.otherFormes && this.getTemplate(pokemon.baseTemplate.otherFormes[0]); if (altForme && altForme.isMega && altForme.requiredMove && pokemon.moves.indexOf(toId(altForme.requiredMove)) > -1) return altForme.species; var item = pokemon.getItem(); if (item.megaEvolves !== pokemon.baseTemplate.baseSpecies || item.megaStone === pokemon.species) return false; return item.megaStone; }, runMegaEvo: function (pokemon) { var template = this.getTemplate(pokemon.canMegaEvo); var side = pokemon.side; // Pokémon affected by Sky Drop cannot mega evolve. Enforce it here for now. var foeActive = side.foe.active; for (var i = 0; i < foeActive.length; i++) { if (foeActive[i].volatiles['skydrop'] && foeActive[i].volatiles['skydrop'].source === pokemon) { return false; } } pokemon.formeChange(template); pokemon.baseTemplate = template; // mega evolution is permanent pokemon.details = template.species + (pokemon.level === 100 ? '' : ', L' + pokemon.level) + (pokemon.gender === '' ? '' : ', ' + pokemon.gender) + (pokemon.set.shiny ? ', shiny' : ''); this.add('detailschange', pokemon, pokemon.details); this.add('-mega', pokemon, template.baseSpecies, template.requiredItem); pokemon.setAbility(template.abilities['0']); pokemon.baseAbility = pokemon.ability; // Limit one mega evolution for (var i = 0; i < side.pokemon.length; i++) { side.pokemon[i].canMegaEvo = false; } return true; }, isAdjacent: function (pokemon1, pokemon2) { if (pokemon1.fainted || pokemon2.fainted) return false; if (pokemon1.side === pokemon2.side) return Math.abs(pokemon1.position - pokemon2.position) === 1; return Math.abs(pokemon1.position + pokemon2.position + 1 - pokemon1.side.active.length) <= 1; }, checkAbilities: function (selectedAbilities, defaultAbilities) { if (!selectedAbilities.length) return true; var selectedAbility = selectedAbilities.pop(); var isValid = false; for (var i = 0; i < defaultAbilities.length; i++) { var defaultAbility = defaultAbilities[i]; if (!defaultAbility) break; if (defaultAbility.indexOf(selectedAbility) !== -1) { defaultAbilities.splice(i, 1); isValid = this.checkAbilities(selectedAbilities, defaultAbilities); if (isValid) break; defaultAbilities.splice(i, 0, defaultAbility); } } if (!isValid) selectedAbilities.push(selectedAbility); return isValid; }, sampleNoReplace: function (list) { var length = list.length; var index = this.random(length); var element = list[index]; for (var nextIndex = index + 1; nextIndex < length; index += 1, nextIndex += 1) { list[index] = list[nextIndex]; } list.pop(); return element; }, hasMegaEvo: function (template) { if (template.otherFormes) { var forme = this.getTemplate(template.otherFormes[0]); if (forme.requiredItem) { var item = this.getItem(forme.requiredItem); if (item.megaStone) return true; } else if (forme.requiredMove && forme.isMega) { return true; } } return false; }, getTeam: function (side, team) { var format = side.battle.getFormat(); if (format.team === 'random') { return this.randomTeam(side); } else if (typeof format.team === 'string' && format.team.substr(0, 6) === 'random') { return this[format.team + 'Team'](side); } else if (team) { return team; } else { return this.randomTeam(side); } }, randomCCTeam: function (side) { var team = []; var natures = Object.keys(this.data.Natures); var items = Object.keys(this.data.Items); var hasDexNumber = {}; var formes = [[], [], [], [], [], []]; // pick six random pokemon--no repeats, even among formes // also need to either normalize for formes or select formes at random // unreleased are okay. No CAP for now, but maybe at some later date var num; for (var i = 0; i < 6; i++) { do { num = this.random(721) + 1; } while (num in hasDexNumber); hasDexNumber[num] = i; } for (var id in this.data.Pokedex) { if (!(this.data.Pokedex[id].num in hasDexNumber)) continue; var template = this.getTemplate(id); if (template.learnset && template.species !== 'Pichu-Spiky-eared') { formes[hasDexNumber[template.num]].push(template.species); } } for (var i = 0; i < 6; i++) { var poke = formes[i][this.random(formes[i].length)]; var template = this.getTemplate(poke); //level balance--calculate directly from stats rather than using some silly lookup table var mbstmin = 1307; //sunkern has the lowest modified base stat total, and that total is 807 var stats = template.baseStats; //modified base stat total assumes 31 IVs, 85 EVs in every stat var mbst = (stats["hp"] * 2 + 31 + 21 + 100) + 10; mbst += (stats["atk"] * 2 + 31 + 21 + 100) + 5; mbst += (stats["def"] * 2 + 31 + 21 + 100) + 5; mbst += (stats["spa"] * 2 + 31 + 21 + 100) + 5; mbst += (stats["spd"] * 2 + 31 + 21 + 100) + 5; mbst += (stats["spe"] * 2 + 31 + 21 + 100) + 5; var level = Math.floor(100 * mbstmin / mbst); //initial level guess will underestimate while (level < 100) { mbst = Math.floor((stats["hp"] * 2 + 31 + 21 + 100) * level / 100 + 10); mbst += Math.floor(((stats["atk"] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100); //since damage is roughly proportional to lvl mbst += Math.floor((stats["def"] * 2 + 31 + 21 + 100) * level / 100 + 5); mbst += Math.floor(((stats["spa"] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100); mbst += Math.floor((stats["spd"] * 2 + 31 + 21 + 100) * level / 100 + 5); mbst += Math.floor((stats["spe"] * 2 + 31 + 21 + 100) * level / 100 + 5); if (mbst >= mbstmin) break; level++; } //random gender--already handled by PS? //random ability (unreleased hidden are par for the course) var abilities = [template.abilities['0']]; if (template.abilities['1']) { abilities.push(template.abilities['1']); } if (template.abilities['H']) { abilities.push(template.abilities['H']); } var ability = abilities[this.random(abilities.length)]; //random nature var nature = natures[this.random(natures.length)]; //random item var item = ''; if (template.requiredItem) { item = template.requiredItem; } else { item = items[this.random(items.length)]; } if (this.getItem(item).megaStone) { // we'll exclude mega stones for now item = items[this.random(items.length)]; } //since we're selecting forme at random, we gotta make sure forme/item combo is correct while (poke === 'Arceus' && item.substr(-5) !== 'plate' || poke === 'Giratina' && item === 'griseousorb') { item = items[this.random(items.length)]; } //random IVs var ivs = {hp: this.random(32), atk: this.random(32), def: this.random(32), spa: this.random(32), spd: this.random(32), spe: this.random(32)}; //random EVs var evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0}; var s = ["hp", "atk", "def", "spa", "spd", "spe"]; var evpool = 510; do { var x = s[this.random(s.length)]; var y = this.random(Math.min(256 - evs[x], evpool + 1)); evs[x] += y; evpool -= y; } while (evpool > 0); //random happiness--useless, since return/frustration is currently a "cheat" var happiness = this.random(256); //random shininess? var shiny = !this.random(1024); //four random unique moves from movepool. don't worry about "attacking" or "viable" var moves; var pool = ['struggle']; if (poke === 'Smeargle') { pool = Object.keys(this.data.Movedex).exclude('struggle', 'chatter', 'magikarpsrevenge'); } else if (template.learnset) { pool = Object.keys(template.learnset); } if (pool.length <= 4) { moves = pool; } else { moves = [this.sampleNoReplace(pool), this.sampleNoReplace(pool), this.sampleNoReplace(pool), this.sampleNoReplace(pool)]; } team.push({ name: poke, moves: moves, ability: ability, evs: evs, ivs: ivs, nature: nature, item: item, level: level, happiness: happiness, shiny: shiny }); } //console.log(team); return team; }, randomHackmonsCCTeam: function (side) { var team = []; var itemPool = Object.keys(this.data.Items); var abilityPool = Object.keys(this.data.Abilities); var movePool = Object.keys(this.data.Movedex); var naturePool = Object.keys(this.data.Natures); var hasDexNumber = {}; var formes = [[], [], [], [], [], []]; // pick six random pokemon--no repeats, even among formes // also need to either normalize for formes or select formes at random // unreleased are okay. No CAP for now, but maybe at some later date var num; for (var i = 0; i < 6; i++) { do { num = this.random(721) + 1; } while (num in hasDexNumber); hasDexNumber[num] = i; } for (var id in this.data.Pokedex) { if (!(this.data.Pokedex[id].num in hasDexNumber)) continue; var template = this.getTemplate(id); if (template.learnset && template.species !== 'Pichu-Spiky-eared') { formes[hasDexNumber[template.num]].push(template.species); } } for (var i = 0; i < 6; i++) { // Choose forme var pokemon = formes[i][this.random(formes[i].length)]; var template = this.getTemplate(pokemon); // Random unique item var item = ''; do { item = this.sampleNoReplace(itemPool); } while (this.data.Items[item].isNonstandard); // Genesect forms are a sprite difference based on its Drives if (template.species.substr(0, 9) === 'Genesect-' && item !== toId(template.requiredItem)) pokemon = 'Genesect'; // Random unique ability var ability = ''; do { ability = this.sampleNoReplace(abilityPool); } while (this.data.Abilities[ability].isNonstandard); // Random unique moves var m = []; while (true) { var moveid = this.sampleNoReplace(movePool); if (!this.data.Movedex[moveid].isNonstandard && (moveid === 'hiddenpower' || moveid.substr(0, 11) !== 'hiddenpower')) { if (m.push(moveid) >= 4) break; } } // Random EVs var evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0}; var s = ['hp', 'atk', 'def', 'spa', 'spd', 'spe']; var evpool = 510; do { var x = s[this.random(s.length)]; var y = this.random(Math.min(256 - evs[x], evpool + 1)); evs[x] += y; evpool -= y; } while (evpool > 0); // Random IVs var ivs = {hp: this.random(32), atk: this.random(32), def: this.random(32), spa: this.random(32), spd: this.random(32), spe: this.random(32)}; // Random nature var nature = naturePool[this.random(naturePool.length)]; // Level balance var mbstmin = 1307; var stats = template.baseStats; var mbst = (stats['hp'] * 2 + 31 + 21 + 100) + 10; mbst += (stats['atk'] * 2 + 31 + 21 + 100) + 5; mbst += (stats['def'] * 2 + 31 + 21 + 100) + 5; mbst += (stats['spa'] * 2 + 31 + 21 + 100) + 5; mbst += (stats['spd'] * 2 + 31 + 21 + 100) + 5; mbst += (stats['spe'] * 2 + 31 + 21 + 100) + 5; var level = Math.floor(100 * mbstmin / mbst); while (level < 100) { mbst = Math.floor((stats['hp'] * 2 + 31 + 21 + 100) * level / 100 + 10); mbst += Math.floor(((stats['atk'] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100); mbst += Math.floor((stats['def'] * 2 + 31 + 21 + 100) * level / 100 + 5); mbst += Math.floor(((stats['spa'] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100); mbst += Math.floor((stats['spd'] * 2 + 31 + 21 + 100) * level / 100 + 5); mbst += Math.floor((stats['spe'] * 2 + 31 + 21 + 100) * level / 100 + 5); if (mbst >= mbstmin) break; level++; } // Random happiness var happiness = this.random(256); // Random shininess var shiny = !this.random(1024); team.push({ name: pokemon, item: item, ability: ability, moves: m, evs: evs, ivs: ivs, nature: nature, level: level, happiness: happiness, shiny: shiny }); } return team; }, randomSet: function (template, slot, noMega) { if (slot === undefined) slot = 1; var baseTemplate = (template = this.getTemplate(template)); var name = template.name; if (!template.exists || (!template.randomBattleMoves && !template.learnset)) { // GET IT? UNOWN? BECAUSE WE CAN'T TELL WHAT THE POKEMON IS template = this.getTemplate('unown'); var stack = 'Template incompatible with random battles: ' + name; var fakeErr = {stack: stack}; require('../crashlogger.js')(fakeErr, 'The randbat set generator'); } // Decide if the Pokemon can mega evolve early, so viable moves for the mega can be generated if (!noMega && this.hasMegaEvo(template)) { // If there's more than one mega evolution, randomly pick one template = this.getTemplate(template.otherFormes[this.random(template.otherFormes.length)]); } if (template.otherFormes && this.getTemplate(template.otherFormes[0]).isPrimal && this.random(2)) { template = this.getTemplate(template.otherFormes[0]); } var movePool = (template.randomBattleMoves ? template.randomBattleMoves.slice() : Object.keys(template.learnset)); 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 availableHP = 0; for (var i = 0, len = movePool.length; i < len; i++) { if (movePool[i].substr(0, 11) === 'hiddenpower') availableHP++; } // Moves that heal a fixed amount: var RecoveryMove = { milkdrink: 1, recover: 1, roost: 1, slackoff: 1, softboiled: 1 }; // Moves which drop stats: var ContraryMove = { leafstorm: 1, overheat: 1, closecombat: 1, superpower: 1, vcreate: 1 }; // Moves that boost Attack: var PhysicalSetup = { swordsdance:1, dragondance:1, coil:1, bulkup:1, curse:1, bellydrum:1, shiftgear:1, honeclaws:1, howl:1, poweruppunch:1 }; // Moves which boost Special Attack: var SpecialSetup = { nastyplot:1, tailglow:1, quiverdance:1, calmmind:1, chargebeam:1, geomancy:1 }; // Moves which boost Attack AND Special Attack: var MixedSetup = { growth:1, workup:1, shellsmash:1 }; var SpeedSetup = { autotomize:1, agility:1, rockpolish:1 }; // These moves can be used even if we aren't setting up to use them: var SetupException = { dracometeor:1, leafstorm:1, overheat:1, extremespeed:1, suckerpunch:1, superpower:1, uturn:1, voltswitch:1 }; var counterAbilities = { 'Blaze':1, 'Overgrow':1, 'Swarm':1, 'Torrent':1, 'Contrary':1, 'Technician':1, 'Skill Link':1, 'Iron Fist':1, 'Adaptability':1, 'Hustle':1 }; var damagingMoves, damagingMoveIndex, hasMove, counter, setupType, hasStab; do { // Keep track of all moves we have: hasMove = {}; for (var k = 0; k < moves.length; k++) { if (moves[k].substr(0, 11) === 'hiddenpower') { hasMove['hiddenpower'] = true; } else { hasMove[moves[k]] = true; } } // Choose next 4 moves from learnset/viable moves and add them to moves list: while (moves.length < 4 && movePool.length) { var moveid = this.sampleNoReplace(movePool); if (moveid.substr(0, 11) === 'hiddenpower') { availableHP--; if (hasMove['hiddenpower']) continue; hasMove['hiddenpower'] = true; } else { hasMove[moveid] = true; } moves.push(moveid); } damagingMoves = []; damagingMoveIndex = {}; hasStab = false; counter = { Physical: 0, Special: 0, Status: 0, damage: 0, recovery: 0, blaze: 0, overgrow: 0, swarm: 0, torrent: 0, adaptability: 0, ate: 0, bite: 0, contrary: 0, hustle: 0, ironfist: 0, serenegrace: 0, sheerforce: 0, skilllink: 0, technician: 0, inaccurate: 0, priority: 0, recoil: 0, physicalsetup: 0, specialsetup: 0, mixedsetup: 0, speedsetup: 0 }; setupType = ''; // Iterate through all moves we've chosen so far and keep track of what they do: for (var k = 0; k < moves.length; k++) { var move = this.getMove(moves[k]); var moveid = move.id; if (move.damage || move.damageCallback) { // Moves that do a set amount of damage: counter['damage']++; damagingMoves.push(move); damagingMoveIndex[moveid] = k; } else { // Are Physical/Special/Status moves: counter[move.category]++; } // Moves that have a low base power: if (moveid === 'lowkick' || (move.basePower && move.basePower <= 60)) counter['technician']++; // Moves that hit multiple times: if (move.multihit && move.multihit[1] === 5) counter['skilllink']++; // Recoil: if (move.recoil) counter['recoil']++; // Moves which have a base power, but aren't super-weak like Rapid Spin: if (move.basePower > 20 || move.multihit || move.basePowerCallback) { if (hasType[move.type]) { counter['adaptability']++; // STAB: // Bounce, Sky Attack, Flame Charge aren't considered STABs. // If they're in the Pokémon's movepool and are STAB, consider the Pokémon not to have that type as a STAB. if (moveid !== 'skyattack' && moveid !== 'bounce' && moveid !== 'flamecharge') hasStab = true; } if (move.category === 'Physical') counter['hustle']++; if (move.type === 'Fire') counter['blaze']++; if (move.type === 'Grass') counter['overgrow']++; if (move.type === 'Bug') counter['swarm']++; if (move.type === 'Water') counter['torrent']++; if (move.type === 'Normal') counter['ate']++; if (move.flags['bite']) counter['bite']++; if (move.flags['punch']) counter['ironfist']++; damagingMoves.push(move); damagingMoveIndex[moveid] = k; } // Moves with secondary effects: if (move.secondary) { counter['sheerforce']++; if (move.secondary.chance >= 20) { counter['serenegrace']++; } } // Moves with low accuracy: if (move.accuracy && move.accuracy !== true && move.accuracy < 90) counter['inaccurate']++; // Moves with non-zero priority: if (move.priority !== 0) counter['priority']++; // Moves that change stats: if (RecoveryMove[moveid]) counter['recovery']++; if (ContraryMove[moveid]) counter['contrary']++; if (PhysicalSetup[moveid]) counter['physicalsetup']++; if (SpecialSetup[moveid]) counter['specialsetup']++; if (MixedSetup[moveid]) counter['mixedsetup']++; if (SpeedSetup[moveid]) counter['speedsetup']++; } // Choose a setup type: if (counter['mixedsetup']) { setupType = 'Mixed'; } else if (counter['specialsetup']) { setupType = 'Special'; } else if (counter['physicalsetup']) { setupType = 'Physical'; } // Iterate through the moves again, this time to cull them: for (var k = 0; k < moves.length; k++) { var moveid = moves[k]; var move = this.getMove(moveid); var rejected = false; var isSetup = false; switch (moveid) { // Not very useful without their supporting moves case 'sleeptalk': if (!hasMove['rest']) rejected = true; break; case 'endure': if (!hasMove['flail'] && !hasMove['endeavor'] && !hasMove['reversal']) rejected = true; break; case 'focuspunch': if (hasMove['sleeptalk'] || !hasMove['substitute']) rejected = true; break; case 'storedpower': if (!hasMove['cosmicpower'] && !setupType) rejected = true; break; case 'batonpass': if (!setupType && !hasMove['substitute'] && !hasMove['protect'] && !hasMove['cosmicpower']) rejected = true; break; // We only need to set up once case 'swordsdance': case 'dragondance': case 'coil': case 'curse': case 'bulkup': case 'bellydrum': if (counter.Physical < 2 && !hasMove['batonpass']) rejected = true; if (setupType !== 'Physical' || counter['physicalsetup'] > 1) rejected = true; isSetup = true; break; case 'nastyplot': case 'tailglow': case 'quiverdance': case 'calmmind': case 'geomancy': if (counter.Special < 2 && !hasMove['batonpass']) rejected = true; if (setupType !== 'Special' || counter['specialsetup'] > 1) rejected = true; isSetup = true; break; case 'shellsmash': case 'growth': case 'workup': if (counter.Physical + counter.Special < 2 && !hasMove['batonpass']) rejected = true; if (setupType !== 'Mixed' || counter['mixedsetup'] > 1) rejected = true; isSetup = true; break; // Bad after setup case 'foulplay': case 'relicsong': case 'nightshade': case 'seismictoss': case 'superfang': case 'rapidspin': if (setupType) rejected = true; break; case 'pursuit': case 'eruption': case 'waterspout': case 'defog': case 'haze': case 'healingwish': case 'magiccoat': case 'protect': case 'spikes': case 'stealthrock': if (setupType || (hasMove['rest'] && hasMove['sleeptalk'])) rejected = true; break; case 'trick': case 'switcheroo': if (setupType || (hasMove['rest'] && hasMove['sleeptalk']) || hasMove['trickroom'] || hasMove['reflect'] || hasMove['lightscreen'] || hasMove['acrobatics']) rejected = true; break; case 'dragontail': case 'circlethrow': if (!!counter['speedsetup'] || hasMove['whirlwind'] || hasMove['roar'] || hasMove['encore']) rejected = true; break; case 'trickroom': if (!!counter['speedsetup']) rejected = true; break; // Bit redundant to have both // Attacks: case 'flamethrower': case 'fierydance': if (hasMove['lavaplume'] || hasMove['overheat'] || hasMove['fireblast'] || hasMove['blueflare']) rejected = true; break; case 'fireblast': if (hasMove['lavaplume'] || hasMove['overheat']) rejected = true; break; case 'firepunch': if (hasMove['flareblitz']) rejected = true; break; case 'flareblitz': if (hasMove['fireblast'] || hasMove['sacredfire']) rejected = true; break; case 'overheat': if (setupType === 'Special' || hasMove['lavaplume']) rejected = true; break; case 'flamecharge': if (hasMove['tailwind']) rejected = true; break; case 'icebeam': if (hasMove['blizzard']) rejected = true; break; case 'naturepower': if (hasMove['hypervoice']) rejected = true; break; case 'hypervoice': if (hasMove['return']) rejected = true; break; case 'surf': case 'originpulse': if (hasMove['scald'] || hasMove['hydropump']) rejected = true; break; case 'hydropump': if (hasMove['razorshell'] || hasMove['scald'] || (hasMove['rest'] && hasMove['sleeptalk'])) rejected = true; break; case 'waterfall': case 'waterpulse': if (hasMove['aquatail'] || hasMove['scald']) rejected = true; break; case 'shadowforce': case 'phantomforce': case 'shadowsneak': if (hasMove['shadowclaw']) rejected = true; break; case 'shadowclaw': if (hasMove['shadowball']) rejected = true; break; case 'ironhead': if (hasMove['flashcannon']) rejected = true; break; case 'acrobatics': case 'pluck': case 'drillpeck': case 'airslash': case 'oblivionwing': if (hasMove['bravebird'] || hasMove['hurricane']) rejected = true; break; case 'solarbeam': if ((!hasMove['sunnyday'] && template.species !== 'Ninetales') || hasMove['gigadrain'] || hasMove['leafstorm']) rejected = true; break; case 'gigadrain': if ((!setupType && hasMove['leafstorm']) || hasMove['petaldance']) rejected = true; break; case 'leafstorm': if (setupType && hasMove['gigadrain']) rejected = true; break; case 'weatherball': if (!hasMove['sunnyday'] && !hasMove['raindance']) rejected = true; break; case 'bugbite': if (hasMove['uturn']) rejected = true; break; case 'crosschop': case 'highjumpkick': if (hasMove['closecombat']) rejected = true; break; case 'drainpunch': case 'focusblast': if (hasMove['closecombat'] || hasMove['highjumpkick'] || hasMove['crosschop'] || hasMove['focuspunch']) rejected = true; if (!setupType && hasMove['superpower']) rejected = true; if (moveid === 'focusblast' && hasMove['rest'] && hasMove['sleeptalk']) rejected = true; break; case 'superpower': if (setupType && (hasMove['drainpunch'] || hasMove['focusblast'])) rejected = true; break; case 'machpunch': if (hasMove['focuspunch']) rejected = true; break; case 'thunderbolt': if (hasMove['discharge'] || hasMove['thunder'] || (hasMove['wildcharge'] && hasMove['voltswitch'])) rejected = true; break; case 'rockslide': case 'rockblast': if (hasMove['stoneedge'] || hasMove['headsmash']) rejected = true; break; case 'headsmash': if (hasMove['stoneedge']) rejected = true; break; case 'bonemerang': case 'earthpower': case 'bulldoze': case 'precipiceblades': if (hasMove['earthquake']) rejected = true; break; case 'dragonclaw': if (hasMove['outrage'] || hasMove['dragontail']) rejected = true; break; case 'ancientpower': if (hasMove['paleowave']) rejected = true; break; case 'dragonpulse': case 'spacialrend': if (hasMove['dracometeor']) rejected = true; break; case 'return': if (hasMove['bodyslam'] || hasMove['facade'] || hasMove['doubleedge'] || hasMove['tailslap']) rejected = true; break; case 'bodyslam': if (hasMove['glare']) rejected = true; break; case 'poisonjab': if (hasMove['gunkshot']) rejected = true; break; case 'psychic': if (hasMove['psyshock'] || hasMove['storedpower']) rejected = true; break; case 'fusionbolt': if (setupType && hasMove['boltstrike']) rejected = true; break; case 'boltstrike': if (!setupType && hasMove['fusionbolt']) rejected = true; break; case 'hiddenpowerice': if (hasMove['icywind']) rejected = true; break; case 'drainingkiss': if (hasMove['dazzlinggleam']) rejected = true; break; case 'voltswitch': if (hasMove['uturn']) rejected = true; if (setupType || !!counter['speedsetup'] || hasMove['magnetrise']) rejected = true; break; case 'uturn': if (setupType || !!counter['speedsetup'] || hasMove['magnetrise']) rejected = true; break; case 'explosion': if (hasMove['wish']) rejected = true; break; case 'fakeout': if (setupType || hasMove['trick'] || hasMove['switcheroo']) rejected = true; break; // Status: case 'rest': if (hasMove['painsplit'] || hasMove['wish'] || hasMove['recover'] || hasMove['moonlight'] || hasMove['synthesis'] || hasMove['morningsun']) rejected = true; break; case 'softboiled': case 'roost': case 'moonlight': case 'synthesis': case 'morningsun': if (hasMove['wish'] || hasMove['recover']) rejected = true; break; case 'memento': if (hasMove['rest'] || hasMove['painsplit'] || hasMove['wish'] || hasMove['recover'] || hasMove['moonlight'] || hasMove['synthesis'] || hasMove['morningsun']) rejected = true; break; case 'perishsong': if (setupType || hasMove['roar'] || hasMove['whirlwind'] || hasMove['haze']) rejected = true; break; case 'roar': if (hasMove['whirlwind'] || hasMove['dragontail'] || hasMove['haze'] || hasMove['circlethrow']) rejected = true; break; case 'substitute': if (hasMove['taunt'] || hasMove['uturn'] || hasMove['voltswitch'] || hasMove['pursuit'] || hasMove['dracometeor']) rejected = true; break; case 'encore': if (hasMove['rest'] && hasMove['sleeptalk']) rejected = true; if (hasMove['whirlwind'] || hasMove['dragontail'] || hasMove['roar'] || hasMove['circlethrow']) rejected = true; break; case 'suckerpunch': case 'lunardance': case 'raindance': case 'sunnyday': if (hasMove['rest'] && hasMove['sleeptalk']) rejected = true; break; case 'cottonguard': if (hasMove['reflect']) rejected = true; break; case 'lightscreen': if (hasMove['calmmind']) rejected = true; break; case 'rockpolish': case 'agility': case 'autotomize': if (counter.Physical + counter.Special < 2 && !hasMove['batonpass'] && !setupType) rejected = true; if ((hasMove['stealthrock'] || hasMove['spikes'] || hasMove['toxicspikes']) && !hasMove['batonpass']) rejected = true; if (hasMove['rest'] || hasMove['sleeptalk']) rejected = true; break; case 'thunderwave': case 'stunspore': if (setupType || !!counter['speedsetup']) rejected = true; if (hasMove['discharge'] || hasMove['trickroom'] || hasMove['gyroball']) rejected = true; if (hasMove['rest'] && hasMove['sleeptalk']) rejected = true; if (hasMove['yawn'] || hasMove['spore'] || hasMove['sleeppowder']) rejected = true; break; case 'willowisp': if (hasMove['scald'] || hasMove['lavaplume'] || hasMove['sacredfire'] || hasMove['yawn'] || hasMove['spore'] || hasMove['sleeppowder'] || hasMove['hypnosis']) rejected = true; break; case 'toxic': if (hasMove['thunderwave'] || hasMove['willowisp'] || hasMove['yawn'] || hasMove['spore'] || hasMove['sleeppowder'] || hasMove['stunspore'] || hasMove['hypnosis']) rejected = true; if (hasMove['rest'] && hasMove['sleeptalk']) rejected = true; break; } // Increased/decreased priority moves unneeded with moves that boost only speed if (move.priority !== 0 && !!counter['speedsetup']) { rejected = true; } if (move.category === 'Special' && setupType === 'Physical' && !SetupException[move.id]) { rejected = true; } if (move.category === 'Physical' && (setupType === 'Special' || hasMove['acidspray']) && !SetupException[move.id]) { rejected = true; } // This move doesn't satisfy our setup requirements: if (setupType === 'Physical' && move.category !== 'Physical' && counter['Physical'] < 2) { rejected = true; } if (setupType === 'Special' && move.category !== 'Special' && counter['Special'] < 2) { rejected = true; } // Hidden Power isn't good enough if (setupType === 'Special' && move.id === 'hiddenpower' && counter['Special'] <= 2 && (!hasMove['shadowball'] || move.type !== 'Fighting')) { rejected = true; } // Remove rejected moves from the move list. if (rejected && (movePool.length - availableHP || availableHP && (move.id === 'hiddenpower' || !hasMove['hiddenpower']))) { moves.splice(k, 1); break; } // handle HP IVs if (move.id === 'hiddenpower') { var HPivs = this.getType(move.type).HPivs; for (var iv in HPivs) { ivs[iv] = HPivs[iv]; } } } if (movePool.length && moves.length === 4 && !hasMove['judgment']) { // Move post-processing: if (damagingMoves.length === 0) { // A set shouldn't have no attacking moves moves.splice(this.random(moves.length), 1); } else if (damagingMoves.length === 1) { var damagingid = damagingMoves[0].id; // Night Shade, Seismic Toss, etc. don't count: if (!damagingMoves[0].damage && (movePool.length - availableHP || availableHP && (damagingid === 'hiddenpower' || !hasMove['hiddenpower']))) { var replace = false; if (damagingid in {counter:1, focuspunch:1, mirrorcoat:1, suckerpunch:1} || (damagingid === 'hiddenpower' && !hasStab)) { // Unacceptable as the only attacking move replace = true; } else { if (!hasStab) { var damagingType = damagingMoves[0].type; if (damagingType === 'Fairy') { // Mono-Fairy is acceptable for Psychic types if (!hasType['Psychic']) replace = true; } else if (damagingType === 'Ice') { if (hasType['Normal'] && template.types.length === 1) { // Mono-Ice is acceptable for special attacking Normal types that lack Boomburst and Hyper Voice if (counter.Physical >= 2 || movePool.indexOf('boomburst') > -1 || movePool.indexOf('hypervoice') > -1) replace = true; } else { replace = true; } } else if (damagingType === 'Normal') { // -ate abilities on Mega Altaria/Gardevoir/Pinsir/Salamence if (template.abilities[0] !== 'Aerilate' && template.abilities[0] !== 'Pixilate') replace = true; } else { replace = true; } } } if (replace) moves.splice(damagingMoveIndex[damagingid], 1); } } else if (damagingMoves.length === 2) { // If you have two attacks, neither is STAB, and the combo isn't Electric/Ice or Fighting/Ghost, reject one of them at random. var type1 = damagingMoves[0].type, type2 = damagingMoves[1].type; var typeCombo = [type1, type2].sort().join('/'); if (!hasStab && typeCombo !== 'Electric/Ice' && typeCombo !== 'Fighting/Ghost') { var rejectableMoves = []; var baseDiff = movePool.length - availableHP; if (baseDiff || availableHP && (!hasMove['hiddenpower'] || damagingMoves[0].id === 'hiddenpower')) { rejectableMoves.push(damagingMoveIndex[damagingMoves[0].id]); } if (baseDiff || availableHP && (!hasMove['hiddenpower'] || damagingMoves[1].id === 'hiddenpower')) { rejectableMoves.push(damagingMoveIndex[damagingMoves[1].id]); } if (rejectableMoves.length) { moves.splice(rejectableMoves[this.random(rejectableMoves.length)], 1); } } } else if (!hasStab) { // If you have three or more attacks, and none of them are STAB, reject one of them at random. var rejectableMoves = []; var baseDiff = movePool.length - availableHP; for (var l = 0; l < damagingMoves.length; l++) { if (baseDiff || availableHP && (!hasMove['hiddenpower'] || damagingMoves[l].id === 'hiddenpower')) { rejectableMoves.push(damagingMoveIndex[damagingMoves[l].id]); } } if (rejectableMoves.length) { moves.splice(rejectableMoves[this.random(rejectableMoves.length)], 1); } } } } while (moves.length < 4 && movePool.length); // any moveset modification goes here //moves[0] = 'safeguard'; if (template.requiredItem && template.requiredItem.slice(-5) === 'Drive' && !hasMove['technoblast']) { delete hasMove[this.getMove(moves[3]).id]; moves[3] = 'technoblast'; hasMove['technoblast'] = true; } if (template.id === 'altariamega' && !counter['ate']) { delete hasMove[this.getMove(moves[3]).id]; moves[3] = 'return'; hasMove['return'] = true; } if (template.id === 'gardevoirmega' && !counter['ate']) { delete hasMove[this.getMove(moves[3]).id]; moves[3] = 'hypervoice'; hasMove['hypervoice'] = true; } if (template.id === 'salamencemega' && !counter['ate']) { delete hasMove[this.getMove(moves[3]).id]; moves[3] = 'return'; hasMove['return'] = true; } if (template.id === 'sylveon' && !counter['ate']) { delete hasMove[this.getMove(moves[3]).id]; moves[3] = 'hypervoice'; hasMove['hypervoice'] = true; } if (template.requiredMove && !hasMove[toId(template.requiredMove)]) { delete hasMove[this.getMove(moves[3]).id]; moves[3] = toId(template.requiredMove); hasMove[toId(template.requiredMove)] = true; } // If Hidden Power has been removed, reset the IVs if (!hasMove['hiddenpower']) { ivs = { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 }; } var abilities = Object.values(baseTemplate.abilities).sort(function (a, b) { return this.getAbility(b).rating - this.getAbility(a).rating; }.bind(this)); var ability0 = this.getAbility(abilities[0]); var ability1 = this.getAbility(abilities[1]); var ability = ability0.name; if (abilities[1]) { if (ability0.rating <= ability1.rating) { if (this.random(2)) ability = ability1.name; } else if (ability0.rating - 0.6 <= ability1.rating) { if (!this.random(3)) ability = ability1.name; } var rejectAbility = false; if (ability in counterAbilities) { // Adaptability, Blaze, Contrary, Hustle, Iron Fist, Overgrow, Skill Link, Swarm, Technician, Torrent rejectAbility = !counter[toId(ability)]; } else if (ability === 'Rock Head' || ability === 'Reckless') { rejectAbility = !counter['recoil']; } else if (ability === 'Sturdy') { rejectAbility = !!counter['recoil'] && !hasMove['recover'] && !hasMove['roost']; } else if (ability === 'No Guard' || ability === 'Compound Eyes') { rejectAbility = !counter['inaccurate']; } else if (ability === 'Strong Jaw') { rejectAbility = !counter['bite']; } else if (ability === 'Sheer Force') { rejectAbility = !counter['sheerforce']; } else if (ability === 'Serene Grace') { rejectAbility = !counter['serenegrace'] || template.id === 'chansey' || template.id === 'blissey'; } else if (ability === 'Simple') { rejectAbility = !setupType && !hasMove['flamecharge'] && !hasMove['stockpile']; } else if (ability === 'Prankster') { rejectAbility = !counter['Status']; } else if (ability === 'Defiant' || ability === 'Moxie') { rejectAbility = !counter['Physical'] && !hasMove['batonpass']; } else if (ability === 'Aerilate' || ability === 'Pixilate' || ability === 'Refrigerate') { rejectAbility = !counter['ate'] && !hasMove['naturepower']; // below 2 checks should be modified, when it becomes possible, to check if the team contains rain or sun } else if (ability === 'Swift Swim') { rejectAbility = !hasMove['raindance']; } else if (ability === 'Chlorophyll') { rejectAbility = !hasMove['sunnyday']; } else if (ability === 'Moody') { rejectAbility = template.id !== 'bidoof'; } else if (ability === 'Limber') { rejectAbility = template.types.indexOf('Electric') >= 0; } else if (ability === 'Lightning Rod') { rejectAbility = template.types.indexOf('Ground') >= 0; } else if (ability === 'Unburden') { rejectAbility = template.baseStats.spe > 120; } else if (ability === 'Gluttony') { rejectAbility = true; } if (rejectAbility) { if (ability === ability1.name) { // or not ability = ability0.name; } else if (ability1.rating > 0) { // only switch if the alternative doesn't suck ability = ability1.name; } } if (abilities.indexOf('Guts') > -1 && ability !== 'Quick Feet' && hasMove['facade']) { ability = 'Guts'; } if (abilities.indexOf('Swift Swim') > -1 && hasMove['raindance']) { ability = 'Swift Swim'; } if (abilities.indexOf('Chlorophyll') > -1 && ability !== 'Solar Power' && hasMove['sunnyday']) { ability = 'Chlorophyll'; } if (template.id === 'sigilyph') { ability = 'Magic Guard'; } else if (template.id === 'bisharp') { ability = 'Defiant'; } else if (template.id === 'combee') { // Combee always gets Hustle but its only physical move is Endeavor, which loses accuracy ability = 'Honey Gather'; } else if (template.id === 'lopunny' && hasMove['switcheroo'] && this.random(3)) { ability = 'Klutz'; } else if (template.id === 'mawilemega') { // Mega Mawile only needs Intimidate for a starting ability ability = 'Intimidate'; } else if (template.id === 'unfezant') { ability = 'Super Luck'; } } if (hasMove['gyroball']) { ivs.spe = 0; evs.atk += evs.spe; evs.spe = 0; } else if (hasMove['trickroom']) { ivs.spe = 0; evs.hp += evs.spe; evs.spe = 0; } item = 'Leftovers'; if (template.requiredItem) { item = template.requiredItem; } else if (template.species === 'Rotom-Fan') { // this is just to amuse myself item = 'Air Balloon'; } else if (template.species === 'Delibird') { // to go along with the Christmas Delibird set item = 'Leftovers'; // First, the extra high-priority items } else if (ability === 'Imposter') { item = 'Choice Scarf'; } else if (hasMove['magikarpsrevenge']) { item = 'Choice Band'; } else if (ability === 'Wonder Guard') { item = 'Focus Sash'; } else if (template.species === 'Unown') { item = 'Choice Specs'; } else if (hasMove['bellydrum']) { item = 'Sitrus Berry'; } else if (hasMove['trick'] && hasMove['gyroball'] && (ability === 'Levitate' || hasType['Flying'])) { item = 'Macho Brace'; } else if (hasMove['trick'] && hasMove['gyroball']) { item = 'Iron Ball'; } else if (ability === 'Klutz' && hasMove['switcheroo']) { // To perma-taunt a Pokemon by giving it Assault Vest item = 'Assault Vest'; } else if (hasMove['trick'] || hasMove['switcheroo']) { var randomNum = this.random(2); if (counter.Physical >= 3 && (template.baseStats.spe >= 95 || randomNum)) { item = 'Choice Band'; } else if (counter.Special >= 3 && (template.baseStats.spe >= 95 || randomNum)) { item = 'Choice Specs'; } else { item = 'Choice Scarf'; } } else if (hasMove['rest'] && !hasMove['sleeptalk'] && ability !== 'Natural Cure' && ability !== 'Shed Skin' && (ability !== 'Hydration' || !hasMove['raindance'])) { item = 'Chesto Berry'; } else if (hasMove['naturalgift']) { item = 'Liechi Berry'; } else if (hasMove['geomancy']) { item = 'Power Herb'; } else if (ability === 'Harvest') { item = 'Sitrus Berry'; } else if (template.species === 'Cubone' || template.species === 'Marowak') { item = 'Thick Club'; } else if (template.baseSpecies === 'Pikachu') { item = 'Light Ball'; } else if (template.species === 'Clamperl') { item = 'DeepSeaTooth'; } else if (template.species === 'Farfetch\'d') { item = 'Stick'; } else if (template.species === 'Dedenne') { item = 'Petaya Berry'; } else if (template.species === 'Unfezant' && counter['Physical'] >= 2) { item = 'Scope Lens'; } else if (template.evos.length) { item = 'Eviolite'; } else if (hasMove['reflect'] && hasMove['lightscreen']) { item = 'Light Clay'; } else if (hasMove['shellsmash']) { item = 'White Herb'; } else if (hasMove['facade'] || ability === 'Poison Heal' || ability === 'Toxic Boost') { item = 'Toxic Orb'; } else if (hasMove['raindance']) { item = 'Damp Rock'; } else if (hasMove['sunnyday']) { item = 'Heat Rock'; } else if (hasMove['sandstorm']) { // lol item = 'Smooth Rock'; } else if (hasMove['hail']) { // lol item = 'Icy Rock'; } else if (ability === 'Magic Guard' && hasMove['psychoshift']) { item = 'Flame Orb'; } else if (ability === 'Sheer Force' || ability === 'Magic Guard') { item = 'Life Orb'; } else if (hasMove['acrobatics']) { item = 'Flying Gem'; } else if (ability === 'Unburden') { if (hasMove['fakeout']) { item = 'Normal Gem'; } else if (hasMove['dracometeor'] || hasMove['leafstorm'] || hasMove['overheat']) { item = 'White Herb'; } else if (hasMove['substitute'] || setupType) { item = 'Sitrus Berry'; } else { item = 'Red Card'; for (var m in moves) { var move = this.getMove(moves[m]); if (hasType[move.type] && move.basePower >= 90) { item = move.type + ' Gem'; break; } } } // medium priority } else if (ability === 'Guts') { item = hasMove['drainpunch'] ? 'Flame Orb' : 'Toxic Orb'; if ((hasMove['return'] || hasMove['hyperfang']) && !hasMove['facade']) { // lol no for (var j = 0; j < moves.length; j++) { if (moves[j] === 'return' || moves[j] === 'hyperfang') { moves[j] = 'facade'; break; } } } } else if (ability === 'Marvel Scale' && hasMove['psychoshift']) { item = 'Flame Orb'; } else if (hasMove['reflect'] || hasMove['lightscreen']) { // less priority than if you'd had both item = 'Light Clay'; } else if (counter.Physical >= 4 && !hasMove['fakeout'] && !hasMove['suckerpunch'] && !hasMove['flamecharge'] && !hasMove['rapidspin']) { item = template.baseStats.spe > 82 && template.baseStats.spe < 109 && !counter['priority'] && this.random(3) ? 'Choice Scarf' : 'Choice Band'; } else if (counter.Special >= 4 && !hasMove['chargebeam']) { item = template.baseStats.spe > 82 && template.baseStats.spe < 109 && !counter['priority'] && this.random(3) ? 'Choice Scarf' : 'Choice Specs'; } else if (hasMove['eruption'] || hasMove['waterspout']) { item = counter.Status <= 1 ? 'Expert Belt' : 'Leftovers'; } else if ((hasMove['flail'] || hasMove['reversal'] || hasMove['endeavor']) && !hasMove['endure'] && ability !== 'Sturdy') { item = 'Focus Sash'; } else if (this.getEffectiveness('Ground', template) >= 2 && !hasType['Poison'] && ability !== 'Levitate' && !hasMove['magnetrise']) { item = 'Air Balloon'; } else if (hasMove['substitute'] || hasMove['detect'] || hasMove['protect'] || hasMove['roar'] || hasMove['whirlwind'] || hasMove['sleeptalk'] || ability === 'Moody') { item = 'Leftovers'; } else if (ability === 'Iron Barbs' || ability === 'Rough Skin') { item = 'Rocky Helmet'; } else if (counter.Physical + counter.Special >= 4 && template.baseStats.def + template.baseStats.spd > 189) { item = 'Assault Vest'; } else if (counter.Physical + counter.Special >= 4) { item = (hasMove['fakeout'] || hasMove['return'] || hasMove['extremespeed'] || (hasMove['suckerpunch'] && !hasType['Dark'])) ? 'Life Orb' : 'Expert Belt'; } else if (hasMove['outrage'] && (setupType || ability === 'Multiscale')) { item = 'Lum Berry'; } else if (counter.Physical + counter.Special >= 3 && !!counter['speedsetup'] && template.baseStats.hp + template.baseStats.def + template.baseStats.spd > 300) { item = 'Weakness Policy'; } else if ((counter.Physical + counter.Special >= 3) && ability !== 'Sturdy' && !hasMove['dragontail']) { item = (setupType || !!counter['speedsetup'] || hasMove['trickroom'] || !!counter['recovery']) ? 'Life Orb' : 'Leftovers'; } else if (template.species === 'Palkia' && (hasMove['dracometeor'] || hasMove['spacialrend']) && hasMove['hydropump']) { item = 'Lustrous Orb'; } else if (slot === 0 && ability !== 'Sturdy' && ability !== 'Multiscale' && !counter['recoil'] && template.baseStats.def + template.baseStats.spd + template.baseStats.hp < 300) { item = 'Focus Sash'; // this is the "REALLY can't think of a good item" cutoff } else if (ability === 'Super Luck') { item = 'Scope Lens'; } else if (ability === 'Sturdy' && hasMove['explosion'] && !counter['speedsetup']) { item = 'Custap Berry'; } else if (hasType['Poison']) { item = 'Black Sludge'; } else if (this.getEffectiveness('Rock', template) >= 1 || hasMove['dragontail']) { item = 'Leftovers'; } else if (this.getImmunity('Ground', template) && this.getEffectiveness('Ground', template) >= 1 && ability !== 'Levitate' && ability !== 'Solid Rock' && !hasMove['magnetrise']) { item = 'Air Balloon'; } else if (counter.Status <= 1 && ability !== 'Sturdy') { item = 'Life Orb'; } else { item = 'Leftovers'; } // For Trick / Switcheroo if (item === 'Leftovers' && hasType['Poison']) { item = 'Black Sludge'; } var levelScale = { LC: 92, 'LC Uber': 92, NFE: 90, PU: 88, BL4: 88, NU: 86, BL3: 84, RU: 82, BL2: 80, UU: 78, BL: 76, OU: 74, CAP: 74, Unreleased: 74, Uber: 70, AG: 68 }; var customScale = { // Between OU and Uber Blaziken: 72, Genesect: 72, 'Genesect-Burn': 72, 'Genesect-Chill': 72, 'Genesect-Douse': 72, 'Genesect-Shock': 72, Greninja: 72, 'Kangaskhan-Mega': 72, 'Lucario-Mega': 72, 'Mawile-Mega': 72, // Not holding mega stone Altaria: 84, Banette: 86, Beedrill: 86, Charizard: 82, Gardevoir: 78, Heracross: 78, Lopunny: 86, Manectric: 78, Metagross: 78, Pinsir: 82, Sableye: 78, Venusaur: 78, // Holistic judgment Articuno: 82, Regigigas: 86, "Rotom-Fan": 88, Sigilyph: 80, Unown: 90 }; var level = levelScale[template.tier] || 90; if (customScale[template.name]) level = customScale[template.name]; if (template.name === 'Magikarp' && hasMove['magikarpsrevenge']) level = 90; if (template.name === 'Xerneas' && hasMove['geomancy']) level = 68; // Prepare HP for Belly Drum. if (hasMove['bellydrum'] && item === 'Sitrus Berry') { var hp = Math.floor(Math.floor(2 * template.baseStats.hp + ivs.hp + Math.floor(evs.hp / 4) + 100) * level / 100 + 10); if (hp % 2 > 0) { evs.hp -= 4; evs.atk += 4; } } else { // Prepare HP for double Stealth Rock weaknesses. Those are mutually exclusive with Belly Drum HP check. // First, 25% damage. if (this.getEffectiveness('Rock', template) === 1) { var hp = Math.floor(Math.floor(2 * template.baseStats.hp + ivs.hp + Math.floor(evs.hp / 4) + 100) * level / 100 + 10); if (hp % 4 === 0) { evs.hp -= 4; if (counter.Physical > counter.Special) { evs.atk += 4; } else { evs.spa += 4; } } } // Then, prepare it for 50% damage. if (this.getEffectiveness('Rock', template) === 2) { var hp = Math.floor(Math.floor(2 * template.baseStats.hp + ivs.hp + Math.floor(evs.hp / 4) + 100) * level / 100 + 10); if (hp % 2 === 0) { evs.hp -= 4; if (counter.Physical > counter.Special) { evs.atk += 4; } else { evs.spa += 4; } } } } return { name: name, moves: moves, ability: ability, evs: evs, ivs: ivs, item: item, level: level, shiny: !this.random(1024) }; }, randomTeam: function (side) { var pokemonLeft = 0; var pokemon = []; var excludedTiers = {'LC':1, 'LC Uber':1, 'NFE':1}; var allowedNFE = {'Chansey':1, 'Doublade':1, 'Pikachu':1, 'Porygon2':1, 'Scyther':1}; var pokemonPool = []; for (var id in this.data.FormatsData) { var template = this.getTemplate(id); if (!excludedTiers[template.tier] && !template.isMega && !template.isPrimal && !template.isNonstandard && template.randomBattleMoves) { pokemonPool.push(id); } } // PotD stuff var potd; if (Config.potd && 'Rule:potd' in this.getBanlistTable(this.getFormat())) { potd = this.getTemplate(Config.potd); } var typeCount = {}; var typeComboCount = {}; var baseFormes = {}; var uberCount = 0; var puCount = 0; var megaCount = 0; while (pokemonPool.length && pokemonLeft < 6) { var template = this.getTemplate(this.sampleNoReplace(pokemonPool)); if (!template.exists) continue; // Limit to one of each species (Species Clause) if (baseFormes[template.baseSpecies]) continue; // Not available on ORAS if (template.species === 'Pichu-Spiky-eared') continue; // Only certain NFE Pokemon are allowed if (template.evos.length && !allowedNFE[template.species]) continue; var tier = template.tier; switch (tier) { case 'PU': // PUs are limited to 2 but have a 20% chance of being added anyway. if (puCount > 1 && this.random(5) >= 1) continue; break; case 'Uber': // Ubers are limited to 2 but have a 20% chance of being added anyway. if (uberCount > 1 && this.random(5) >= 1) continue; break; case 'CAP': // CAPs have 20% the normal rate if (this.random(5) >= 1) continue; break; case 'Unreleased': // Unreleased Pokémon have 20% the normal rate if (this.random(5) >= 1) continue; } // Adjust rate for species with multiple formes switch (template.baseSpecies) { case 'Arceus': if (this.random(18) >= 1) continue; break; case 'Basculin': if (this.random(2) >= 1) continue; break; case 'Genesect': if (this.random(5) >= 1) continue; break; case 'Gourgeist': if (this.random(4) >= 1) continue; break; case 'Pikachu': // Cosplay Pikachu formes have 20% the normal rate (1/30 the normal rate each) if (template.species !== 'Pikachu' && this.random(30) >= 1) continue; } // Limit 2 of any type var types = template.types; var skip = false; for (var t = 0; t < types.length; t++) { if (typeCount[types[t]] > 1 && this.random(5) >= 1) { skip = true; break; } } if (skip) continue; if (potd && potd.exists) { // The Pokemon of the Day belongs in slot 2 if (pokemon.length === 1) { template = potd; if (template.species === 'Magikarp') { template.randomBattleMoves = ['magikarpsrevenge', 'splash', 'bounce']; } else if (template.species === 'Delibird') { template.randomBattleMoves = ['present', 'bestow']; } } else if (template.species === potd.species) { continue; // No, thanks, I've already got one } } var set = this.randomSet(template, pokemon.length, megaCount); // Illusion shouldn't be on the last pokemon of the team if (set.ability === 'Illusion' && pokemonLeft > 4) continue; // Limit 1 of any type combination var typeCombo = types.join(); if (set.ability === 'Drought' || set.ability === 'Drizzle') { // Drought and Drizzle don't count towards the type combo limit typeCombo = set.ability; } if (typeCombo in typeComboCount) continue; // Limit the number of Megas to one var forme = template.otherFormes && this.getTemplate(template.otherFormes[0]); var isMegaSet = this.getItem(set.item).megaStone || (forme && forme.isMega && forme.requiredMove && set.moves.indexOf(toId(forme.requiredMove)) >= 0); if (isMegaSet && megaCount > 0) continue; // Okay, the set passes, add it to our team pokemon.push(set); // Now that our Pokemon has passed all checks, we can increment our counters pokemonLeft++; // Increment type counters for (var t = 0; t < types.length; t++) { if (types[t] in typeCount) { typeCount[types[t]]++; } else { typeCount[types[t]] = 1; } } typeComboCount[typeCombo] = 1; // Increment Uber/NU counters if (tier === 'Uber') { uberCount++; } else if (tier === 'PU') { puCount++; } // Increment mega and base species counters if (isMegaSet) megaCount++; baseFormes[template.baseSpecies] = 1; } return pokemon; }, randomDoublesTeam: function (side) { var pokemonLeft = 0; var pokemon = []; var excludedTiers = {'LC':1, 'LC Uber':1, 'NFE':1}; var allowedNFE = {'Chansey':1, 'Doublade':1, 'Pikachu':1, 'Porygon2':1, 'Scyther':1}; var pokemonPool = []; for (var id in this.data.FormatsData) { var template = this.getTemplate(id); if (!excludedTiers[template.tier] && !template.isMega && !template.isPrimal && !template.isNonstandard && template.randomBattleMoves) { pokemonPool.push(id); } } // PotD stuff var potd; if (Config.potd && 'Rule:potd' in this.getBanlistTable(this.getFormat())) { potd = this.getTemplate(Config.potd); } var typeCount = {}; var typeComboCount = {}; var baseFormes = {}; var uberCount = 0; var puCount = 0; var megaCount = 0; while (pokemonPool.length && pokemonLeft < 6) { var template = this.getTemplate(this.sampleNoReplace(pokemonPool)); if (!template.exists) continue; // Limit to one of each species (Species Clause) if (baseFormes[template.baseSpecies]) continue; // Not available on ORAS if (template.species === 'Pichu-Spiky-eared') continue; // Only certain NFE Pokemon are allowed if (template.evos.length && !allowedNFE[template.species]) continue; var tier = template.tier; switch (tier) { case 'CAP': // CAPs have 20% the normal rate if (this.random(5) >= 1) continue; break; case 'Unreleased': // Unreleased Pokémon have 20% the normal rate if (this.random(5) >= 1) continue; } // Adjust rate for species with multiple formes switch (template.baseSpecies) { case 'Arceus': if (this.random(18) >= 1) continue; break; case 'Basculin': if (this.random(2) >= 1) continue; break; case 'Genesect': if (this.random(5) >= 1) continue; break; case 'Gourgeist': if (this.random(4) >= 1) continue; break; case 'Pikachu': // Cosplay Pikachu formes have 20% the normal rate (1/30 the normal rate each) if (template.species !== 'Pikachu' && this.random(30) >= 1) continue; } // Limit 2 of any type var types = template.types; var skip = false; for (var t = 0; t < types.length; t++) { if (typeCount[types[t]] > 1 && this.random(5) >= 1) { skip = true; break; } } if (skip) continue; if (potd && potd.exists) { // The Pokemon of the Day belongs in slot 3 if (pokemon.length === 2) { template = potd; } else if (template.species === potd.species) { continue; // No, thanks, I've already got one } } var set = this.randomDoublesSet(template, pokemon.length, megaCount); // Illusion shouldn't be on the last pokemon of the team if (set.ability === 'Illusion' && pokemonLeft > 4) continue; // Limit 1 of any type combination var typeCombo = types.join(); if (set.ability === 'Drought' || set.ability === 'Drizzle') { // Drought and Drizzle don't count towards the type combo limit typeCombo = set.ability; } if (typeCombo in typeComboCount) continue; // Limit the number of Megas to one var forme = template.otherFormes && this.getTemplate(template.otherFormes[0]); var isMegaSet = this.getItem(set.item).megaStone || (forme && forme.isMega && forme.requiredMove && set.moves.indexOf(toId(forme.requiredMove)) >= 0); if (isMegaSet && megaCount > 0) continue; // Okay, the set passes, add it to our team pokemon.push(set); // Now that our Pokemon has passed all checks, we can increment our counters pokemonLeft++; // Increment type counters for (var t = 0; t < types.length; t++) { if (types[t] in typeCount) { typeCount[types[t]]++; } else { typeCount[types[t]] = 1; } } typeComboCount[typeCombo] = 1; // Increment Uber/NU counters if (tier === 'Uber') { uberCount++; } else if (tier === 'PU') { puCount++; } // Increment mega and base species counters if (isMegaSet) megaCount++; baseFormes[template.baseSpecies] = 1; } return pokemon; }, randomDoublesSet: function (template, slot, noMega) { var baseTemplate = (template = this.getTemplate(template)); var name = template.name; if (!template.exists || (!template.randomDoubleBattleMoves && !template.randomBattleMoves && !template.learnset)) { template = this.getTemplate('unown'); var stack = 'Template incompatible with random battles: ' + name; var fakeErr = {stack: stack}; require('../crashlogger.js')(fakeErr, 'The doubles randbat set generator'); } // Decide if the Pokemon can mega evolve early, so viable moves for the mega can be generated if (!noMega && this.hasMegaEvo(template)) { // If there's more than one mega evolution, randomly pick one template = this.getTemplate(template.otherFormes[this.random(template.otherFormes.length)]); } if (template.otherFormes && this.getTemplate(template.otherFormes[0]).isPrimal && this.random(2)) { template = this.getTemplate(template.otherFormes[0]); } var movePool = (template.randomDoubleBattleMoves || template.randomBattleMoves); movePool = movePool ? movePool.slice() : Object.keys(template.learnset); var moves = []; var ability = ''; var item = ''; var evs = { hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0 }; 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 availableHP = 0; for (var i = 0, len = movePool.length; i < len; i++) { if (movePool[i].substr(0, 11) === 'hiddenpower') availableHP++; } // Moves which drop stats: var ContraryMove = { leafstorm: 1, overheat: 1, closecombat: 1, superpower: 1, vcreate: 1 }; // Moves that boost Attack: var PhysicalSetup = { swordsdance:1, dragondance:1, coil:1, bulkup:1, curse:1, bellydrum:1, shiftgear:1, honeclaws:1, howl:1 }; // Moves which boost Special Attack: var SpecialSetup = { nastyplot:1, tailglow:1, quiverdance:1, calmmind:1, chargebeam:1, geomancy:1 }; // Moves which boost Attack AND Special Attack: var MixedSetup = { growth:1, workup:1, shellsmash:1 }; // These moves can be used even if we aren't setting up to use them: var SetupException = { overheat:1, dracometeor:1, leafstorm:1, voltswitch:1, uturn:1, suckerpunch:1, extremespeed:1 }; var counterAbilities = { 'Blaze':1, 'Overgrow':1, 'Swarm':1, 'Torrent':1, 'Contrary':1, 'Technician':1, 'Skill Link':1, 'Iron Fist':1, 'Adaptability':1, 'Hustle':1 }; var damagingMoves, damagingMoveIndex, hasMove, counter, setupType, hasStab; do { // Keep track of all moves we have: hasMove = {}; for (var k = 0; k < moves.length; k++) { if (moves[k].substr(0, 11) === 'hiddenpower') { hasMove['hiddenpower'] = true; } else { hasMove[moves[k]] = true; } } // Choose next 4 moves from learnset/viable moves and add them to moves list: while (moves.length < 4 && movePool.length) { var moveid = toId(this.sampleNoReplace(movePool)); if (moveid.substr(0, 11) === 'hiddenpower') { availableHP--; if (hasMove['hiddenpower']) continue; hasMove['hiddenpower'] = true; } else { hasMove[moveid] = true; } moves.push(moveid); } damagingMoves = []; damagingMoveIndex = {}; hasStab = false; counter = { Physical: 0, Special: 0, Status: 0, damage: 0, technician: 0, skilllink: 0, contrary: 0, sheerforce: 0, ironfist: 0, adaptability: 0, hustle: 0, blaze: 0, overgrow: 0, swarm: 0, torrent: 0, serenegrace: 0, ate: 0, bite: 0, recoil: 0, inaccurate: 0, physicalsetup: 0, specialsetup: 0, mixedsetup: 0 }; setupType = ''; // Iterate through all moves we've chosen so far and keep track of what they do: for (var k = 0; k < moves.length; k++) { var move = this.getMove(moves[k]); var moveid = move.id; if (move.damage || move.damageCallback) { // Moves that do a set amount of damage: counter['damage']++; damagingMoves.push(move); damagingMoveIndex[moveid] = k; } else { // Are Physical/Special/Status moves: counter[move.category]++; } // Moves that have a low base power: if (moveid === 'lowkick' || (move.basePower && move.basePower <= 60)) counter['technician']++; // Moves that hit multiple times: if (move.multihit && move.multihit[1] === 5) counter['skilllink']++; // Recoil: if (move.recoil) counter['recoil']++; // Moves which have a base power and aren't super-weak like Rapid Spin: if (move.basePower > 20 || move.multihit || move.basePowerCallback) { if (hasType[move.type]) { counter['adaptability']++; // STAB: // Bounce, Flame Charge, Sky Drop aren't considered STABs. // If they're in the Pokémon's movepool and are STAB, consider the Pokémon not to have that type as a STAB. if (moveid !== 'bounce' && moveid !== 'flamecharge' && moveid !== 'skydrop') hasStab = true; } if (move.category === 'Physical') counter['hustle']++; if (move.type === 'Fire') counter['blaze']++; if (move.type === 'Grass') counter['overgrow']++; if (move.type === 'Bug') counter['swarm']++; if (move.type === 'Water') counter['torrent']++; if (move.type === 'Normal') counter['ate']++; if (move.flags['punch']) counter['ironfist']++; if (move.flags['bite']) counter['bite']++; damagingMoves.push(move); damagingMoveIndex[moveid] = k; } // Moves with secondary effects: if (move.secondary) { counter['sheerforce']++; if (move.secondary.chance >= 20) { counter['serenegrace']++; } } // Moves with low accuracy: if (move.accuracy && move.accuracy !== true && move.accuracy < 90) { counter['inaccurate']++; } if (ContraryMove[moveid]) counter['contrary']++; if (PhysicalSetup[moveid]) counter['physicalsetup']++; if (SpecialSetup[moveid]) counter['specialsetup']++; if (MixedSetup[moveid]) counter['mixedsetup']++; } // Choose a setup type: if (counter['mixedsetup']) { setupType = 'Mixed'; } else if (counter['specialsetup']) { setupType = 'Special'; } else if (counter['physicalsetup']) { setupType = 'Physical'; } // Iterate through the moves again, this time to cull them: for (var k = 0; k < moves.length; k++) { var moveid = moves[k]; var move = this.getMove(moveid); var rejected = false; var isSetup = false; switch (moveid) { // not very useful without their supporting moves case 'sleeptalk': if (!hasMove['rest']) rejected = true; break; case 'endure': if (!hasMove['flail'] && !hasMove['endeavor'] && !hasMove['reversal']) rejected = true; break; case 'focuspunch': if (hasMove['sleeptalk'] || !hasMove['substitute']) rejected = true; break; case 'storedpower': if (!hasMove['cosmicpower'] && !setupType) rejected = true; break; case 'batonpass': if (!setupType && !hasMove['substitute'] && !hasMove['cosmicpower']) rejected = true; break; // we only need to set up once case 'swordsdance': case 'dragondance': case 'coil': case 'curse': case 'bulkup': case 'bellydrum': if (counter.Physical < 2 && !hasMove['batonpass']) rejected = true; if (setupType !== 'Physical' || counter['physicalsetup'] > 1) rejected = true; isSetup = true; break; case 'nastyplot': case 'tailglow': case 'quiverdance': case 'calmmind': case 'geomancy': if (counter.Special < 2 && !hasMove['batonpass']) rejected = true; if (setupType !== 'Special' || counter['specialsetup'] > 1) rejected = true; isSetup = true; break; case 'shellsmash': case 'growth': case 'workup': if (counter.Physical + counter.Special < 2 && !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 'rapidspin': case 'perishsong': case 'magiccoat': case 'spikes': if (setupType) rejected = true; break; case 'uturn': case 'voltswitch': if (setupType || hasMove['agility'] || hasMove['rockpolish'] || hasMove['magnetrise']) rejected = true; break; case 'relicsong': if (setupType) rejected = true; break; case 'pursuit': case 'protect': case 'haze': case 'stealthrock': if (setupType || (hasMove['rest'] && hasMove['sleeptalk'])) rejected = true; break; case 'trick': case 'switcheroo': if (setupType || (hasMove['rest'] && hasMove['sleeptalk']) || hasMove['trickroom'] || hasMove['reflect'] || hasMove['lightscreen'] || hasMove['batonpass'] || template.isMega) rejected = true; break; case 'dragontail': case 'circlethrow': if (hasMove['agility'] || hasMove['rockpolish']) rejected = true; if (hasMove['whirlwind'] || hasMove['roar'] || hasMove['encore']) rejected = true; break; // bit redundant to have both // Attacks: case 'flamethrower': case 'fierydance': if (hasMove['heatwave'] || hasMove['overheat'] || hasMove['fireblast'] || hasMove['blueflare']) rejected = true; break; case 'overheat': if (setupType === 'Special' || hasMove['fireblast']) rejected = true; break; case 'icebeam': if (hasMove['blizzard']) rejected = true; break; case 'surf': if (hasMove['scald'] || hasMove['hydropump'] || hasMove['muddywater']) rejected = true; break; case 'hydropump': if (hasMove['razorshell'] || hasMove['scald'] || hasMove['muddywater']) rejected = true; break; case 'waterfall': if (hasMove['aquatail']) rejected = true; break; case 'airslash': if (hasMove['hurricane']) rejected = true; break; case 'acrobatics': case 'pluck': case 'drillpeck': if (hasMove['bravebird']) rejected = true; break; case 'solarbeam': if ((!hasMove['sunnyday'] && template.species !== 'Ninetales') || hasMove['gigadrain'] || hasMove['leafstorm']) rejected = true; break; case 'gigadrain': if ((!setupType && hasMove['leafstorm']) || hasMove['petaldance']) rejected = true; break; case 'leafstorm': if (setupType && hasMove['gigadrain']) rejected = true; break; case 'weatherball': if (!hasMove['sunnyday']) rejected = true; break; case 'firepunch': if (hasMove['flareblitz']) rejected = true; break; case 'crosschop': case 'highjumpkick': if (hasMove['closecombat']) rejected = true; break; case 'drainpunch': if (hasMove['closecombat'] || hasMove['crosschop']) rejected = true; break; case 'thunder': if (hasMove['thunderbolt']) rejected = true; break; case 'thunderbolt': case 'electroweb': if (hasMove['discharge']) rejected = true; break; case 'stoneedge': if (hasMove['rockslide'] || hasMove['headsmash'] || hasMove['rockblast']) rejected = true; break; case 'headsmash': if (hasMove['rockslide']) rejected = true; break; case 'bonemerang': case 'earthpower': if (hasMove['earthquake']) rejected = true; break; case 'outrage': if (hasMove['dragonclaw'] || hasMove['dragontail']) rejected = true; break; case 'ancientpower': if (hasMove['paleowave']) rejected = true; break; case 'dragonpulse': if (hasMove['dracometeor']) rejected = true; break; case 'moonblast': if (hasMove['dazzlinggleam']) rejected = true; break; case 'acidspray': if (hasMove['sludgebomb']) rejected = true; break; case 'return': if (hasMove['bodyslam'] || hasMove['facade'] || hasMove['doubleedge'] || hasMove['tailslap'] || hasMove['doublehit']) rejected = true; break; case 'poisonjab': if (hasMove['gunkshot']) rejected = true; break; case 'psychic': if (hasMove['psyshock']) rejected = true; break; case 'fusionbolt': if (setupType && hasMove['boltstrike']) rejected = true; break; case 'boltstrike': if (!setupType && hasMove['fusionbolt']) rejected = true; break; case 'hiddenpowerice': if (hasMove['icywind']) rejected = true; break; case 'quickattack': if (hasMove['feint']) rejected = true; break; case 'wideguard': if (hasMove['protect']) rejected = true; break; case 'powersplit': if (hasMove['guardsplit']) rejected = true; break; // Status: case 'rest': if (hasMove['painsplit'] || hasMove['wish'] || hasMove['recover'] || hasMove['moonlight'] || hasMove['synthesis']) rejected = true; break; case 'softboiled': case 'roost': if (hasMove['wish'] || hasMove['recover']) rejected = true; break; case 'perishsong': if (hasMove['roar'] || hasMove['whirlwind'] || hasMove['haze']) rejected = true; break; case 'roar': // Whirlwind outclasses Roar because Soundproof if (hasMove['whirlwind'] || hasMove['dragontail'] || hasMove['haze'] || hasMove['circlethrow']) rejected = true; break; case 'substitute': if (hasMove['uturn'] || hasMove['voltswitch'] || hasMove['pursuit']) rejected = true; break; case 'fakeout': if (hasMove['trick'] || hasMove['switcheroo'] || ability === 'Sheer Force') rejected = true; break; case 'encore': if (hasMove['rest'] && hasMove['sleeptalk']) rejected = true; if (hasMove['whirlwind'] || hasMove['dragontail'] || hasMove['roar'] || hasMove['circlethrow']) rejected = true; break; case 'suckerpunch': if (hasMove['rest'] && hasMove['sleeptalk']) rejected = true; break; case 'cottonguard': if (hasMove['reflect']) rejected = true; break; case 'lightscreen': if (hasMove['calmmind']) rejected = true; break; case 'rockpolish': case 'agility': case 'autotomize': if (!setupType && !hasMove['batonpass'] && hasMove['thunderwave']) rejected = true; if ((hasMove['stealthrock'] || hasMove['spikes'] || hasMove['toxicspikes']) && !hasMove['batonpass']) rejected = true; break; case 'thunderwave': if (setupType && (hasMove['rockpolish'] || hasMove['agility'])) rejected = true; if (hasMove['discharge'] || hasMove['trickroom']) rejected = true; if (hasMove['rest'] && hasMove['sleeptalk']) rejected = true; if (hasMove['yawn'] || hasMove['spore'] || hasMove['sleeppowder']) rejected = true; break; case 'lavaplume': if (hasMove['willowisp']) rejected = true; break; case 'trickroom': if (hasMove['rockpolish'] || hasMove['agility']) rejected = true; break; case 'willowisp': if (hasMove['scald'] || hasMove['yawn'] || hasMove['spore'] || hasMove['sleeppowder']) rejected = true; break; case 'toxic': if (hasMove['thunderwave'] || hasMove['willowisp'] || hasMove['scald'] || hasMove['yawn'] || hasMove['spore'] || hasMove['sleeppowder']) rejected = true; break; } // Increased/decreased priority moves unneeded with moves that boost only speed if (move.priority !== 0 && (hasMove['rockpolish'] || hasMove['agility'])) { rejected = true; } if (move.category === 'Special' && setupType === 'Physical' && !SetupException[move.id]) { rejected = true; } if (move.category === 'Physical' && (setupType === 'Special' || hasMove['acidspray']) && !SetupException[move.id]) { rejected = true; } // This move doesn't satisfy our setup requirements: if (setupType === 'Physical' && move.category !== 'Physical' && counter['Physical'] < 2) { rejected = true; } if (setupType === 'Special' && move.category !== 'Special' && counter['Special'] < 2) { rejected = true; } // Hidden Power isn't good enough if (setupType === 'Special' && move.id === 'hiddenpower' && counter['Special'] <= 2 && (!hasMove['shadowball'] || move.type !== 'Fighting')) { rejected = true; } // Remove rejected moves from the move list. if (rejected && (movePool.length - availableHP || availableHP && (move.id === 'hiddenpower' || !hasMove['hiddenpower']))) { moves.splice(k, 1); break; } // Handle HP IVs if (move.id === 'hiddenpower') { var HPivs = this.getType(move.type).HPivs; for (var iv in HPivs) { ivs[iv] = HPivs[iv]; } } } if (movePool.length && moves.length === 4 && !hasMove['judgment']) { // Move post-processing: if (damagingMoves.length === 0) { // A set shouldn't have no attacking moves moves.splice(this.random(moves.length), 1); } else if (damagingMoves.length === 1) { var damagingid = damagingMoves[0].id; // Night Shade, Seismic Toss, etc. don't count: if (!damagingMoves[0].damage && (movePool.length - availableHP || availableHP && (damagingid === 'hiddenpower' || !hasMove['hiddenpower']))) { var replace = false; if (damagingid === 'suckerpunch' || damagingid === 'counter' || damagingid === 'mirrorcoat') { // A player shouldn't be forced to rely upon the opponent attacking them to do damage. if (!hasMove['encore'] && this.random(2)) replace = true; } else if (damagingid === 'focuspunch') { // Focus Punch is a bad idea without a sub: if (!hasMove['substitute']) replace = true; } else if (damagingid === 'hiddenpower' && !hasStab) { // Hidden Power is only acceptable if it has STAB replace = true; } else { // If you have one attack, and it's not STAB, Ice, Fire, or Ground, reject it. // Mono-Ice/Ground/Fire is only acceptable if the Pokémon's STABs are one of: Poison, Normal, Grass. if (!hasStab) { var damagingType = damagingMoves[0].type; if (damagingType === 'Ice' || damagingType === 'Fire' || damagingType === 'Ground') { if (!hasType['Poison'] && !hasType['Normal'] && !hasType['Grass']) { replace = true; } } else { replace = true; } } } if (replace) moves.splice(damagingMoveIndex[damagingid], 1); } } else if (damagingMoves.length === 2) { // If you have two attacks, neither is STAB, and the combo isn't Ice/Electric or Ghost/Fighting, reject one of them at random. var type1 = damagingMoves[0].type, type2 = damagingMoves[1].type; var typeCombo = [type1, type2].sort().join('/'); if (!hasStab && typeCombo !== 'Electric/Ice' && typeCombo !== 'Fighting/Ghost') { var rejectableMoves = []; var baseDiff = movePool.length - availableHP; if (baseDiff || availableHP && (!hasMove['hiddenpower'] || damagingMoves[0].id === 'hiddenpower')) { rejectableMoves.push(damagingMoveIndex[damagingMoves[0].id]); } if (baseDiff || availableHP && (!hasMove['hiddenpower'] || damagingMoves[1].id === 'hiddenpower')) { rejectableMoves.push(damagingMoveIndex[damagingMoves[1].id]); } if (rejectableMoves.length) { moves.splice(rejectableMoves[this.random(rejectableMoves.length)], 1); } } } else if (!hasStab) { // If you have three or more attacks, and none of them are STAB, reject one of them at random. var rejectableMoves = []; var baseDiff = movePool.length - availableHP; for (var l = 0; l < damagingMoves.length; l++) { if (baseDiff || availableHP && (!hasMove['hiddenpower'] || damagingMoves[l].id === 'hiddenpower')) { rejectableMoves.push(damagingMoveIndex[damagingMoves[l].id]); } } if (rejectableMoves.length) { moves.splice(rejectableMoves[this.random(rejectableMoves.length)], 1); } } } } while (moves.length < 4 && movePool.length); // If Hidden Power has been removed, reset the IVs if (!hasMove['hiddenpower']) { ivs = { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 }; } var abilities = Object.values(baseTemplate.abilities).sort(function (a, b) { return this.getAbility(b).rating - this.getAbility(a).rating; }.bind(this)); var ability0 = this.getAbility(abilities[0]); var ability1 = this.getAbility(abilities[1]); var ability = ability0.name; if (abilities[1]) { if (ability0.rating <= ability1.rating) { if (this.random(2)) ability = ability1.name; } else if (ability0.rating - 0.6 <= ability1.rating) { if (!this.random(3)) ability = ability1.name; } var rejectAbility = false; if (ability in counterAbilities) { rejectAbility = !counter[toId(ability)]; } else if (ability === 'Rock Head' || ability === 'Reckless') { rejectAbility = !counter['recoil']; } else if (ability === 'No Guard' || ability === 'Compound Eyes') { rejectAbility = !counter['inaccurate']; } else if (ability === 'Strong Jaw') { rejectAbility = !counter['bite']; } else if (ability === 'Sheer Force') { rejectAbility = !counter['sheerforce']; } else if (ability === 'Serene Grace') { rejectAbility = !counter['serenegrace'] && template.species !== 'Blissey' && template.species !== 'Chansey'; } else if (ability === 'Simple') { rejectAbility = !setupType && !hasMove['flamecharge'] && !hasMove['stockpile']; } else if (ability === 'Prankster') { rejectAbility = !counter['Status']; } else if (ability === 'Defiant') { rejectAbility = !counter['Physical'] && !hasMove['batonpass']; } else if (ability === 'Moody') { rejectAbility = template.id !== 'bidoof'; } else if (ability === 'Lightning Rod') { rejectAbility = template.types.indexOf('Ground') >= 0; } else if (ability === 'Chlorophyll') { rejectAbility = !hasMove['sunnyday']; } else if (ability === 'Aerilate' || ability === 'Pixilate' || ability === 'Refrigerate') { rejectAbility = !counter['ate'] && !hasMove['naturepower']; } if (rejectAbility) { if (ability === ability1.name) { // or not ability = ability0.name; } else if (ability1.rating > 0) { // only switch if the alternative doesn't suck ability = ability1.name; } } if (abilities.indexOf('Guts') > -1 && ability !== 'Quick Feet' && hasMove['facade']) { ability = 'Guts'; } if (abilities.indexOf('Swift Swim') > -1 && hasMove['raindance']) { ability = 'Swift Swim'; } if (abilities.indexOf('Chlorophyll') > -1 && ability !== 'Solar Power') { ability = 'Chlorophyll'; } if (abilities.indexOf('Intimidate') > -1 || template.id === 'mawilemega') { ability = 'Intimidate'; } if (template.id === 'unfezant') { ability = 'Super Luck'; } } // Make EVs comply with the sets. // Quite simple right now, 252 attack, 252 hp if slow 252 speed if fast, 4 evs for the strong defense. // TO-DO: Make this more complex if (counter.Special >= 2) { evs.atk = 0; evs.spa = 252; } else if (counter.Physical >= 2) { evs.atk = 252; evs.spa = 0; } else { // Fallback in case a Pokémon lacks attacks... go by stats if (template.baseStats.spa >= template.baseStats.atk) { evs.atk = 0; evs.spa = 252; } else { evs.atk = 252; evs.spa = 0; } } if (template.baseStats.spe > 80) { evs.spe = 252; evs.hp = 4; } else { evs.hp = 252; if (template.baseStats.def > template.baseStats.spd) { evs.def = 4; } else { evs.spd = 4; } } // Naturally slow mons already have the proper EVs, check IVs for Gyro Ball and TR if (hasMove['gyroball'] || hasMove['trickroom']) { ivs.spe = 0; } item = 'Sitrus Berry'; if (template.requiredItem) { item = template.requiredItem; // First, the extra high-priority items } else if (ability === 'Imposter') { item = 'Choice Scarf'; } else if (hasMove["magikarpsrevenge"]) { item = 'Mystic Water'; } else if (ability === 'Wonder Guard') { item = 'Focus Sash'; } else if (template.species === 'Unown') { item = 'Choice Specs'; } else if (hasMove['trick'] && hasMove['gyroball'] && (ability === 'Levitate' || hasType['Flying'])) { item = 'Macho Brace'; } else if (hasMove['trick'] && hasMove['gyroball']) { item = 'Iron Ball'; } else if (hasMove['trick'] || hasMove['switcheroo']) { var randomNum = this.random(2); if (counter.Physical >= 3 && (template.baseStats.spe >= 95 || randomNum)) { item = 'Choice Band'; } else if (counter.Special >= 3 && (template.baseStats.spe >= 95 || randomNum)) { item = 'Choice Specs'; } else { item = 'Choice Scarf'; } } else if (hasMove['rest'] && !hasMove['sleeptalk'] && ability !== 'Natural Cure' && ability !== 'Shed Skin') { item = 'Chesto Berry'; } else if (hasMove['naturalgift']) { item = 'Liechi Berry'; } else if (hasMove['geomancy']) { item = 'Power Herb'; } else if (ability === 'Harvest') { item = 'Sitrus Berry'; } else if (template.species === 'Cubone' || template.species === 'Marowak') { item = 'Thick Club'; } else if (template.baseSpecies === 'Pikachu') { item = 'Light Ball'; } else if (template.species === 'Clamperl') { item = 'DeepSeaTooth'; } else if (template.species === 'Spiritomb') { item = 'Leftovers'; } else if (template.species === 'Scrafty' && counter['Status'] === 0) { item = 'Assault Vest'; } else if (template.species === 'Farfetch\'d') { item = 'Stick'; } else if (template.species === 'Amoonguss') { item = 'Black Sludge'; } else if (template.species === 'Dedenne') { item = 'Petaya Berry'; } else if (hasMove['focusenergy'] || (template.species === 'Unfezant' && counter['Physical'] >= 2)) { item = 'Scope Lens'; } else if (template.evos.length) { item = 'Eviolite'; } else if (hasMove['reflect'] && hasMove['lightscreen']) { item = 'Light Clay'; } else if (hasMove['shellsmash']) { item = 'White Herb'; } else if (hasMove['facade'] || ability === 'Poison Heal' || ability === 'Toxic Boost') { item = 'Toxic Orb'; } else if (hasMove['raindance']) { item = 'Damp Rock'; } else if (hasMove['sunnyday']) { item = 'Heat Rock'; } else if (hasMove['sandstorm']) { item = 'Smooth Rock'; } else if (hasMove['hail']) { item = 'Icy Rock'; } else if (ability === 'Magic Guard' && hasMove['psychoshift']) { item = 'Flame Orb'; } else if (ability === 'Sheer Force' || ability === 'Magic Guard') { item = 'Life Orb'; } else if (hasMove['acrobatics']) { item = 'Flying Gem'; } else if (ability === 'Unburden') { item = 'Red Card'; // Give Unburden mons a Normal Gem if they have Fake Out, or White Herb if they have a move that lowers stats if (hasMove['fakeout']) { item = 'Normal Gem'; } else if (hasMove['leafstorm'] || hasMove['dracometeor'] || hasMove['overheat'] || hasMove['superpower']) { item = 'White Herb'; } // medium priority } else if (ability === 'Guts') { item = hasMove['drainpunch'] ? 'Flame Orb' : 'Toxic Orb'; if ((hasMove['return'] || hasMove['hyperfang']) && !hasMove['facade']) { // lol no for (var j = 0; j < moves.length; j++) { if (moves[j] === 'Return' || moves[j] === 'Hyper Fang') { moves[j] = 'Facade'; break; } } } } else if (ability === 'Marvel Scale' && hasMove['psychoshift']) { item = 'Flame Orb'; } else if (counter.Physical >= 4 && !hasMove['fakeout'] && !hasMove['suckerpunch'] && !hasMove['flamecharge'] && !hasMove['rapidspin'] && ability !== 'Sturdy' && ability !== 'Multiscale') { item = 'Life Orb'; } else if (counter.Special >= 4 && !hasMove['eruption'] && !hasMove['waterspout'] && ability !== 'Sturdy') { item = 'Life Orb'; } else if (this.getImmunity('Ground', template) && this.getEffectiveness('Ground', template) >= 2 && ability !== 'Levitate' && !hasMove['magnetrise']) { item = 'Shuca Berry'; } else if (this.getEffectiveness('Ice', template) >= 2) { item = 'Yache Berry'; } else if (this.getEffectiveness('Rock', template) >= 2) { item = 'Charti Berry'; } else if (this.getEffectiveness('Fire', template) >= 2) { item = 'Occa Berry'; } else if (this.getImmunity('Fighting', template) && this.getEffectiveness('Fighting', template) >= 2) { item = 'Chople Berry'; } else if (ability === 'Iron Barbs' || ability === 'Rough Skin') { item = 'Rocky Helmet'; } 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 = 'Sitrus Berry'; } else if (counter.Physical + counter.Special >= 3 && setupType && ability !== 'Sturdy' && ability !== 'Multiscale') { item = 'Life Orb'; } else if (counter.Special >= 3 && setupType && ability !== 'Sturdy') { item = 'Life Orb'; } else if (counter.Physical + counter.Special >= 4 && template.baseStats.def + template.baseStats.spd > 179) { item = 'Assault Vest'; } else if (counter.Physical + counter.Special >= 4) { item = 'Expert Belt'; } else if (hasMove['outrage']) { item = 'Lum Berry'; } else if (hasMove['substitute'] || hasMove['detect'] || hasMove['protect'] || ability === 'Moody') { item = 'Leftovers'; } else if (this.getImmunity('Ground', template) && this.getEffectiveness('Ground', template) >= 1 && ability !== 'Levitate' && !hasMove['magnetrise']) { item = 'Shuca Berry'; } else if (this.getEffectiveness('Ice', template) >= 1) { item = 'Yache Berry'; // this is the "REALLY can't think of a good item" cutoff } else if (counter.Physical + counter.Special >= 2 && template.baseStats.hp + template.baseStats.def + template.baseStats.spd > 315) { item = 'Weakness Policy'; } else if (ability === 'Sturdy' && hasMove['explosion'] && !counter['speedsetup']) { item = 'Custap Berry'; } else if (ability === 'Super Luck') { item = 'Scope Lens'; } else if (hasType['Poison']) { item = 'Black Sludge'; } else if (counter.Status <= 1 && ability !== 'Sturdy' && ability !== 'Multiscale') { item = 'Life Orb'; } else { item = 'Sitrus Berry'; } // For Trick / Switcheroo if (item === 'Leftovers' && hasType['Poison']) { item = 'Black Sludge'; } // We choose level based on BST. Min level is 70, max level is 99. 600+ BST is 70, less than 300 is 99. Calculate with those values. // Every 10.35 BST adds a level from 70 up to 99. Results are floored. Uses the Mega's stats if holding a Mega Stone // To-do: adjust levels of mons with boosting items (Light Ball, Thick Club etc) var bst = template.baseStats.hp + template.baseStats.atk + template.baseStats.def + template.baseStats.spa + template.baseStats.spd + template.baseStats.spe; var level = 70 + Math.floor(((600 - this.clampIntRange(bst, 300, 600)) / 10.35)); return { name: name, moves: moves, ability: ability, evs: evs, ivs: ivs, item: item, level: level, shiny: !this.random(template.id === 'missingno' ? 4 : 1024) }; }, randomSeasonalMulanTeam: function (side) { var armySide = 'china'; var team = []; var pokemon = ''; var template = {}; var set = {}; var megaCount = 0; var pokemonLeft = 0; // If the other team has been chosen, we get its opposing force. if (this.seasonal && this.seasonal.side) { armySide = (this.seasonal.side === 'hun' ? 'china' : 'hun'); } else { // First team being generated, pick a armySide at random. armySide = (Math.random() > 0.5 ? 'china' : 'hun'); this.seasonal = {'side': armySide}; } if (armySide === 'china') { var chinese = [ 'accelgor', 'bisharp', 'gallade', 'hitmonchan', 'hitmonlee', 'hitmontop', 'infernape', 'lucario', 'machoke', 'medicham', 'medicham', 'mienshao', 'pangoro', 'sawk', 'scrafty', 'scizor', 'throh', 'ursaring', 'vigoroth', 'weavile', 'zangoose' ].randomize(); var weakCount = {}; // Add the members of the army. var names = ["Li Shang", "Mulan", "Yao", "Ling"]; for (var i = 0; i < chinese.length && pokemonLeft < 4; i++) { var mainWeakness = {}; pokemon = chinese[i]; template = this.getTemplate(pokemon); // Li Shang shouldn't be an NFE Pokemon. if (names[pokemonLeft] === "Li Shang" && template.evos.length) continue; // We don't want too many Fighting or Flying weaknesses, since those moves will be common // Hard limit it to two, since after factoring in Chien-Po we might have a lot of common weakness var mainWeakness = {}; if (Tools.getEffectiveness('Flying', template) > 0) mainWeakness['Flying'] = true; if (Tools.getEffectiveness('Fighting', template) > 0) mainWeakness['Fighting'] = true; if (mainWeakness['Fighting'] && weakCount['Fighting'] >= 2) continue; else if (mainWeakness['Flying'] && weakCount['Flying'] >= 2) continue; else { for (var type in mainWeakness) { if (type in weakCount) { weakCount[type]++; } else { weakCount[type] = 1; } } } set = this.randomSet(template, pokemonLeft, !!megaCount); if (this.getItem(set.item).megaStone) megaCount++; set.species = toId(set.name); set.name = names[pokemonLeft]; set.gender = (set.name === "Mulan" ? 'F' : 'M'); set.moves[4] = 'searingshot'; if (set.name === "Li Shang") { set.moves[5] = 'sing'; } // Baton Pass is not allowed. var batonpass = set.moves.indexOf('batonpass'); if (batonpass >= 0) { set.moves[batonpass] = 'secretpower'; } team.push(set); pokemonLeft++; } // Chien Po is very large, so he samples from a different pool of Pokemon pokemon = ['blastoise', 'snorlax', 'golem', 'lickilicky', 'poliwrath', 'hariyama', 'magmortar'][this.random(7)]; template = this.getTemplate(pokemon); set = this.randomSet(template, 4, !!megaCount); if (this.getItem(set.item).megaStone) megaCount++; set.species = toId(set.name); set.name = "Chien-Po"; set.gender = 'M'; set.moves[4] = 'searingshot'; team.push(set); // Add Eddie Murphy-- I mean, Mushu, to the team as a Dragonair. template = this.getTemplate('dragonair'); template.randomBattleMoves = ['dragondance', 'aquatail', 'waterfall', 'wildcharge', 'extremespeed', 'dracometeor', 'dragonascent']; set = this.randomSet(template, 5); set.species = toId(set.name); set.name = "Mushu"; set.gender = 'M'; set.ability = "Turboblaze"; set.moves[4] = 'sacredfire'; set.moves[5] = 'dragonrush'; team.push(set); } else { var huns = [ 'aggron', 'chesnaught', 'conkeldurr', 'drapion', 'electivire', 'emboar', 'exploud', 'feraligatr', 'granbull', 'haxorus', 'machamp', 'nidoking', 'rhyperior', 'swampert', 'tyranitar' ].randomize(); for (var i = 0; i < huns.length && pokemonLeft < 5; i++) { pokemon = huns[pokemonLeft]; template = this.getTemplate(pokemon); set = this.randomSet(template, pokemonLeft, !!megaCount); if (this.getItem(set.item).megaStone) megaCount++; set.species = toId(set.name); if (i === 0) { set.name = "Shan Yu"; } else { set.name = "Hun " + template.species; } set.gender = 'M'; team.push(set); pokemonLeft++; } // Add Hayabusa the falcon. pokemon = ['fearow', 'pidgeot', 'staraptor', 'honchkrow', 'aerodactyl', 'archeops', 'braviary', 'noivern'][this.random(8)]; template = this.getTemplate(pokemon); set = this.randomSet(template, 5, !!megaCount); set.species = toId(set.name); set.name = "Hayabusa"; team.push(set); } return team; } };