'use strict'; const PRNG = require('./../sim/prng'); const CHOOSABLE_TARGETS = new Set(['normal', 'any', 'adjacentAlly', 'adjacentAllyOrSelf', 'adjacentFoe']); exports.BattleScripts = { gen: 7, /** * runMove is the "outside" move caller. It handles deducting PP, * flinching, full paralysis, etc. All the stuff up to and including * the "POKEMON used MOVE" message. * * For details of the difference between runMove and useMove, see * useMove's info. * * externalMove skips LockMove and PP deduction, mostly for use by * Dancer. */ runMove: function (move, pokemon, targetLoc, sourceEffect, zMove, externalMove) { let target = this.getTarget(pokemon, zMove || move, targetLoc); if (!sourceEffect && toId(move) !== 'struggle' && !zMove) { let changedMove = this.runEvent('OverrideDecision', pokemon, target, move); if (changedMove && changedMove !== true) { move = changedMove; target = null; } } let baseMove = this.getMove(move); move = zMove ? this.getZMoveCopy(move, pokemon) : baseMove; if (!target && target !== false) target = this.resolveTarget(pokemon, move); // copy the priority for Quick Guard if (zMove) move.priority = baseMove.priority; move.isExternal = externalMove; 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)) { this.runEvent('MoveAborted', pokemon, target, move); // Prevent Pursuit from running again against a slower U-turn/Volt Switch/Parting Shot pokemon.moveThisTurn = true; this.clearActiveMove(true); return; } if (move.beforeMoveCallback) { if (move.beforeMoveCallback.call(this, pokemon, target, move)) { this.clearActiveMove(true); return; } } pokemon.lastDamage = 0; let lockedMove; if (!externalMove) { lockedMove = this.runEvent('LockMove', pokemon); if (lockedMove === true) lockedMove = false; if (!lockedMove) { if (!pokemon.deductPP(baseMove, null, target) && (move.id !== 'struggle')) { this.add('cant', pokemon, 'nopp', move); let gameConsole = [null, 'Game Boy', 'Game Boy', 'Game Boy Advance', 'DS', 'DS'][this.gen] || '3DS'; this.add('-hint', "This is not a bug, this is really how it works on the " + gameConsole + "; try it yourself if you don't believe us."); this.clearActiveMove(true); return; } } else { sourceEffect = this.getEffect('lockedmove'); } pokemon.moveUsed(move, targetLoc); } // Dancer Petal Dance hack // TODO: implement properly let noLock = externalMove && !pokemon.volatiles.lockedmove; if (zMove) { if (pokemon.illusion) { this.singleEvent('End', this.getAbility('Illusion'), pokemon.abilityData, pokemon); } this.add('-zpower', pokemon); pokemon.side.zMoveUsed = true; } this.useMove(baseMove, pokemon, target, sourceEffect, zMove); this.singleEvent('AfterMove', move, null, pokemon, target, move); this.runEvent('AfterMove', pokemon, target, move); if (noLock && pokemon.volatiles.lockedmove) delete pokemon.volatiles.lockedmove; }, /** * useMove is the "inside" move caller. It handles effects of the * move itself, but not the idea of using the move. * * Most caller effects, like Sleep Talk, Nature Power, Magic Bounce, * etc use useMove. * * The only ones that use runMove are Instruct, Pursuit, and * Dancer. */ useMove: function (move, pokemon, target, sourceEffect, zMove) { if (!sourceEffect && this.effect.id) sourceEffect = this.effect; if (zMove && move.id === 'weatherball') { let baseMove = move; this.singleEvent('ModifyMove', move, null, pokemon, target, move, move); move = this.getZMoveCopy(move, pokemon); if (move.type !== 'Normal') sourceEffect = baseMove; } else if (zMove || (sourceEffect && sourceEffect.isZ)) { move = this.getZMoveCopy(move, pokemon); } else { move = this.getMoveCopy(move); } if (this.activeMove) { move.priority = this.activeMove.priority; move.pranksterBoosted = move.hasBounced ? false : this.activeMove.pranksterBoosted; } let 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; let moveResult = false; 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; let attrs = ''; if (pokemon.fainted) { return false; } if (move.flags['charge'] && !pokemon.volatiles[move.id]) { attrs = '|[still]'; // suppress the default move animation } let movename = move.name; if (move.id === 'hiddenpower') movename = 'Hidden Power'; if (sourceEffect) attrs += '|[from]' + this.getEffect(sourceEffect); if (zMove && move.isZ === true) { attrs = '|[anim]' + movename + attrs; movename = 'Z-' + movename; } this.addMove('move', pokemon, movename, target + attrs); if (zMove && move.category !== 'Status') { this.attrLastMove('[zeffect]'); } else if (zMove && move.zMoveBoost) { this.boost(move.zMoveBoost, pokemon, pokemon, {id: 'zpower'}); } else if (zMove && move.zMoveEffect === 'heal') { this.heal(pokemon.maxhp, pokemon, pokemon, {id: 'zpower'}); } else if (zMove && move.zMoveEffect === 'healreplacement') { move.self = {sideCondition: 'healreplacement'}; } else if (zMove && move.zMoveEffect === 'clearnegativeboost') { let boosts = {}; for (let i in pokemon.boosts) { if (pokemon.boosts[i] < 0) { boosts[i] = 0; } } pokemon.setBoost(boosts); this.add('-clearnegativeboost', pokemon, '[zeffect]'); } else if (zMove && move.zMoveEffect === 'redirect') { pokemon.addVolatile('followme', pokemon, {id: 'zpower'}); } else if (zMove && move.zMoveEffect === 'crit2') { pokemon.addVolatile('focusenergy', pokemon, {id: 'zpower'}); } else if (zMove && move.zMoveEffect === 'curse') { if (pokemon.hasType('Ghost')) { this.heal(pokemon.maxhp, pokemon, pokemon, {id: 'zpower'}); } else { this.boost({atk: 1}, pokemon, pokemon, {id: 'zpower'}); } } if (target === false) { this.attrLastMove('[notarget]'); this.add('-notarget'); if (move.target === 'normal') pokemon.isStaleCon = 0; return true; } let targets = pokemon.getMoveTargets(move, target); if (!sourceEffect || sourceEffect.id === 'pursuit') { let extraPP = 0; for (let i = 0; i < targets.length; i++) { let ppDrop = this.singleEvent('DeductPP', targets[i].getAbility(), targets[i].abilityData, targets[i], pokemon, move); if (ppDrop !== true) { extraPP += ppDrop || 0; } } if (extraPP > 0) { pokemon.deductPP(move, extraPP); } } if (!this.singleEvent('TryMove', move, null, pokemon, target, move)) { return true; } if (!this.runEvent('TryMove', pokemon, target, move)) { return true; } this.singleEvent('UseMoveMessage', move, null, pokemon, target, move); if (move.ignoreImmunity === undefined) { move.ignoreImmunity = (move.category === 'Status'); } if (move.selfdestruct === 'always') { this.faint(pokemon, pokemon, move); } let damage = false; if (move.target === 'all' || move.target === 'foeSide' || move.target === 'allySide' || move.target === 'allyTeam') { damage = this.tryMoveHit(target, pokemon, move); if (damage || damage === 0 || damage === undefined) moveResult = true; } else if (move.target === 'allAdjacent' || move.target === 'allAdjacentFoes') { if (!targets.length) { this.attrLastMove('[notarget]'); this.add('-notarget'); return true; } if (targets.length > 1) move.spreadHit = true; damage = 0; let hitTargets = []; for (let i = 0; i < targets.length; i++) { let hitResult = this.tryMoveHit(targets[i], pokemon, move); if (hitResult || hitResult === 0 || hitResult === undefined) { moveResult = true; hitTargets.push(targets[i].toString().substr(0, 3)); } damage += hitResult || 0; } if (move.spreadHit) this.attrLastMove('[spread] ' + hitTargets.join(',')); } else { target = targets[0]; let 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 && (!move.flags['charge'] || pokemon.volatiles['twoturnmove'])) { this.attrLastMove('[notarget]'); this.add('-notarget'); if (move.target === 'normal') pokemon.isStaleCon = 0; return true; } damage = this.tryMoveHit(target, pokemon, move); if (damage || damage === 0 || damage === undefined) moveResult = true; } if (!pokemon.hp) { this.faint(pokemon, pokemon, move); } if (!moveResult) { this.singleEvent('MoveFail', move, null, target, pokemon, move); return true; } if (!move.negateSecondary && !(pokemon.hasAbility('sheerforce') && pokemon.volatiles['sheerforce'])) { this.singleEvent('AfterMoveSecondarySelf', move, null, pokemon, target, move); this.runEvent('AfterMoveSecondarySelf', pokemon, target, move); } return true; }, tryMoveHit: function (target, pokemon, move) { this.setActiveMove(move, pokemon, target); let 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.ignoreImmunity === undefined) { move.ignoreImmunity = (move.category === 'Status'); } if (move.ignoreImmunity !== true && !move.ignoreImmunity[move.type] && !target.runImmunity(move.type, true)) { return false; } hitResult = this.runEvent('TryHit', target, pokemon, move); if (!hitResult) { if (hitResult === false) this.add('-fail', target); return false; } if (move.flags['powder'] && target !== pokemon && !this.getImmunity('powder', target)) { this.debug('natural powder immunity'); this.add('-immune', target, '[msg]'); return false; } if (this.gen >= 7 && move.pranksterBoosted && target.side !== pokemon.side && !this.getImmunity('prankster', target)) { this.debug('natural prankster immunity'); if (!target.illusion) this.add('-hint', "In gen 7, Dark is immune to Prankster moves."); this.add('-immune', target, '[msg]'); return false; } let boostTable = [1, 4 / 3, 5 / 3, 2, 7 / 3, 8 / 3, 3]; // calculate true accuracy let accuracy = move.accuracy; let boosts, boost; if (accuracy !== true) { if (!move.ignoreAccuracy) { boosts = this.runEvent('ModifyBoost', pokemon, null, null, Object.assign({}, pokemon.boosts)); boost = this.clampIntRange(boosts['accuracy'], -6, 6); if (boost > 0) { accuracy *= boostTable[boost]; } else { accuracy /= boostTable[-boost]; } } if (!move.ignoreEvasion) { boosts = this.runEvent('ModifyBoost', target, null, null, Object.assign({}, target.boosts)); boost = this.clampIntRange(boosts['evasion'], -6, 6); if (boost > 0) { accuracy /= boostTable[boost]; } else if (boost < 0) { accuracy *= boostTable[-boost]; } } } if (move.ohko) { // bypasses accuracy modifiers if (!target.isSemiInvulnerable()) { accuracy = 30; if (move.ohko === 'Ice' && this.gen >= 7 && !pokemon.hasType('Ice')) { accuracy = 20; } if (pokemon.level >= target.level && (move.ohko === true || !target.hasType(move.ohko))) { accuracy += (pokemon.level - target.level); } else { this.add('-immune', target, '[ohko]'); return false; } } } else { accuracy = this.runEvent('ModifyAccuracy', target, pokemon, move, accuracy); } if (move.alwaysHit || (move.id === 'toxic' && this.gen >= 6 && pokemon.hasType('Poison'))) { accuracy = true; // bypasses ohko accuracy modifiers } else { accuracy = this.runEvent('Accuracy', target, pokemon, move, accuracy); } if (accuracy !== true && this.random(100) >= accuracy) { if (!move.spreadHit) this.attrLastMove('[miss]'); this.add('-miss', pokemon, target); return false; } if (move.breaksProtect) { let broke = false; for (let i in {banefulbunker:1, kingsshield:1, protect:1, spikyshield:1}) { if (target.removeVolatile(i)) broke = true; } if (this.gen >= 6 || target.side !== pokemon.side) { for (let i in {craftyshield:1, matblock:1, quickguard:1, wideguard:1}) { if (target.side.removeSideCondition(i)) broke = true; } } if (broke) { if (move.id === 'feint') { this.add('-activate', target, 'move: Feint'); } else { this.add('-activate', target, 'move: ' + move.name, '[broken]'); } } } if (move.stealsBoosts) { let boosts = {}; let stolen = false; for (let statName in target.boosts) { let stage = target.boosts[statName]; if (stage > 0) { boosts[statName] = stage; stolen = true; } } if (stolen) { this.attrLastMove('[still]'); this.add('-clearpositiveboost', target, pokemon, 'move: ' + move.name); this.boost(boosts, pokemon); for (let statName in boosts) { boosts[statName] = 0; } target.setBoost(boosts); this.add('-anim', pokemon, "Spectral Thief", target); } } move.totalDamage = 0; let damage = 0; pokemon.lastDamage = 0; if (move.multihit) { let 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); let nullDamage = true; let moveDamage; // There is no need to recursively check the ´sleepUsable´ flag as Sleep Talk can only be used while asleep. let isSleepUsable = move.sleepUsable || this.getMove(move.sourceEffect).sleepUsable; let i; for (i = 0; i < hits && target.hp && pokemon.hp; i++) { if (pokemon.status === 'slp' && !isSleepUsable) break; if (move.multiaccuracy && i > 0) { accuracy = move.accuracy; if (accuracy !== true) { if (!move.ignoreAccuracy) { boosts = this.runEvent('ModifyBoost', pokemon, null, null, Object.assign({}, pokemon.boosts)); boost = this.clampIntRange(boosts['accuracy'], -6, 6); if (boost > 0) { accuracy *= boostTable[boost]; } else { accuracy /= boostTable[-boost]; } } if (!move.ignoreEvasion) { boosts = this.runEvent('ModifyBoost', target, null, null, Object.assign({}, target.boosts)); boost = this.clampIntRange(boosts['evasion'], -6, 6); if (boost > 0) { accuracy /= boostTable[boost]; } else if (boost < 0) { accuracy *= boostTable[-boost]; } } } accuracy = this.runEvent('ModifyAccuracy', target, pokemon, move, accuracy); if (!move.alwaysHit) { accuracy = this.runEvent('Accuracy', target, pokemon, move, accuracy); if (accuracy !== true && this.random(100) >= accuracy) break; } } moveDamage = this.moveHit(target, pokemon, move); if (moveDamage === false) break; if (nullDamage && (moveDamage || moveDamage === 0 || moveDamage === undefined)) 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). move.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); move.totalDamage = damage; } if (move.recoil && move.totalDamage) { this.damage(this.calcRecoilDamage(move.totalDamage, move), pokemon, target, 'recoil'); } if (move.struggleRecoil) { this.directDamage(this.clampIntRange(Math.round(pokemon.maxhp / 4), 1), pokemon, pokemon, {id: 'strugglerecoil'}); } if (target && pokemon !== target) target.gotAttacked(move, damage, pokemon); if (move.ohko) this.add('-ohko'); if (!damage && damage !== 0) return damage; this.eachEvent('Update'); if (target && !move.negateSecondary && !(pokemon.hasAbility('sheerforce') && pokemon.volatiles['sheerforce'])) { this.singleEvent('AfterMoveSecondary', move, null, target, pokemon, move); this.runEvent('AfterMoveSecondary', target, pokemon, move); } return damage; }, moveHit: function (target, pokemon, move, moveData, isSecondary, isSelf) { let damage; move = this.getMoveCopy(move); if (!moveData) moveData = move; if (!moveData.flags) moveData.flags = {}; let 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) { if (move.target !== 'all' && move.target !== 'allySide' && move.target !== 'foeSide') { 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) { let 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 === false || damage === null) { if (damage === false && !isSecondary && !isSelf) { this.add('-fail', target); } this.debug('damage calculation interrupted'); return false; } if (move.selfdestruct === 'ifHit') { this.faint(pokemon, pokemon, move); } 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 (moveData.boosts && !target.fainted) { hitResult = this.boost(moveData.boosts, target, pokemon, move, isSecondary, isSelf); didSomething = didSomething || hitResult; } if (moveData.heal && !target.fainted) { let 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) { hitResult = target.trySetStatus(moveData.status, pokemon, moveData.ability ? moveData.ability : move); if (!hitResult && move.status) return hitResult; didSomething = didSomething || hitResult; } 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 && !move.selfDropped) { let selfRoll; if (!isSecondary && moveData.self.boosts) { selfRoll = this.random(100); if (!move.multihit) move.selfDropped = true; } // 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) { let secondaryRoll; let secondaries = this.runEvent('ModifySecondaries', target, pokemon, moveData, moveData.secondaries.slice()); for (let i = 0; i < secondaries.length; i++) { secondaryRoll = this.random(100); if (typeof secondaries[i].chance === 'undefined' || secondaryRoll < secondaries[i].chance) { this.moveHit(target, pokemon, move, 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 && move.category === 'Status') { this.add('-fail', target); } } if (move.selfSwitch && pokemon.hp) { pokemon.switchFlag = move.selfSwitch; } return damage; }, calcRecoilDamage: function (damageDealt, move) { return this.clampIntRange(Math.round(damageDealt * move.recoil[0] / move.recoil[1]), 1); }, zMoveTable: { Poison: "Acid Downpour", Fighting: "All-Out Pummeling", Dark: "Black Hole Eclipse", Grass: "Bloom Doom", Normal: "Breakneck Blitz", Rock: "Continental Crush", Steel: "Corkscrew Crash", Dragon: "Devastating Drake", Electric: "Gigavolt Havoc", Water: "Hydro Vortex", Fire: "Inferno Overdrive", Ghost: "Never-Ending Nightmare", Bug: "Savage Spin-Out", Psychic: "Shattered Psyche", Ice: "Subzero Slammer", Flying: "Supersonic Skystrike", Ground: "Tectonic Rage", Fairy: "Twinkle Tackle", }, getZMove: function (move, pokemon, skipChecks) { let item = pokemon.getItem(); if (!skipChecks) { if (pokemon.side.zMoveUsed) return; if (!item.zMove) return; if (item.zMoveUser && !item.zMoveUser.includes(pokemon.template.species)) return; let moveData = pokemon.getMoveData(move); if (!moveData || !moveData.pp) return; // Draining the PP of the base move prevents the corresponding Z-move from being used. } if (item.zMoveFrom) { if (move.name === item.zMoveFrom) return item.zMove; } else if (item.zMove === true) { if (move.type === item.zMoveType) { if (move.category === "Status") { return move.name; } else if (move.zMovePower) { return this.zMoveTable[move.type]; } } } }, getZMoveCopy: function (move, pokemon) { move = this.getMove(move); let zMove; if (pokemon) { let item = pokemon.getItem(); if (move.name === item.zMoveFrom) { return this.getMoveCopy(item.zMove); } } if (move.category === 'Status') { zMove = this.getMoveCopy(move); zMove.isZ = true; return zMove; } zMove = this.getMoveCopy(this.zMoveTable[move.type]); zMove.basePower = move.zMovePower; zMove.category = move.category; return zMove; }, canZMove: function (pokemon) { if (pokemon.side.zMoveUsed) return; let item = pokemon.getItem(); if (!item.zMove) return; if (item.zMoveUser && !item.zMoveUser.includes(pokemon.template.species)) return; let atLeastOne = false; let zMoves = []; for (let i = 0; i < pokemon.moves.length; i++) { if (pokemon.moveset[i].pp <= 0) { zMoves.push(null); continue; } let move = this.getMove(pokemon.moves[i]); let zMoveName = this.getZMove(move, pokemon, true) || ''; if (zMoveName) { let zMove = this.getMove(zMoveName); if (!zMove.isZ && zMove.category === 'Status') zMoveName = "Z-" + zMoveName; zMoves.push({move: zMoveName, target: zMove.target}); } else { zMoves.push(null); } if (zMoveName) atLeastOne = true; } if (atLeastOne) return zMoves; }, canMegaEvo: function (pokemon) { let altForme = pokemon.baseTemplate.otherFormes && this.getTemplate(pokemon.baseTemplate.otherFormes[0]); let item = pokemon.getItem(); if (altForme && altForme.isMega && altForme.requiredMove && pokemon.moves.includes(toId(altForme.requiredMove)) && !item.zMove) return altForme.species; if (item.megaEvolves !== pokemon.baseTemplate.baseSpecies || item.megaStone === pokemon.species) return false; return item.megaStone; }, runMegaEvo: function (pokemon) { let template = this.getTemplate(pokemon.canMegaEvo); let side = pokemon.side; // Pokémon affected by Sky Drop cannot mega evolve. Enforce it here for now. let foeActive = side.foe.active; for (let 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' : ''); if (pokemon.illusion) { pokemon.ability = ''; // Don't allow Illusion to wear off this.add('-mega', pokemon, pokemon.illusion.template.baseSpecies, template.requiredItem); } else { 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 (let i = 0; i < side.pokemon.length; i++) { side.pokemon[i].canMegaEvo = false; } this.runEvent('AfterMega', pokemon); 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; }, targetTypeChoices: function (targetType) { return CHOOSABLE_TARGETS.has(targetType); }, checkAbilities: function (selectedAbilities, defaultAbilities) { if (!selectedAbilities.length) return true; let selectedAbility = selectedAbilities.pop(); let isValid = false; for (let i = 0; i < defaultAbilities.length; i++) { let defaultAbility = defaultAbilities[i]; if (!defaultAbility) break; if (defaultAbility.includes(selectedAbility)) { defaultAbilities.splice(i, 1); isValid = this.checkAbilities(selectedAbilities, defaultAbilities); if (isValid) break; defaultAbilities.splice(i, 0, defaultAbility); } } if (!isValid) selectedAbilities.push(selectedAbility); return isValid; }, fastPop: function (list, index) { // If an array doesn't need to be in order, replacing the // element at the given index with the removed element // is much, much faster than using list.splice(index, 1). let length = list.length; let element = list[index]; list[index] = list[length - 1]; list.pop(); return element; }, sampleNoReplace: function (list) { // The cute code to sample no replace is: // return list.splice(this.random(length), 1)[0]; // However manually removing the element is twice as fast. // In fact, we don't even need to keep the array in order, so // we just replace the removed element with the last element. let length = list.length; let index = this.random(length); return this.fastPop(list, index); }, checkBattleForme: function (template) { // If the Pokémon has a Mega or Primal alt forme, that's its preferred battle forme. // No randomization, no choice. We are just checking its existence. // Returns a Pokémon template for further details. if (!template.otherFormes) return null; let firstForme = this.getTemplate(template.otherFormes[0]); if (firstForme.isMega || firstForme.isPrimal) return firstForme; return null; }, hasMegaEvo: function (template) { if (!template.otherFormes) return false; let firstForme = this.getTemplate(template.otherFormes[0]); return !!firstForme.isMega; }, getTeam: function (side, team) { const format = this.getFormat(); const teamGenerator = typeof format.team === 'string' && format.team.startsWith('random') ? format.team + 'Team' : ''; if (!teamGenerator && team) return team; // Teams are generated each one with a shiny new PRNG to prevent // information leaks that would empower brute-force attacks. const originalPrng = this.prng; this.prng = new PRNG(); this.prngSeed.push(...this.prng.startingSeed); team = this[teamGenerator || 'randomTeam'](side); this.prng = originalPrng; return team; }, randomCCTeam: function (side) { let team = []; let natures = Object.keys(this.data.Natures); let items = Object.keys(this.data.Items); let random6 = this.random6Pokemon(); for (let i = 0; i < 6; i++) { let species = random6[i]; let template = this.getTemplate(species); // Random legal item let item = ''; if (this.gen >= 2) { do { item = items[this.random(items.length)]; } while (this.getItem(item).gen > this.gen || this.data.Items[item].isNonstandard); } // Make sure forme is legal if (template.battleOnly || template.requiredItems && !template.requiredItems.some(req => toId(req) === item)) { template = this.getTemplate(template.baseSpecies); species = template.name; } // Make sure that a base forme does not hold any forme-modifier items. let itemData = this.getItem(item); if (itemData.forcedForme && species === this.getTemplate(itemData.forcedForme).baseSpecies) { do { item = items[this.random(items.length)]; itemData = this.getItem(item); } while (itemData.gen > this.gen || itemData.isNonstandard || itemData.forcedForme && species === this.getTemplate(itemData.forcedForme).baseSpecies); } // Random ability let abilities = Object.values(template.abilities); let ability = this.gen <= 2 ? 'None' : abilities[this.random(abilities.length)]; // Four random unique moves from the movepool let moves; let pool = ['struggle']; if (species === 'Smeargle') { pool = Object.keys(this.data.Movedex).filter(moveid => !(moveid in {'chatter':1, 'struggle':1, 'paleowave':1, 'shadowstrike':1, 'magikarpsrevenge':1} || this.data.Movedex[moveid].isZ)); } else if (template.learnset) { pool = Object.keys(template.learnset); if (template.species.substr(0, 6) === 'Rotom-') { pool = Array.from(new Set(pool.concat(Object.keys(this.getTemplate(template.baseSpecies).learnset)))); } } else { const learnset = this.getTemplate(template.baseSpecies).learnset; pool = Object.keys(learnset); } if (pool.length <= 4) { moves = pool; } else { moves = [this.sampleNoReplace(pool), this.sampleNoReplace(pool), this.sampleNoReplace(pool), this.sampleNoReplace(pool)]; } // Random EVs let evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0}; let s = ["hp", "atk", "def", "spa", "spd", "spe"]; let evpool = 510; do { let x = s[this.random(s.length)]; let y = this.random(Math.min(256 - evs[x], evpool + 1)); evs[x] += y; evpool -= y; } while (evpool > 0); // Random IVs let 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 let nature = natures[this.random(natures.length)]; // Level balance--calculate directly from stats rather than using some silly lookup table let mbstmin = 1307; // Sunkern has the lowest modified base stat total, and that total is 807 let stats = template.baseStats; // If Wishiwashi, use the school-forme's much higher stats if (template.baseSpecies === 'Wishiwashi') stats = Dex.getTemplate('wishiwashischool').baseStats; // Modified base stat total assumes 31 IVs, 85 EVs in every stat let 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; let 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 level 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 happiness let happiness = this.random(256); // Random shininess let shiny = !this.random(1024); team.push({ name: template.baseSpecies, species: template.species, item: item, ability: ability, moves: moves, evs: evs, ivs: ivs, nature: nature, level: level, happiness: happiness, shiny: shiny, }); } return team; }, random6Pokemon: function () { // Pick six random pokemon--no repeats, even among formes // Also need to either normalize for formes or select formes at random // Unreleased are okay but no CAP let last = [0, 151, 251, 386, 493, 649, 721, 802][this.gen]; let hasDexNumber = {}; for (let i = 0; i < 6; i++) { let num; do { num = this.random(last) + 1; } while (num in hasDexNumber); hasDexNumber[num] = i; } let formes = [[], [], [], [], [], []]; for (let id in this.data.Pokedex) { if (!(this.data.Pokedex[id].num in hasDexNumber)) continue; let template = this.getTemplate(id); if (template.gen <= this.gen && template.learnset && template.species !== 'Pichu-Spiky-eared' && template.species.substr(0, 8) !== 'Pikachu-') { formes[hasDexNumber[template.num]].push(template.species); } } let sixPokemon = []; for (let i = 0; i < 6; i++) { if (!formes[i].length) { // console.log("Could not find pokemon " + i); // for (var k in hasDexNumber) { // if (hasDexNumber[k] === i) { // console.log("dexNumber was " + k); // console.log("dex found: " + JSON.stringify(Object.values(this.data.Pokedex).filter(t => t.num == Number(k)).map(t => t.species))); // } // } throw new Error("Invalid pokemon gen " + this.gen + ": " + JSON.stringify(formes) + " numbers " + JSON.stringify(hasDexNumber)); } sixPokemon.push(formes[i][this.random(formes[i].length)]); } return sixPokemon; }, randomHCTeam: function (side) { let team = []; let itemPool = Object.keys(this.data.Items); let abilityPool = Object.keys(this.data.Abilities); let movePool = Object.keys(this.data.Movedex); let naturePool = Object.keys(this.data.Natures); let random6 = this.random6Pokemon(); for (let i = 0; i < 6; i++) { // Choose forme let template = this.getTemplate(random6[i]); // Random unique item let item = ''; if (this.gen >= 2) { do { item = this.sampleNoReplace(itemPool); } while (this.getItem(item).gen > this.gen || this.data.Items[item].isNonstandard); } // Random unique ability let ability = 'None'; if (this.gen >= 3) { do { ability = this.sampleNoReplace(abilityPool); } while (this.getAbility(ability).gen > this.gen || this.data.Abilities[ability].isNonstandard); } // Random unique moves let m = []; do { let moveid = this.sampleNoReplace(movePool); if (this.getMove(moveid).gen <= this.gen && !this.data.Movedex[moveid].isNonstandard && (moveid === 'hiddenpower' || moveid.substr(0, 11) !== 'hiddenpower')) { m.push(moveid); } } while (m.length < 4); // Random EVs let evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0}; let s = ['hp', 'atk', 'def', 'spa', 'spd', 'spe']; if (this.gen === 6) { let evpool = 510; do { let x = s[this.random(s.length)]; let y = this.random(Math.min(256 - evs[x], evpool + 1)); evs[x] += y; evpool -= y; } while (evpool > 0); } else { for (let x of s) evs[x] = this.random(256); } // Random IVs let 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 let nature = naturePool[this.random(naturePool.length)]; // Level balance let mbstmin = 1307; let stats = template.baseStats; let 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; let 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 let happiness = this.random(256); // Random shininess let shiny = !this.random(1024); team.push({ name: template.baseSpecies, species: template.species, item: item, ability: ability, moves: m, evs: evs, ivs: ivs, nature: nature, level: level, happiness: happiness, shiny: shiny, }); } return team; }, queryMoves: function (moves, hasType, hasAbility, movePool) { // This is primarily a helper function for random setbuilder functions. let counter = { Physical: 0, Special: 0, Status: 0, damage: 0, recovery: 0, stab: 0, inaccurate: 0, priority: 0, recoil: 0, drain: 0, adaptability: 0, bite: 0, contrary: 0, hustle: 0, ironfist: 0, serenegrace: 0, sheerforce: 0, skilllink: 0, technician: 0, physicalsetup: 0, specialsetup: 0, mixedsetup: 0, defensesetup: 0, speedsetup: 0, physicalpool: 0, specialpool: 0, damagingMoves: [], damagingMoveIndex: {}, setupType: '', }; for (let type in Dex.data.TypeChart) { counter[type] = 0; } if (!moves || !moves.length) return counter; if (!hasType) hasType = {}; if (!hasAbility) hasAbility = {}; if (!movePool) movePool = []; // Moves that heal a fixed amount: let RecoveryMove = { healorder: 1, milkdrink: 1, recover: 1, roost: 1, slackoff: 1, softboiled: 1, }; // Moves which drop stats: let ContraryMove = { closecombat: 1, leafstorm: 1, overheat: 1, superpower: 1, vcreate: 1, }; // Moves that boost Attack: let PhysicalSetup = { bellydrum:1, bulkup:1, coil:1, curse:1, dragondance:1, honeclaws:1, howl:1, poweruppunch:1, shiftgear:1, swordsdance:1, }; // Moves which boost Special Attack: let SpecialSetup = { calmmind:1, chargebeam:1, geomancy:1, nastyplot:1, quiverdance:1, tailglow:1, }; // Moves which boost Attack AND Special Attack: let MixedSetup = { conversion: 1, growth:1, shellsmash:1, workup:1, }; // Moves which boost Defense and/or Special Defense: let DefenseSetup = { cosmicpower:1, cottonguard:1, defendorder:1, }; // Moves which boost Speed: let SpeedSetup = { agility:1, autotomize:1, rockpolish:1, }; // Moves that shouldn't be the only STAB moves: let NoStab = { aquajet:1, bounce:1, explosion:1, fakeout:1, flamecharge:1, iceshard:1, pursuit:1, quickattack:1, skyattack:1, chargebeam:1, clearsmog:1, eruption:1, vacuumwave:1, waterspout:1, }; // Iterate through all moves we've chosen so far and keep track of what they do: for (let k = 0; k < moves.length; k++) { let move = this.getMove(moves[k]); let moveid = move.id; let movetype = move.type; if (moveid === 'judgment' || moveid === 'multiattack') movetype = Object.keys(hasType)[0]; if (move.damage || move.damageCallback) { // Moves that do a set amount of damage: counter['damage']++; counter.damagingMoves.push(move); counter.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 && moveid !== 'rapidspin')) counter['technician']++; // Moves that hit up to 5 times: if (move.multihit && move.multihit[1] === 5) counter['skilllink']++; if (move.recoil) counter['recoil']++; if (move.drain) counter['drain']++; // Moves which have a base power, but aren't super-weak like Rapid Spin: if (move.basePower > 30 || move.multihit || move.basePowerCallback || moveid === 'naturepower') { counter[movetype]++; if (hasType[movetype]) { counter['adaptability']++; // STAB: // Certain moves aren't acceptable as a Pokemon's only STAB attack if (!(moveid in NoStab) && (moveid !== 'hiddenpower' || Object.keys(hasType).length === 1)) counter['stab']++; } if (move.priority === 0 && (hasAbility['Protean'] || moves.includes('conversion')) && !(moveid in NoStab)) counter['stab']++; if (move.category === 'Physical') counter['hustle']++; if (movetype === 'Normal' && !(moveid in NoStab)) { if (hasAbility['Aerilate'] || hasAbility['Pixilate'] || hasAbility['Refrigerate']) counter['stab']++; } if (move.flags['bite']) counter['bite']++; if (move.flags['punch']) counter['ironfist']++; counter.damagingMoves.push(move); counter.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 (DefenseSetup[moveid]) counter['defensesetup']++; if (SpeedSetup[moveid]) counter['speedsetup']++; } // Keep track of the available moves for (let i = 0; i < movePool.length; i++) { let move = this.getMove(movePool[i]); if (move.category === 'Physical') counter['physicalpool']++; if (move.category === 'Special') counter['specialpool']++; } // Choose a setup type: if (counter['mixedsetup']) { counter.setupType = 'Mixed'; } else if (counter['physicalsetup'] || counter['specialsetup']) { let physical = counter.Physical + counter['physicalpool']; let special = counter.Special + counter['specialpool']; if (counter['physicalsetup'] && counter['specialsetup']) { if (physical === special) { counter.setupType = counter.Physical > counter.Special ? 'Physical' : 'Special'; } else { counter.setupType = physical > special ? 'Physical' : 'Special'; } } else if (counter['physicalsetup'] && physical >= 1) { if (physical >= 2 || moves.includes('rest') && moves.includes('sleeptalk')) { counter.setupType = 'Physical'; } } else if (counter['specialsetup'] && special >= 1) { if (special >= 2 || moves.includes('rest') && moves.includes('sleeptalk')) { counter.setupType = 'Special'; } } } return counter; }, randomSet: function (template, slot, teamDetails) { if (slot === undefined) slot = 1; let baseTemplate = (template = this.getTemplate(template)); let species = template.species; if (!template.exists || (!template.randomBattleMoves && !template.learnset)) { // GET IT? UNOWN? BECAUSE WE CAN'T TELL WHAT THE POKEMON IS template = this.getTemplate('unown'); let err = new Error('Template incompatible with random battles: ' + species); require('../crashlogger')(err, 'The randbat set generator'); } if (typeof teamDetails !== 'object') teamDetails = {megaStone: teamDetails}; if (template.battleOnly) { // Only change the species. The template has custom moves, and may have different typing and requirements. species = template.baseSpecies; } let battleForme = this.checkBattleForme(template); if (battleForme && battleForme.randomBattleMoves && (battleForme.isMega ? !teamDetails.megaStone : this.random(2))) { template = this.getTemplate(template.otherFormes.length >= 2 ? template.otherFormes[this.random(template.otherFormes.length)] : template.otherFormes[0]); } let movePool = (template.randomBattleMoves ? template.randomBattleMoves.slice() : Object.keys(template.learnset)); let moves = []; let ability = ''; let item = ''; let evs = { hp: 85, atk: 85, def: 85, spa: 85, spd: 85, spe: 85, }; let ivs = { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31, }; let hasType = {}; hasType[template.types[0]] = true; if (template.types[1]) { hasType[template.types[1]] = true; } let hasAbility = {}; hasAbility[template.abilities[0]] = true; if (template.abilities[1]) { hasAbility[template.abilities[1]] = true; } if (template.abilities['H']) { hasAbility[template.abilities['H']] = true; } let availableHP = 0; for (let i = 0, len = movePool.length; i < len; i++) { if (movePool[i].substr(0, 11) === 'hiddenpower') availableHP++; } // These moves can be used even if we aren't setting up to use them: let SetupException = { closecombat:1, extremespeed:1, suckerpunch:1, superpower:1, dracometeor:1, leafstorm:1, overheat:1, }; let counterAbilities = { 'Adaptability':1, 'Contrary':1, 'Hustle':1, 'Iron Fist':1, 'Skill Link':1, }; let ateAbilities = { 'Aerilate':1, 'Pixilate':1, 'Refrigerate':1, }; let hasMove, counter; do { // Keep track of all moves we have: hasMove = {}; for (let 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) { let 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); } counter = this.queryMoves(moves, hasType, hasAbility, movePool); // Iterate through the moves again, this time to cull them: for (let k = 0; k < moves.length; k++) { let move = this.getMove(moves[k]); let moveid = move.id; let rejected = false; let isSetup = false; switch (moveid) { // Not very useful without their supporting moves case 'batonpass': if (!counter.setupType && !counter['speedsetup'] && !hasMove['substitute'] && !hasMove['wish'] && !hasAbility['Speed Boost']) rejected = true; break; case 'focuspunch': if (!hasMove['substitute'] || counter.damagingMoves.length < 2) rejected = true; break; case 'perishsong': if (!hasMove['protect']) rejected = true; break; case 'rest': { if (movePool.includes('sleeptalk')) rejected = true; break; } case 'sleeptalk': if (!hasMove['rest']) rejected = true; if (movePool.length > 1) { let rest = movePool.indexOf('rest'); if (rest >= 0) this.fastPop(movePool, rest); } break; case 'storedpower': if (!counter.setupType && !hasMove['cosmicpower']) rejected = true; break; // Set up once and only if we have the moves for it case 'bellydrum': case 'bulkup': case 'coil': case 'curse': case 'dragondance': case 'honeclaws': case 'swordsdance': if (counter.setupType !== 'Physical' || counter['physicalsetup'] > 1) { if (!hasMove['growth'] || hasMove['sunnyday']) rejected = true; } if (counter.Physical + counter['physicalpool'] < 2 && !hasMove['batonpass'] && (!hasMove['rest'] || !hasMove['sleeptalk'])) rejected = true; isSetup = true; break; case 'calmmind': case 'geomancy': case 'nastyplot': case 'quiverdance': case 'tailglow': if (counter.setupType !== 'Special' || counter['specialsetup'] > 1) rejected = true; if (counter.Special + counter['specialpool'] < 2 && !hasMove['batonpass'] && (!hasMove['rest'] || !hasMove['sleeptalk'])) rejected = true; isSetup = true; break; case 'growth': case 'shellsmash': case 'workup': if (counter.setupType !== 'Mixed' || counter['mixedsetup'] > 1) rejected = true; if (counter.damagingMoves.length + counter['physicalpool'] + counter['specialpool'] < 2 && !hasMove['batonpass']) rejected = true; if (moveid === 'growth' && !hasMove['sunnyday']) rejected = true; isSetup = true; break; case 'agility': case 'autotomize': case 'rockpolish': if (counter.damagingMoves.length < 2 && !hasMove['batonpass']) rejected = true; if (hasMove['rest'] && hasMove['sleeptalk']) rejected = true; if (!counter.setupType) isSetup = true; break; case 'flamecharge': if (counter.damagingMoves.length < 3 && !counter.setupType && !hasMove['batonpass']) rejected = true; if (hasMove['dracometeor'] || hasMove['overheat']) rejected = true; break; case 'conversion': if (teamDetails.zMove || hasMove['triattack']) rejected = true; break; // Bad after setup case 'circlethrow': case 'dragontail': if (counter.setupType && ((!hasMove['rest'] && !hasMove['sleeptalk']) || hasMove['stormthrow'])) rejected = true; if (!!counter['speedsetup'] || hasMove['encore'] || hasMove['raindance'] || hasMove['roar'] || hasMove['whirlwind']) rejected = true; break; case 'defog': if (counter.setupType || hasMove['spikes'] || (hasMove['rest'] && hasMove['sleeptalk']) || teamDetails.hazardClear) rejected = true; break; case 'fakeout': case 'superfang': if (counter.setupType || hasMove['substitute'] || hasMove['switcheroo'] || hasMove['trick']) rejected = true; break; case 'foulplay': if (counter.setupType || !!counter['speedsetup'] || counter['Dark'] > 2 || (hasMove['rest'] && hasMove['sleeptalk'])) rejected = true; if (counter.damagingMoves.length - 1 === counter['priority']) rejected = true; break; case 'haze': case 'spikes': case 'waterspout': if (counter.setupType || !!counter['speedsetup'] || (hasMove['rest'] && hasMove['sleeptalk'])) rejected = true; break; case 'healbell': if (counter['speedsetup']) rejected = true; break; case 'healingwish': case 'memento': if (counter.setupType || !!counter['recovery'] || hasMove['substitute']) rejected = true; break; case 'nightshade': case 'seismictoss': if (counter.stab || counter.setupType || counter.damagingMoves.length > 2) rejected = true; break; case 'protect': if (counter.setupType && (hasAbility['Guts'] || hasAbility['Speed Boost']) && !hasMove['batonpass']) rejected = true; if ((hasMove['lightscreen'] && hasMove['reflect']) || (hasMove['rest'] && hasMove['sleeptalk'])) rejected = true; break; case 'pursuit': if (counter.setupType || (hasMove['rest'] && hasMove['sleeptalk']) || (hasMove['knockoff'] && !hasType['Dark'])) rejected = true; break; case 'rapidspin': if (counter.setupType || teamDetails.hazardClear) rejected = true; break; case 'roar': case 'whirlwind': if (counter.setupType || hasMove['dragontail']) rejected = true; break; case 'stealthrock': if (counter.setupType || !!counter['speedsetup'] || hasMove['rest'] || teamDetails.stealthRock) rejected = true; break; case 'switcheroo': case 'trick': if (counter.Physical + counter.Special < 3 || counter.setupType) rejected = true; if (hasMove['acrobatics'] || hasMove['lightscreen'] || hasMove['reflect'] || hasMove['suckerpunch'] || hasMove['trickroom']) rejected = true; break; case 'toxicspikes': if (counter.setupType || teamDetails.toxicSpikes) rejected = true; break; case 'trickroom': if (counter.setupType || !!counter['speedsetup'] || counter.damagingMoves.length < 2) rejected = true; if (hasMove['lightscreen'] || hasMove['reflect']) rejected = true; break; case 'uturn': if (counter.setupType || !!counter['speedsetup'] || hasMove['batonpass']) rejected = true; if (hasType['Bug'] && counter.stab < 2 && counter.damagingMoves.length > 2 && !hasAbility['Adaptability'] && !hasMove['technoblast']) rejected = true; break; case 'voltswitch': if (counter.setupType || !!counter['speedsetup'] || hasMove['batonpass'] || hasMove['magnetrise'] || hasMove['uturn']) rejected = true; if (hasMove['nuzzle'] && hasMove['thunderbolt']) rejected = true; break; // Bit redundant to have both // Attacks: case 'bugbite': case 'bugbuzz': case 'signalbeam': if (hasMove['uturn'] && !counter.setupType) rejected = true; break; case 'lunge': if (hasMove['leechlife']) rejected = true; break; case 'darkpulse': if (hasMove['shadowball']) rejected = true; if ((hasMove['crunch'] || hasMove['hyperspacefury']) && counter.setupType !== 'Special') rejected = true; break; case 'suckerpunch': if (counter['Dark'] > 2 || (counter.setupType === 'Special' && hasType['Dark'] && counter.stab < 2)) rejected = true; if (counter['Dark'] > 1 && !hasType['Dark']) rejected = true; if (counter.damagingMoves.length < 2 || hasMove['rest'] && hasMove['sleeptalk']) rejected = true; break; case 'dragonclaw': if (hasMove['outrage'] || hasMove['dragontail']) rejected = true; break; case 'dracometeor': if (counter.setupType === 'Physical' && hasMove['outrage']) rejected = true; break; case 'dragonpulse': case 'spacialrend': if (hasMove['dracometeor'] || hasMove['outrage']) rejected = true; break; case 'outrage': if (hasMove['dracometeor'] && counter.damagingMoves.length < 3) rejected = true; break; case 'chargebeam': if (hasMove['thunderbolt'] && counter.Special < 3) rejected = true; break; case 'thunder': if (hasMove['thunderbolt'] && !hasMove['raindance']) rejected = true; break; case 'thunderbolt': if (hasMove['discharge'] || (hasMove['raindance'] && hasMove['thunder']) || (hasMove['voltswitch'] && hasMove['wildcharge'])) rejected = true; if (!counter.setupType && !counter['speedsetup'] && hasMove['voltswitch'] && template.types.length > 1 && !counter[template.types.find(type => type !== 'Electric')]) rejected = true; break; case 'dazzlinggleam': if (hasMove['playrough'] && counter.setupType !== 'Special') rejected = true; break; case 'drainingkiss': if (hasMove['dazzlinggleam'] || counter.setupType !== 'Special' && !hasAbility['triage']) rejected = true; break; case 'aurasphere': case 'focusblast': if ((hasMove['closecombat'] || hasMove['superpower']) && counter.setupType !== 'Special') rejected = true; if (hasMove['rest'] && hasMove['sleeptalk']) rejected = true; break; case 'drainpunch': if (!hasMove['bulkup'] && (hasMove['closecombat'] || hasMove['highjumpkick'])) rejected = true; if (hasMove['focusblast'] || hasMove['superpower']) rejected = true; break; case 'closecombat': case 'highjumpkick': if ((hasMove['aurasphere'] || hasMove['focusblast'] || movePool.includes('aurasphere')) && counter.setupType === 'Special') rejected = true; if (hasMove['bulkup'] && hasMove['drainpunch']) rejected = true; break; case 'machpunch': if (hasType['Fighting'] && counter.stab < 2 && !hasAbility['Technician']) rejected = true; break; case 'stormthrow': if (hasMove['circlethrow'] && hasMove['rest'] && hasMove['sleeptalk']) rejected = true; break; case 'superpower': if (counter['Fighting'] > 1 && counter.setupType) rejected = true; if (hasMove['rest'] && hasMove['sleeptalk']) rejected = true; break; case 'vacuumwave': if ((hasMove['closecombat'] || hasMove['machpunch']) && counter.setupType !== 'Special') rejected = true; break; case 'blazekick': if (hasMove['flamethrower'] && counter.setupType !== 'Physical') rejected = true; break; case 'fierydance': case 'firefang': case 'flamethrower': if ((hasMove['fireblast'] && counter.setupType !== 'Physical') || hasMove['overheat']) rejected = true; break; case 'fireblast': if (hasMove['lavaplume'] && !counter.setupType && !counter['speedsetup']) rejected = true; if (hasMove['flareblitz'] && counter.setupType !== 'Special') rejected = true; break; case 'firepunch': case 'sacredfire': if (hasMove['fireblast'] || hasMove['flareblitz']) rejected = true; break; case 'lavaplume': if (hasMove['fireblast'] && (counter.setupType || !!counter['speedsetup'])) rejected = true; break; case 'overheat': if (hasMove['lavaplume'] || counter.setupType === 'Special') rejected = true; break; case 'acrobatics': if (hasMove['hurricane'] && counter.setupType !== 'Physical') rejected = true; break; case 'airslash': case 'oblivionwing': if (hasMove['acrobatics'] || hasMove['bravebird'] || hasMove['hurricane']) rejected = true; break; case 'hex': if (!hasMove['willowisp']) rejected = true; break; case 'shadowball': if (hasMove['hex'] && hasMove['willowisp']) rejected = true; break; case 'shadowclaw': if (hasMove['phantomforce'] || (hasMove['shadowball'] && counter.setupType !== 'Physical') || hasMove['shadowsneak']) rejected = true; break; case 'shadowsneak': if (hasType['Ghost'] && template.types.length > 1 && counter.stab < 2) rejected = true; if (hasMove['rest'] && hasMove['sleeptalk']) rejected = true; break; case 'solarbeam': if ((!hasAbility['Drought'] && !hasMove['sunnyday']) || hasMove['gigadrain'] || hasMove['leafstorm']) rejected = true; break; case 'gigadrain': if (hasMove['petaldance'] || !counter.setupType && hasMove['leafstorm']) rejected = true; break; case 'leafblade': case 'seedbomb': case 'woodhammer': if (hasMove['gigadrain'] && counter.setupType !== 'Physical') rejected = true; break; case 'leafstorm': if (counter['Grass'] > 1 && counter.setupType) rejected = true; break; case 'bonemerang': case 'precipiceblades': if (hasMove['earthquake']) rejected = true; break; case 'earthpower': if (hasMove['earthquake'] && counter.setupType !== 'Special') rejected = true; break; case 'icebeam': if (hasMove['blizzard'] || hasMove['freezedry']) rejected = true; break; case 'iceshard': if (hasMove['freezedry']) rejected = true; break; case 'bodyslam': if (hasMove['glare'] || hasMove['headbutt']) rejected = true; break; case 'endeavor': if (slot > 0) rejected = true; break; case 'explosion': if (counter.setupType || (hasAbility['Refrigerate'] && hasMove['freezedry']) || hasMove['wish']) rejected = true; break; case 'extremespeed': if (counter.setupType !== 'Physical' && hasMove['vacuumwave']) rejected = true; break; case 'facade': if (hasMove['rest'] && hasMove['sleeptalk']) rejected = true; break; case 'hiddenpower': if (hasMove['rest'] || !counter.stab && counter.damagingMoves.length < 2) rejected = true; break; case 'hypervoice': if (hasMove['naturepower'] || hasMove['return']) rejected = true; if (hasAbility['Liquid Voice'] && hasMove['scald']) rejected = true; break; case 'judgment': if (counter.setupType !== 'Special' && counter.stab > 1) rejected = true; break; case 'quickattack': if (hasType['Normal'] && counter['Normal'] > 1 && template.types.length > 1 && counter.stab < 2) rejected = true; break; case 'return': case 'rockclimb': if (hasMove['bodyslam'] || hasMove['doubleedge']) rejected = true; break; case 'weatherball': if (!hasMove['raindance'] && !hasMove['sunnyday']) rejected = true; break; case 'acidspray': if (hasMove['sludgebomb'] || counter.Special < 2) rejected = true; break; case 'poisonjab': if (hasMove['gunkshot']) rejected = true; break; case 'sludgewave': if (hasMove['poisonjab']) rejected = true; break; case 'psychic': if (hasMove['psyshock'] || hasMove['storedpower']) rejected = true; break; case 'psychocut': case 'zenheadbutt': if ((hasMove['psychic'] || hasMove['psyshock']) && counter.setupType !== 'Physical') rejected = true; break; case 'psyshock': if (movePool.length > 1) { let psychic = movePool.indexOf('psychic'); if (psychic >= 0) this.fastPop(movePool, psychic); } break; case 'headsmash': if (hasMove['stoneedge']) rejected = true; break; case 'rockblast': case 'rockslide': if (hasMove['headsmash'] || hasMove['stoneedge']) rejected = true; break; case 'bulletpunch': if (hasType['Steel'] && counter.stab < 2 && !hasAbility['Adaptability'] && !hasAbility['Technician']) rejected = true; break; case 'flashcannon': if (hasMove['ironhead']) rejected = true; break; case 'hydropump': if (hasMove['razorshell'] || hasMove['scald'] || hasMove['waterfall'] || (hasMove['rest'] && hasMove['sleeptalk'])) rejected = true; break; case 'originpulse': case 'surf': if (hasMove['hydropump'] || hasMove['scald']) rejected = true; break; case 'scald': if (hasMove['liquidation'] || hasMove['waterfall'] || hasMove['waterpulse']) rejected = true; break; // Status: case 'raindance': if (counter.Physical + counter.Special < 2 || hasMove['rest'] && hasMove['sleeptalk']) rejected = true; if (!hasType['Water'] && !hasMove['thunder']) rejected = true; break; case 'sunnyday': if (counter.Physical + counter.Special < 2 || hasMove['rest'] && hasMove['sleeptalk']) rejected = true; if (!hasAbility['Chlorophyll'] && !hasAbility['Flower Gift'] && !hasMove['solarbeam']) rejected = true; if (rejected && movePool.length > 1) { let solarbeam = movePool.indexOf('solarbeam'); if (solarbeam >= 0) this.fastPop(movePool, solarbeam); if (movePool.length > 1) { let weatherball = movePool.indexOf('weatherball'); if (weatherball >= 0) this.fastPop(movePool, weatherball); } } break; case 'stunspore': case 'thunderwave': if (counter.setupType || !!counter['speedsetup'] || (hasMove['rest'] && hasMove['sleeptalk'])) rejected = true; if (hasMove['discharge'] || hasMove['gyroball'] || hasMove['spore'] || hasMove['toxic'] || hasMove['trickroom'] || hasMove['yawn']) rejected = true; break; case 'toxic': if (counter.setupType || hasMove['flamecharge'] || (hasMove['rest'] && hasMove['sleeptalk'])) rejected = true; if (hasMove['hypnosis'] || hasMove['sleeppowder'] || hasMove['willowisp'] || hasMove['yawn']) rejected = true; break; case 'willowisp': if (hasMove['scald']) rejected = true; break; case 'moonlight': case 'painsplit': case 'recover': case 'roost': case 'softboiled': case 'synthesis': if (hasMove['leechseed'] || hasMove['rest'] || hasMove['wish']) rejected = true; break; case 'safeguard': if (hasMove['destinybond']) rejected = true; break; case 'substitute': if (hasMove['dracometeor'] || (hasMove['leafstorm'] && !hasAbility['Contrary']) || hasMove['pursuit'] || hasMove['rest'] || hasMove['taunt'] || hasMove['uturn'] || hasMove['voltswitch']) rejected = true; break; } // Increased/decreased priority moves are unneeded with moves that boost only speed if (move.priority !== 0 && (!!counter['speedsetup'] || hasMove['copycat'])) { rejected = true; } // Certain Pokemon should always have a recovery move if (!counter.recovery && template.baseStats.hp >= 165 && movePool.includes('wish')) { if (move.category === 'Status' || !hasType[move.type] && !move.damage) rejected = true; } if (template.nfe && !isSetup && !counter.recovery && !!counter['Status'] && (movePool.includes('recover') || movePool.includes('roost'))) { if (move.category === 'Status' || !hasType[move.type]) rejected = true; } // This move doesn't satisfy our setup requirements: if ((move.category === 'Physical' && counter.setupType === 'Special') || (move.category === 'Special' && counter.setupType === 'Physical')) { // Reject STABs last in case the setup type changes later on if (!SetupException[moveid] && (!hasType[move.type] || counter.stab > 1 || counter[move.category] < 2)) rejected = true; } if (counter.setupType && !isSetup && counter.setupType !== 'Mixed' && move.category !== counter.setupType && counter[counter.setupType] < 2 && !hasMove['batonpass'] && moveid !== 'rest' && moveid !== 'sleeptalk') { // Mono-attacking with setup and RestTalk is allowed // Reject Status moves only if there is nothing else to reject if (move.category !== 'Status' || counter[counter.setupType] + counter.Status > 3 && counter['physicalsetup'] + counter['specialsetup'] < 2) rejected = true; } if (counter.setupType === 'Special' && moveid === 'hiddenpower' && template.types.length > 1 && counter['Special'] <= 2 && !hasType[move.type] && !counter['Physical'] && counter['specialpool']) { // Hidden Power isn't good enough rejected = true; } // Pokemon should have moves that benefit their Ability/Type/Weather, as well as moves required by its forme if ((hasType['Bug'] && !hasMove['batonpass'] && (movePool.includes('megahorn') || movePool.includes('pinmissile') || (hasType['Flying'] && !hasMove['hurricane'] && movePool.includes('bugbuzz')))) || (hasType['Dark'] && hasMove['suckerpunch'] && counter.stab < template.types.length) || (hasType['Dragon'] && !counter['Dragon'] && !hasAbility['Aerilate'] && !hasAbility['Pixilate'] && !hasMove['rest'] && !hasMove['sleeptalk']) || (hasType['Electric'] && !counter['Electric']) || (hasType['Fighting'] && !counter['Fighting'] && (counter.setupType || !counter['Status'])) || (hasType['Fire'] && !counter['Fire']) || (hasType['Ground'] && !counter['Ground'] && (counter.setupType || counter['speedsetup'] || hasMove['raindance'] || !counter['Status'])) || (hasType['Ice'] && !counter['Ice'] && !hasAbility['Refrigerate']) || (hasType['Psychic'] && !!counter['Psychic'] && !hasType['Flying'] && !hasAbility['Pixilate'] && template.types.length > 1 && counter.stab < 2) || (hasType['Water'] && !counter['Water'] && (!hasType['Ice'] || !counter['Ice']) && !hasAbility['Protean']) || ((hasAbility['Adaptability'] && !counter.setupType && template.types.length > 1 && (!counter[template.types[0]] || !counter[template.types[1]])) || ((hasAbility['Aerilate'] || hasAbility['Pixilate'] || hasAbility['Refrigerate']) && !counter['Normal']) || (hasAbility['Contrary'] && !counter['contrary'] && template.species !== 'Shuckle') || (hasAbility['Dark Aura'] && !counter['Dark']) || (hasAbility['Electric Surge'] && !counter['Electric']) || (hasAbility['Gale Wings'] && !counter['Flying']) || (hasAbility['Grassy Surge'] && !counter['Grass']) || (hasAbility['Guts'] && hasType['Normal'] && movePool.includes('facade')) || (hasAbility['Psychic Surge'] && !counter['Psychic']) || (hasAbility['Slow Start'] && movePool.includes('substitute')) || (hasAbility['Stance Change'] && !counter.setupType && movePool.includes('kingsshield')) || (hasAbility['Water Bubble'] && !counter['Water']) || (counter['defensesetup'] && !counter.recovery && !hasMove['rest']) || (movePool.includes('technoblast') || template.requiredMove && movePool.includes(toId(template.requiredMove)))) && (counter['physicalsetup'] + counter['specialsetup'] < 2 && (!counter.setupType || counter.setupType === 'Mixed' || (move.category !== counter.setupType && move.category !== 'Status') || counter[counter.setupType] + counter.Status > 3))) { // Reject Status or non-STAB if (!isSetup && !move.weather && moveid !== 'judgment' && moveid !== 'rest' && moveid !== 'sleeptalk') { if (move.category === 'Status' || !hasType[move.type] || (move.basePower && move.basePower < 40 && !move.multihit)) rejected = true; } } // Sleep Talk shouldn't be selected without Rest if (moveid === 'rest' && rejected) { let sleeptalk = movePool.indexOf('sleeptalk'); if (sleeptalk >= 0) { if (movePool.length < 2) { rejected = false; } else { this.fastPop(movePool, sleeptalk); } } } // Remove rejected moves from the move list if (rejected && (movePool.length - availableHP || availableHP && (moveid === 'hiddenpower' || !hasMove['hiddenpower']))) { moves.splice(k, 1); break; } } if (moves.length === 4 && !counter.stab && !hasMove['metalburst'] && (counter['physicalpool'] || counter['specialpool'])) { // Move post-processing: if (counter.damagingMoves.length === 0) { // A set shouldn't have no attacking moves moves.splice(this.random(moves.length), 1); } else if (counter.damagingMoves.length === 1) { // In most cases, a set shouldn't have no STAB let damagingid = counter.damagingMoves[0].id; if (movePool.length - availableHP || availableHP && (damagingid === 'hiddenpower' || !hasMove['hiddenpower'])) { let replace = false; if (!counter.damagingMoves[0].damage && template.species !== 'Porygon2') { let damagingType = counter.damagingMoves[0].type; if (damagingType === 'Fairy') { // Mono-Fairy is acceptable for Psychic types if (counter.setupType !== 'Special' || template.types.length > 1 || !hasType['Psychic']) replace = true; } else { replace = true; } } if (replace) moves.splice(counter.damagingMoveIndex[damagingid], 1); } } else if (!counter.damagingMoves[0].damage && !counter.damagingMoves[1].damage && template.species !== 'Porygon2') { // If you have three or more attacks, and none of them are STAB, reject one of them at random. let rejectableMoves = []; let baseDiff = movePool.length - availableHP; for (let l = 0; l < counter.damagingMoves.length; l++) { if (counter.damagingMoves[l].id === 'technoblast') continue; if (baseDiff || availableHP && (!hasMove['hiddenpower'] || counter.damagingMoves[l].id === 'hiddenpower')) { rejectableMoves.push(counter.damagingMoveIndex[counter.damagingMoves[l].id]); } } if (rejectableMoves.length) { moves.splice(rejectableMoves[this.random(rejectableMoves.length)], 1); } } } } while (moves.length < 4 && movePool.length); // Moveset modifications if (hasMove['autotomize'] && hasMove['heavyslam']) { moves[moves.indexOf('autotomize')] = 'rockpolish'; } if (moves[0] === 'conversion') { moves[0] = moves[3]; moves[3] = 'conversion'; } let abilities = Object.values(baseTemplate.abilities); abilities.sort((a, b) => this.getAbility(b).rating - this.getAbility(a).rating); let ability0 = this.getAbility(abilities[0]); let ability1 = this.getAbility(abilities[1]); let ability2 = this.getAbility(abilities[2]); ability = ability0.name; if (abilities[1]) { if (abilities[2] && ability2.rating === ability1.rating) { if (this.random(2)) ability1 = ability2; } 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; } let rejectAbility = false; if (ability in counterAbilities) { // Adaptability, Contrary, Hustle, Iron Fist, Skill Link rejectAbility = !counter[toId(ability)]; } else if (ability in ateAbilities) { rejectAbility = !counter['Normal']; } else if (ability === 'Blaze') { rejectAbility = !counter['Fire']; } else if (ability === 'Chlorophyll') { rejectAbility = !hasMove['sunnyday']; } else if (ability === 'Compound Eyes' || ability === 'No Guard') { rejectAbility = !counter['inaccurate']; } else if (ability === 'Defiant' || ability === 'Moxie') { rejectAbility = !counter['Physical'] && !hasMove['batonpass']; } else if (ability === 'Flare Boost' || ability === 'Moody') { rejectAbility = true; } else if (ability === 'Gluttony') { rejectAbility = !hasMove['bellydrum']; } else if (ability === 'Lightning Rod') { rejectAbility = template.types.includes('Ground'); } else if (ability === 'Limber') { rejectAbility = template.types.includes('Electric'); } else if (ability === 'Liquid Voice') { rejectAbility = !hasMove['hypervoice']; } else if (ability === 'Overgrow') { rejectAbility = !counter['Grass']; } else if (ability === 'Poison Heal') { rejectAbility = abilities.includes('Technician') && !!counter['technician']; } else if (ability === 'Prankster') { rejectAbility = !counter['Status']; } else if (ability === 'Quick Feet') { rejectAbility = hasMove['bellydrum']; } else if (ability === 'Reckless' || ability === 'Rock Head') { rejectAbility = !counter['recoil']; } else if (ability === 'Sand Veil') { rejectAbility = !teamDetails['sand']; } else if (ability === 'Serene Grace') { rejectAbility = !counter['serenegrace'] || template.id === 'chansey' || template.id === 'blissey'; } else if (ability === 'Sheer Force') { rejectAbility = !counter['sheerforce'] || (abilities.includes('Iron Fist') && counter['sheerforce'] < 2 && counter['ironfist'] > counter['sheerforce']); } else if (ability === 'Simple') { rejectAbility = !counter.setupType && !hasMove['cosmicpower'] && !hasMove['flamecharge']; } else if (ability === 'Snow Cloak') { rejectAbility = !teamDetails['hail']; } else if (ability === 'Solar Power') { rejectAbility = !counter['Special'] || template.isMega; } else if (ability === 'Strong Jaw') { rejectAbility = !counter['bite']; } else if (ability === 'Sturdy') { rejectAbility = !!counter['recoil'] && !counter['recovery']; } else if (ability === 'Swift Swim') { rejectAbility = !hasMove['raindance'] && !teamDetails['rain']; } else if (ability === 'Swarm') { rejectAbility = !counter['Bug']; } else if (ability === 'Synchronize') { rejectAbility = counter.Status < 2; } else if (ability === 'Technician') { rejectAbility = !counter['technician'] || (abilities.includes('Skill Link') && counter['skilllink'] >= counter['technician']); } else if (ability === 'Tinted Lens') { rejectAbility = counter['damage'] >= counter.damagingMoves.length; } else if (ability === 'Torrent') { rejectAbility = !counter['Water']; } else if (ability === 'Unburden') { rejectAbility = !counter.setupType && !hasMove['acrobatics']; } if (rejectAbility) { if (ability === ability1.name) { // or not ability = ability0.name; } else if (ability1.rating > 1) { // only switch if the alternative doesn't suck ability = ability1.name; } } if (abilities.includes('Chlorophyll') && ability !== 'Solar Power' && hasMove['sunnyday']) { ability = 'Chlorophyll'; } if (abilities.includes('Guts') && ability !== 'Quick Feet' && (hasMove['facade'] || hasMove['protect'] || (hasMove['rest'] && hasMove['sleeptalk']))) { ability = 'Guts'; } if (abilities.includes('Liquid Voice') && hasMove['hypervoice']) { ability = 'Liquid Voice'; } if (abilities.includes('Marvel Scale') && hasMove['rest'] && hasMove['sleeptalk']) { ability = 'Marvel Scale'; } if (abilities.includes('Prankster') && counter.Status > 1) { ability = 'Prankster'; } if (abilities.includes('Swift Swim') && hasMove['raindance']) { ability = 'Swift Swim'; } if (abilities.includes('Unburden') && hasMove['acrobatics']) { ability = 'Unburden'; } if (abilities.includes('Water Bubble') && counter['Water']) { ability = 'Water Bubble'; } if (template.id === 'ambipom' && !counter['technician']) { // If it doesn't qualify for Technician, Skill Link is useless on it ability = 'Pickup'; } else if (template.id === 'aurorus' && ability === 'Snow Warning' && hasMove['hypervoice']) { for (let i = 0; i < moves.length; i++) { if (moves[i] === 'hypervoice') { moves[i] = 'blizzard'; counter['Normal'] = 0; break; } } } else if (template.baseSpecies === 'Basculin') { ability = 'Adaptability'; } else if (template.id === 'lilligant' && hasMove['petaldance']) { ability = 'Own Tempo'; } else if (template.id === 'lopunny' && hasMove['switcheroo'] && this.random(3)) { ability = 'Klutz'; } else if (template.id === 'mawilemega') { ability = 'Intimidate'; } else if (template.id === 'rampardos' && !hasMove['headsmash']) { ability = 'Sheer Force'; } else if (template.id === 'rhyperior') { ability = 'Solid Rock'; } else if (template.id === 'reuniclus') { ability = 'Magic Guard'; } else if (template.id === 'togetic' || template.id === 'unfezant') { ability = 'Super Luck'; } else if (template.id === 'venusaurmega') { ability = 'Chlorophyll'; } } if (hasMove['rockclimb'] && ability !== 'Sheer Force') { moves[moves.indexOf('rockclimb')] = 'doubleedge'; } if (hasMove['thunderpunch'] && ability === 'Galvanize') { moves[moves.indexOf('thunderpunch')] = 'return'; } item = 'Leftovers'; if (template.requiredItems) { if (template.baseSpecies === 'Arceus' && hasMove['judgment']) { // Judgment doesn't change type with Z-Crystals item = template.requiredItems[0]; } else { item = template.requiredItems[this.random(template.requiredItems.length)]; } } else if (hasMove['magikarpsrevenge']) { // PoTD Magikarp item = 'Choice Band'; } else if (template.species === 'Rotom-Fan') { // This is just to amuse Zarel item = 'Air Balloon'; // First, the extra high-priority items } else if (template.species === 'Clamperl' && !hasMove['shellsmash']) { item = 'Deep Sea Tooth'; } else if (template.species === 'Cubone' || template.baseSpecies === 'Marowak') { item = 'Thick Club'; } else if (template.species === 'Dedenne') { item = 'Petaya Berry'; } else if (template.species === 'Deoxys-Attack') { item = (slot === 0 && hasMove['stealthrock']) ? 'Focus Sash' : 'Life Orb'; } else if (template.species === 'Farfetch\'d') { item = 'Stick'; } else if (template.baseSpecies === 'Pikachu') { item = 'Light Ball'; } else if (template.species === 'Shedinja' || template.species === 'Smeargle') { item = 'Focus Sash'; } else if (template.species === 'Unfezant' && counter['Physical'] >= 2) { item = 'Scope Lens'; } else if (template.species === 'Unown') { item = 'Choice Specs'; } else if (template.species === 'Wobbuffet') { item = hasMove['destinybond'] ? 'Custap Berry' : ['Leftovers', 'Sitrus Berry'][this.random(2)]; } else if (template.species === 'Raichu-Alola' && hasMove['thunderbolt'] && !teamDetails.zMove && this.random(4) < 1) { item = 'Aloraichium Z'; } else if (ability === 'Imposter') { item = 'Choice Scarf'; } else if (ability === 'Klutz' && hasMove['switcheroo']) { // To perma-taunt a Pokemon by giving it Assault Vest item = 'Assault Vest'; } else if (hasMove['conversion']) { item = 'Normalium Z'; } else if (hasMove['geomancy']) { item = 'Power Herb'; } else if (hasMove['switcheroo'] || hasMove['trick']) { let randomNum = this.random(3); if (counter.Physical >= 3 && (template.baseStats.spe < 60 || template.baseStats.spe > 108 || randomNum)) { item = 'Choice Band'; } else if (counter.Special >= 3 && (template.baseStats.spe < 60 || template.baseStats.spe > 108 || randomNum)) { item = 'Choice Specs'; } else { item = 'Choice Scarf'; } } else if (template.evos.length) { item = (ability === 'Technician' && counter.Physical >= 4) ? 'Choice Band' : 'Eviolite'; } else if (hasMove['bellydrum']) { if (ability === 'Gluttony') { item = ['Aguav', 'Figy', 'Iapapa', 'Mago', 'Wiki'][this.random(5)] + ' Berry'; } else if (template.baseStats.spe <= 50 && !teamDetails.zMove && this.random(2)) { item = 'Normalium Z'; } else { item = 'Sitrus Berry'; } } else if (hasMove['shellsmash']) { item = (ability === 'Solid Rock' && counter['priority']) ? 'Weakness Policy' : 'White Herb'; } else if (ability === 'Harvest') { item = hasMove['rest'] ? 'Lum Berry' : 'Sitrus Berry'; } else if (ability === 'Magic Guard' || ability === 'Sheer Force') { item = hasMove['psychoshift'] ? 'Flame Orb' : 'Life Orb'; } else if (ability === 'Poison Heal' || ability === 'Toxic Boost') { item = 'Toxic Orb'; } else if (hasMove['rest'] && !hasMove['sleeptalk'] && ability !== 'Natural Cure' && ability !== 'Shed Skin') { item = (hasMove['raindance'] && ability === 'Hydration') ? 'Damp Rock' : 'Chesto Berry'; } else if (hasMove['raindance']) { item = (ability === 'Swift Swim' && counter.Status < 2) ? 'Life Orb' : 'Damp Rock'; } else if (hasMove['sunnyday']) { item = (ability === 'Chlorophyll' && counter.Status < 2) ? 'Life Orb' : 'Heat Rock'; } else if ((hasMove['lightscreen'] && hasMove['reflect']) || hasMove['auroraveil']) { item = 'Light Clay'; } else if ((ability === 'Guts' || hasMove['facade']) && !hasMove['sleeptalk']) { item = (hasType['Fire'] || ability === 'Quick Feet') ? 'Toxic Orb' : 'Flame Orb'; } else if (ability === 'Unburden') { if (hasMove['fakeout']) { item = 'Normal Gem'; } else { item = 'Sitrus Berry'; } } else if (hasMove['acrobatics']) { item = ''; // Medium priority } else if (((ability === 'Speed Boost' && !hasMove['substitute']) || (ability === 'Stance Change')) && counter.Physical + counter.Special > 2) { item = 'Life Orb'; } else if (template.baseStats.spe <= 50 && hasMove['sleeppowder'] && counter.setupType && !teamDetails.zMove) { item = 'Grassium Z'; } else if (counter.Physical >= 4 && !hasMove['bodyslam'] && !hasMove['dragontail'] && !hasMove['fakeout'] && !hasMove['flamecharge'] && !hasMove['rapidspin'] && !hasMove['suckerpunch']) { item = template.baseStats.atk >= 100 && template.baseStats.spe >= 60 && template.baseStats.spe <= 108 && !counter['priority'] && this.random(3) ? 'Choice Scarf' : 'Choice Band'; } else if (counter.Special >= 4 && !hasMove['acidspray'] && !hasMove['chargebeam'] && !hasMove['clearsmog'] && !hasMove['fierydance']) { item = template.baseStats.spa >= 100 && template.baseStats.spe >= 60 && template.baseStats.spe <= 108 && !counter['priority'] && this.random(3) ? 'Choice Scarf' : 'Choice Specs'; } else if (counter.Special >= 3 && hasMove['uturn'] && template.baseStats.spe >= 60 && template.baseStats.spe <= 108 && !counter['priority'] && this.random(3)) { item = 'Choice Scarf'; } else if (ability === 'Defeatist' || hasMove['eruption'] || hasMove['waterspout']) { item = counter.Status <= 1 ? 'Expert Belt' : 'Leftovers'; } else if ((hasMove['endeavor'] || hasMove['flail'] || hasMove['reversal']) && ability !== 'Sturdy') { item = 'Focus Sash'; } else if (this.getEffectiveness('Ground', template) >= 2 && ability !== 'Levitate' && !hasMove['magnetrise']) { item = 'Air Balloon'; } else if (hasMove['outrage'] && (counter.setupType || ability === 'Multiscale')) { item = 'Lum Berry'; } else if (ability === 'Slow Start' || hasMove['clearsmog'] || hasMove['curse'] || hasMove['detect'] || hasMove['protect'] || hasMove['sleeptalk']) { item = 'Leftovers'; } else if (hasMove['substitute']) { item = !counter['drain'] || counter.damagingMoves.length < 2 ? 'Leftovers' : 'Life Orb'; } else if (hasMove['lightscreen'] || hasMove['reflect']) { item = 'Light Clay'; } else if (ability === 'Iron Barbs' || ability === 'Rough Skin') { item = 'Rocky Helmet'; } else if (counter.Physical + counter.Special >= 4 && template.baseStats.spd >= 65 && (template.baseStats.def + template.baseStats.spd >= 190 || hasMove['rapidspin'])) { item = 'Assault Vest'; } else if (counter.damagingMoves.length >= 4) { item = (!!counter['Normal'] || (hasMove['suckerpunch'] && !hasType['Dark'])) ? 'Life Orb' : 'Expert Belt'; } else if (counter.damagingMoves.length >= 3 && !!counter['speedsetup'] && template.baseStats.hp + template.baseStats.def + template.baseStats.spd >= 300) { item = 'Weakness Policy'; } else if (counter.damagingMoves.length >= 3 && ability !== 'Sturdy' && !hasMove['clearsmog'] && !hasMove['dragontail'] && !hasMove['foulplay'] && !hasMove['superfang']) { item = (template.baseStats.hp + template.baseStats.def + template.baseStats.spd < 285 || !!counter['speedsetup'] || hasMove['trickroom']) ? 'Life Orb' : 'Leftovers'; } else if (template.species === 'Palkia' && (hasMove['dracometeor'] || hasMove['spacialrend']) && hasMove['hydropump']) { item = 'Lustrous Orb'; } else if (slot === 0 && ability !== 'Regenerator' && ability !== 'Sturdy' && !counter['recoil'] && !counter['recovery'] && template.baseStats.hp + template.baseStats.def + template.baseStats.spd < 285) { 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 (ability === 'Gale Wings' && hasMove['bravebird']) { item = !teamDetails.zMove ? 'Flyinium Z' : 'Sharp Beak'; } 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'] && !hasMove['sleeptalk']) { item = 'Air Balloon'; } else if (counter.Status <= 1 && ability !== 'Sturdy' && !hasMove['rapidspin']) { item = 'Life Orb'; } else { item = 'Leftovers'; } // For Trick / Switcheroo if (item === 'Leftovers' && hasType['Poison']) { item = 'Black Sludge'; } let levelScale = { LC: 87, 'LC Uber': 86, NFE: 84, PU: 83, BL4: 82, NU: 81, BL3: 80, RU: 79, BL2: 78, UU: 77, BL: 76, OU: 75, Uber: 73, AG: 71, }; let customScale = { // Banned Abilities Gothitelle: 77, Politoed: 79, Wobbuffet: 77, // Holistic judgement Unown: 100, }; let tier = template.tier; if (tier.includes('Unreleased') && baseTemplate.tier === 'Uber') { tier = 'Uber'; } if (tier.charAt(0) === '(') { tier = tier.slice(1, -1); } let level = levelScale[tier] || 75; if (customScale[template.name]) level = customScale[template.name]; // Custom level based on moveset if (ability === 'Power Construct') level = 73; if (hasMove['batonpass'] && counter.setupType && level > 77) level = 77; // if (template.name === 'Slurpuff' && !counter.setupType) level = 81; // if (template.name === 'Xerneas' && hasMove['geomancy']) level = 71; // Prepare optimal HP let hp = Math.floor(Math.floor(2 * template.baseStats.hp + ivs.hp + Math.floor(evs.hp / 4) + 100) * level / 100 + 10); if (hasMove['substitute'] && (item === 'Sitrus Berry' || (ability === 'Power Construct' && item !== 'Leftovers'))) { // Two Substitutes should activate Sitrus Berry or Power Construct while (hp % 4 > 0) { evs.hp -= 4; hp = Math.floor(Math.floor(2 * template.baseStats.hp + ivs.hp + Math.floor(evs.hp / 4) + 100) * level / 100 + 10); } } else if (hasMove['bellydrum'] && (item === 'Sitrus Berry' || ability === 'Gluttony')) { // Belly Drum should activate Sitrus Berry if (hp % 2 > 0) evs.hp -= 4; } else if (hasMove['substitute'] && hasMove['reversal']) { // Reversal users should be able to use four Substitutes if (hp % 4 === 0) evs.hp -= 4; } else { // Maximize number of Stealth Rock switch-ins let srWeakness = this.getEffectiveness('Rock', template); if (srWeakness > 0 && hp % (4 / srWeakness) === 0) evs.hp -= 4; } // Minimize confusion damage if (!counter['Physical'] && !hasMove['copycat'] && !hasMove['transform']) { evs.atk = 0; ivs.atk = 0; } if (hasMove['gyroball'] || hasMove['trickroom']) { evs.spe = 0; ivs.spe = 0; } return { name: template.baseSpecies, species: species, moves: moves, ability: ability, evs: evs, ivs: ivs, item: item, level: level, shiny: !this.random(1024), }; }, randomTeam: function (side) { let pokemon = []; let excludedTiers = {'NFE':1, 'LC Uber':1, 'LC':1}; let allowedNFE = {'Chansey':1, 'Doublade':1, 'Gligar':1, 'Porygon2':1, 'Scyther':1, 'Togetic':1}; // For Monotype let isMonotype = this.format === 'gen7monotyperandombattle'; let typePool = Object.keys(this.data.TypeChart); let type = typePool[this.random(typePool.length)]; let pokemonPool = []; for (let id in this.data.FormatsData) { let template = this.getTemplate(id); if (isMonotype) { let types = template.types; if (template.battleOnly) types = this.getTemplate(template.baseSpecies).types; if (types.indexOf(type) < 0) continue; } if (template.gen <= this.gen && !excludedTiers[template.tier] && !template.isMega && !template.isPrimal && !template.isNonstandard && template.randomBattleMoves) { pokemonPool.push(id); } } // PotD stuff let potd; if (Config.potd && 'Rule:potd' in this.getBanlistTable(this.getFormat())) { potd = this.getTemplate(Config.potd); } let typeCount = {}; let typeComboCount = {}; let baseFormes = {}; let uberCount = 0; let puCount = 0; let teamDetails = {}; while (pokemonPool.length && pokemon.length < 6) { let template = this.getTemplate(this.sampleNoReplace(pokemonPool)); if (!template.exists) continue; // Limit to one of each species (Species Clause) if (baseFormes[template.baseSpecies]) continue; // Only certain NFE Pokemon are allowed if (template.evos.length && !allowedNFE[template.species]) continue; let tier = template.tier; switch (tier) { 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 '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 'Unreleased': case 'CAP': // Unreleased and CAP have 20% the normal rate if (this.random(5) >= 1) continue; } // Adjust rate for species with multiple formes switch (template.baseSpecies) { case 'Arceus': case 'Silvally': if (this.random(18) >= 1) continue; break; case 'Pikachu': if (this.random(7) >= 1) continue; continue; case 'Genesect': if (this.random(5) >= 1) continue; break; case 'Castform': case 'Gourgeist': case 'Oricorio': if (this.random(4) >= 1) continue; break; case 'Basculin': case 'Cherrim': case 'Greninja': case 'Hoopa': case 'Meloetta': case 'Meowstic': if (this.random(2) >= 1) continue; break; } 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 = ['bounce', 'flail', 'splash', 'magikarpsrevenge']; } else if (template.species === 'Delibird') { template.randomBattleMoves = ['present', 'bestow']; } } else if (template.species === potd.species) { continue; // No thanks, I've already got one } } let types = template.types; if (!isMonotype) { // Limit 2 of any type let skip = false; for (let t = 0; t < types.length; t++) { if (typeCount[types[t]] > 1 && this.random(5) >= 1) { skip = true; break; } } if (skip) continue; } let set = this[this.gameType === 'singles' ? 'randomSet' : 'randomDoublesSet'](template, pokemon.length, teamDetails); // Illusion shouldn't be the last Pokemon of the team if (set.ability === 'Illusion' && pokemon.length > 4) continue; // Pokemon shouldn't have Physical and Special setup on the same set let incompatibleMoves = ['bellydrum', 'swordsdance', 'calmmind', 'nastyplot']; let intersectMoves = set.moves.filter(move => incompatibleMoves.includes(move)); if (intersectMoves.length > 1) continue; // Limit 1 of any type combination, 2 in monotype let typeCombo = types.slice().sort().join(); if (set.ability === 'Drought' || set.ability === 'Drizzle' || set.ability === 'Sand Stream') { // Drought, Drizzle and Sand Stream don't count towards the type combo limit typeCombo = set.ability; if (typeCombo in typeComboCount) continue; } else { if (typeComboCount[typeCombo] >= (isMonotype ? 2 : 1)) 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 baseFormes[template.baseSpecies] = 1; // Increment type counters for (let t = 0; t < types.length; t++) { if (types[t] in typeCount) { typeCount[types[t]]++; } else { typeCount[types[t]] = 1; } } if (typeCombo in typeComboCount) { typeComboCount[typeCombo]++; } else { typeComboCount[typeCombo] = 1; } // Increment Uber/PU counters if (tier === 'Uber') { uberCount++; } else if (tier === 'PU') { puCount++; } // Team has Mega/weather/hazards let item = this.getItem(set.item); if (item.megaStone) teamDetails['megaStone'] = 1; if (item.zMove) teamDetails['zMove'] = 1; if (set.ability === 'Snow Warning') teamDetails['hail'] = 1; if (set.ability === 'Drizzle' || set.moves.includes('raindance')) teamDetails['rain'] = 1; if (set.ability === 'Sand Stream') teamDetails['sand'] = 1; if (set.moves.includes('stealthrock')) teamDetails['stealthRock'] = 1; if (set.moves.includes('toxicspikes')) teamDetails['toxicSpikes'] = 1; if (set.moves.includes('defog') || set.moves.includes('rapidspin')) teamDetails['hazardClear'] = 1; } return pokemon; }, randomDoublesSet: function (template, slot, teamDetails) { let baseTemplate = (template = this.getTemplate(template)); let species = template.species; if (!template.exists || (!template.randomDoubleBattleMoves && !template.randomBattleMoves && !template.learnset)) { template = this.getTemplate('unown'); let err = new Error('Template incompatible with random battles: ' + species); require('../crashlogger')(err, 'The doubles randbat set generator'); } if (typeof teamDetails !== 'object') teamDetails = {megaStone: teamDetails}; if (template.battleOnly) { // Only change the species. The template has custom moves, and may have different typing and requirements. species = template.baseSpecies; } let battleForme = this.checkBattleForme(template); if (battleForme && (battleForme.isMega ? !teamDetails.megaStone : this.random(2))) { template = this.getTemplate(template.otherFormes.length >= 2 ? template.otherFormes[this.random(template.otherFormes.length)] : template.otherFormes[0]); } let movePool = (template.randomDoubleBattleMoves || template.randomBattleMoves); movePool = movePool ? movePool.slice() : Object.keys(template.learnset); let moves = []; let ability = ''; let item = ''; let evs = { hp: 85, atk: 85, def: 85, spa: 85, spd: 85, spe: 85, }; let ivs = { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31, }; let hasType = {}; hasType[template.types[0]] = true; if (template.types[1]) { hasType[template.types[1]] = true; } let hasAbility = {}; hasAbility[template.abilities[0]] = true; if (template.abilities[1]) { hasAbility[template.abilities[1]] = true; } if (template.abilities['H']) { hasAbility[template.abilities['H']] = true; } let availableHP = 0; for (let i = 0, len = movePool.length; i < len; i++) { if (movePool[i].substr(0, 11) === 'hiddenpower') availableHP++; } // These moves can be used even if we aren't setting up to use them: let SetupException = { dracometeor:1, leafstorm:1, overheat:1, extremespeed:1, suckerpunch:1, superpower:1, }; let counterAbilities = { 'Adaptability':1, 'Contrary':1, 'Hustle':1, 'Iron Fist':1, 'Skill Link':1, }; // -ate Abilities let ateAbilities = { 'Aerilate':1, 'Pixilate':1, 'Refrigerate':1, }; let hasMove, counter; do { // Keep track of all moves we have: hasMove = {}; for (let 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) { let 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); } counter = this.queryMoves(moves, hasType, hasAbility); // Iterate through the moves again, this time to cull them: for (let k = 0; k < moves.length; k++) { let move = this.getMove(moves[k]); let moveid = move.id; let rejected = false; let 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'] && !counter.setupType) rejected = true; break; case 'batonpass': if (!counter.setupType && !hasMove['substitute'] && !hasMove['cosmicpower'] && !counter['speedsetup'] && !hasAbility['Speed Boost']) 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 (counter.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 (counter.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 (counter.setupType !== 'Mixed' || counter['mixedsetup'] > 1) rejected = true; isSetup = true; break; // bad after setup case 'seismictoss': case 'nightshade': case 'superfang': if (counter.setupType) rejected = true; break; case 'rapidspin': case 'magiccoat': case 'spikes': case 'toxicspikes': if (counter.setupType) rejected = true; break; case 'uturn': case 'voltswitch': if (counter.setupType || hasMove['agility'] || hasMove['rockpolish'] || hasMove['magnetrise']) rejected = true; break; case 'perishsong': if (hasMove['roar'] || hasMove['whirlwind'] || hasMove['haze']) rejected = true; if (counter.setupType || hasMove['agility'] || hasMove['rockpolish'] || hasMove['magnetrise']) rejected = true; break; case 'relicsong': if (counter.setupType) rejected = true; break; case 'pursuit': case 'protect': case 'haze': case 'stealthrock': if (counter.setupType || (hasMove['rest'] && hasMove['sleeptalk'])) rejected = true; break; case 'trick': case 'switcheroo': if (counter.setupType || counter.Physical + counter.Special < 2) rejected = true; if ((hasMove['rest'] && hasMove['sleeptalk']) || hasMove['trickroom'] || hasMove['reflect'] || hasMove['lightscreen'] || hasMove['acrobatics']) 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 (counter.setupType === 'Special' || hasMove['fireblast']) rejected = true; break; case 'icebeam': if (hasMove['blizzard'] || hasMove['freezedry']) rejected = true; break; case 'iceshard': if (hasMove['freezedry']) rejected = true; break; case 'surf': if (hasMove['scald'] || hasMove['hydropump'] || hasMove['muddywater']) rejected = true; break; case 'hydropump': if (hasMove['razorshell'] || hasMove['waterfall'] || 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 'lunge': if (hasMove['leechlife']) rejected = true; break; case 'solarbeam': if ((!hasMove['sunnyday'] && !hasAbility['Drought']) || hasMove['gigadrain'] || hasMove['leafstorm']) rejected = true; break; case 'gigadrain': if ((!counter.setupType && hasMove['leafstorm']) || hasMove['petaldance']) rejected = true; break; case 'leafstorm': if (counter.setupType && hasMove['gigadrain']) rejected = true; break; case 'seedbomb': case 'woodhammer': if (hasMove['gigadrain']) rejected = true; break; case 'weatherball': if (!hasMove['sunnyday']) rejected = true; break; case 'firepunch': if (hasMove['flareblitz'] || hasMove['fireblast']) rejected = true; break; case 'crosschop': case 'highjumpkick': if (hasMove['closecombat']) rejected = true; break; case 'drainpunch': if (hasMove['closecombat'] || hasMove['crosschop']) rejected = true; break; case 'machpunch': if (hasType['Fighting'] && counter.stab < 2 && !hasAbility['Technician']) 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'] || hasMove['hyperspacehole']) rejected = true; break; case 'fusionbolt': if (counter.setupType && hasMove['boltstrike']) rejected = true; break; case 'boltstrike': if (!counter.setupType && hasMove['fusionbolt']) rejected = true; break; case 'darkpulse': if (hasMove['crunch'] && counter.setupType !== 'Special') rejected = true; break; case 'quickattack': if (hasMove['feint']) rejected = true; break; case 'bulletpunch': if (hasType['Steel'] && counter.stab < 2 && !hasAbility['Technician']) rejected = true; break; case 'flashcannon': if (hasMove['ironhead']) 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 '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']) rejected = true; break; case 'feint': if (hasMove['fakeout']) 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 (!counter.setupType && !hasMove['batonpass'] && hasMove['thunderwave']) rejected = true; if ((hasMove['stealthrock'] || hasMove['spikes'] || hasMove['toxicspikes']) && !hasMove['batonpass']) rejected = true; break; case 'thunderwave': if (counter.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'] || hasMove['icywind']) 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; // Z-status case 'hypnosis': if ((teamDetails.zMove || !counter.setupType) && template.baseStats.spe < 100) 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' && counter.setupType === 'Physical' && !SetupException[moveid]) { rejected = true; } if (move.category === 'Physical' && (counter.setupType === 'Special' || hasMove['acidspray']) && !SetupException[moveid]) { rejected = true; } // This move doesn't satisfy our setup requirements: if (counter.setupType === 'Physical' && move.category !== 'Physical' && counter['Physical'] < 2) { rejected = true; } if (counter.setupType === 'Special' && move.category !== 'Special' && counter['Special'] < 2) { rejected = true; } // Hidden Power isn't good enough if (counter.setupType === 'Special' && moveid === 'hiddenpower' && counter['Special'] <= 2 && (!hasMove['shadowball'] || move.type !== 'Fighting')) { rejected = true; } // Pokemon should have moves that benefit their Ability/Type/Weather, as well as moves required by its forme if ((hasType['Bug'] && !hasMove['batonpass'] && (movePool.includes('megahorn') || movePool.includes('pinmissile') || (hasType['Flying'] && !hasMove['hurricane'] && movePool.includes('bugbuzz')))) || (hasType['Dark'] && hasMove['suckerpunch'] && counter.stab < template.types.length) || (hasType['Dragon'] && !counter['Dragon'] && !hasAbility['Aerilate'] && !hasAbility['Pixilate'] && !hasMove['rest'] && !hasMove['sleeptalk']) || (hasType['Electric'] && !counter['Electric']) || (hasType['Fighting'] && !counter['Fighting'] && (counter.setupType || !counter['Status'])) || (hasType['Fire'] && !counter['Fire']) || (hasType['Ground'] && !counter['Ground'] && (counter.setupType || counter['speedsetup'] || hasMove['raindance'] || !counter['Status'])) || (hasType['Ice'] && !counter['Ice'] && !hasAbility['Refrigerate']) || (hasType['Psychic'] && !!counter['Psychic'] && !hasType['Flying'] && !hasAbility['Pixilate'] && template.types.length > 1 && counter.stab < 2) || (hasType['Water'] && !counter['Water'] && (!hasType['Ice'] || !counter['Ice']) && !hasAbility['Protean']) || ((hasAbility['Adaptability'] && !counter.setupType && template.types.length > 1 && (!counter[template.types[0]] || !counter[template.types[1]])) || ((hasAbility['Aerilate'] || hasAbility['Pixilate'] || hasAbility['Refrigerate']) && !counter['Normal']) || (hasAbility['Contrary'] && !counter['contrary'] && template.species !== 'Shuckle') || (hasAbility['Dark Aura'] && !counter['Dark']) || (hasAbility['Electric Surge'] && !counter['Electric']) || (hasAbility['Gale Wings'] && !counter['Flying']) || (hasAbility['Grassy Surge'] && !counter['Grass']) || (hasAbility['Guts'] && hasType['Normal'] && movePool.includes('facade')) || (hasAbility['Psychic Surge'] && !counter['Psychic']) || (hasAbility['Slow Start'] && movePool.includes('substitute')) || (hasAbility['Stance Change'] && !counter.setupType && movePool.includes('kingsshield')) || (hasAbility['Water Bubble'] && !counter['Water']) || (counter['defensesetup'] && !counter.recovery && !hasMove['rest']) || (movePool.includes('technoblast') || template.requiredMove && movePool.includes(toId(template.requiredMove)))) && (counter['physicalsetup'] + counter['specialsetup'] < 2 && (!counter.setupType || counter.setupType === 'Mixed' || (move.category !== counter.setupType && move.category !== 'Status') || counter[counter.setupType] + counter.Status > 3))) { // Reject Status or non-STAB if (!isSetup && !move.weather && moveid !== 'judgment' && moveid !== 'rest' && moveid !== 'sleeptalk') { if (move.category === 'Status' || !hasType[move.type] || (move.basePower && move.basePower < 40 && !move.multihit)) rejected = true; } } // Remove rejected moves from the move list. if (rejected && (movePool.length - availableHP || availableHP && (moveid === 'hiddenpower' || !hasMove['hiddenpower']))) { moves.splice(k, 1); break; } } if (moves.length === 4 && !counter.stab && !hasMove['metalburst'] && (counter['physicalpool'] || counter['specialpool'])) { // Move post-processing: if (counter.damagingMoves.length === 0) { // A set shouldn't have no attacking moves moves.splice(this.random(moves.length), 1); } else if (counter.damagingMoves.length === 1) { // In most cases, a set shouldn't have no STAB let damagingid = counter.damagingMoves[0].id; if (movePool.length - availableHP || availableHP && (damagingid === 'hiddenpower' || !hasMove['hiddenpower'])) { let replace = false; if (!counter.damagingMoves[0].damage && template.species !== 'Porygon2') { let damagingType = counter.damagingMoves[0].type; if (damagingType === 'Fairy') { // Mono-Fairy is acceptable for Psychic types if (counter.setupType !== 'Special' || template.types.length > 1 || !hasType['Psychic']) replace = true; } else { replace = true; } } if (replace) moves.splice(counter.damagingMoveIndex[damagingid], 1); } } else if (!counter.damagingMoves[0].damage && !counter.damagingMoves[1].damage && template.species !== 'Porygon2') { // If you have three or more attacks, and none of them are STAB, reject one of them at random. let rejectableMoves = []; let baseDiff = movePool.length - availableHP; for (let l = 0; l < counter.damagingMoves.length; l++) { if (counter.damagingMoves[l].id === 'technoblast') continue; if (baseDiff || availableHP && (!hasMove['hiddenpower'] || counter.damagingMoves[l].id === 'hiddenpower')) { rejectableMoves.push(counter.damagingMoveIndex[counter.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'; let changedMove = false; if (template.requiredItem && template.requiredItem.slice(-5) === 'Drive' && !hasMove['technoblast']) { delete hasMove[this.getMove(moves[3]).id]; moves[3] = 'technoblast'; hasMove['technoblast'] = true; changedMove = true; } if (template.id === 'meloettapirouette' && !hasMove['relicsong']) { delete hasMove[this.getMove(moves[3]).id]; moves[3] = 'relicsong'; hasMove['relicsong'] = true; changedMove = 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; changedMove = true; } // Re-query in case a moveset modification occurred if (changedMove) counter = this.queryMoves(moves, hasType, hasAbility); let abilities = Object.values(baseTemplate.abilities); abilities.sort((a, b) => this.getAbility(b).rating - this.getAbility(a).rating); let ability0 = this.getAbility(abilities[0]); let ability1 = this.getAbility(abilities[1]); let ability2 = this.getAbility(abilities[2]); ability = ability0.name; if (abilities[1]) { if (abilities[2] && ability2.rating === ability1.rating) { if (this.random(2)) ability1 = ability2; } 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; } let rejectAbility = false; if (ability in counterAbilities) { rejectAbility = !counter[toId(ability)]; } else if (ability in ateAbilities) { rejectAbility = !counter['Normal']; } else if (ability === 'Blaze') { rejectAbility = !counter['Fire']; } else if (ability === 'Chlorophyll') { rejectAbility = !hasMove['sunnyday']; } else if (ability === 'Compound Eyes' || ability === 'No Guard') { rejectAbility = !counter['inaccurate']; } else if (ability === 'Defiant' || ability === 'Moxie') { rejectAbility = !counter['Physical'] && !hasMove['batonpass']; } else if (ability === 'Limber') { rejectAbility = template.types.includes('Electric'); } else if (ability === 'Lightning Rod') { rejectAbility = template.types.includes('Ground'); } else if (ability === 'Liquid Voice') { rejectAbility = !hasMove['hypervoice']; } else if (ability === 'Moody') { rejectAbility = template.id !== 'bidoof'; } else if (ability === 'Overgrow') { rejectAbility = !counter['Grass']; } else if (ability === 'Poison Heal') { rejectAbility = abilities.includes('Technician') && !!counter['technician']; } else if (ability === 'Prankster') { rejectAbility = !counter['Status']; } else if (ability === 'Reckless' || ability === 'Rock Head') { rejectAbility = !counter['recoil']; } else if (ability === 'Serene Grace') { rejectAbility = !counter['serenegrace'] || template.id === 'chansey' || template.id === 'blissey'; } else if (ability === 'Sheer Force') { rejectAbility = !counter['sheerforce'] || hasMove['fakeout'] || (abilities.includes('Iron Fist') && counter['sheerforce'] < 2 && counter['ironfist'] > counter['sheerforce']); } else if (ability === 'Simple') { rejectAbility = !counter.setupType && !hasMove['cosmicpower'] && !hasMove['flamecharge']; } else if (ability === 'Snow Cloak') { rejectAbility = !teamDetails['hail']; } else if (ability === 'Solar Power') { rejectAbility = !counter['Special'] || template.isMega; } else if (ability === 'Strong Jaw') { rejectAbility = !counter['bite']; } else if (ability === 'Sturdy') { rejectAbility = !!counter['recoil'] && !counter['recovery']; } else if (ability === 'Swift Swim') { rejectAbility = !hasMove['raindance'] && !teamDetails['rain']; } else if (ability === 'Swarm') { rejectAbility = !counter['Bug']; } else if (ability === 'Synchronize') { rejectAbility = counter.Status < 2; } else if (ability === 'Technician') { rejectAbility = !counter['technician'] || (abilities.includes('Skill Link') && counter['skilllink'] >= counter['technician']); } else if (ability === 'Tinted Lens') { rejectAbility = counter['damage'] >= counter.damagingMoves.length; } else if (ability === 'Torrent') { rejectAbility = !counter['Water']; } else if (ability === 'Unburden') { rejectAbility = template.baseStats.spe > 120 || (template.id === 'slurpuff' && !counter.setupType); } else if (ability === 'Quick Feet') { rejectAbility = hasMove['bellydrum'] && abilities.includes('Gluttony'); } 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.includes('Chlorophyll') && ability !== 'Solar Power') { ability = 'Chlorophyll'; } if (abilities.includes('Guts') && ability !== 'Quick Feet' && hasMove['facade']) { ability = 'Guts'; } if (abilities.includes('Intimidate') || template.id === 'mawilemega') { ability = 'Intimidate'; } if (abilities.includes('Liquid Voice') && hasMove['hypervoice']) { ability = 'Liquid Voice'; } if (abilities.includes('Swift Swim') && hasMove['raindance']) { ability = 'Swift Swim'; } if (abilities.includes('Water Bubble') && counter['Water']) { ability = 'Water Bubble'; } if (template.id === 'ambipom' && !counter['technician']) { // If it doesn't qualify for Technician, Skill Link is useless on it // Might as well give it Pickup just in case ability = 'Pickup'; } else if (template.id === 'aurorus' && ability === 'Snow Warning' && hasMove['hypervoice']) { for (let i = 0; i < moves.length; i++) { if (moves[i] === 'hypervoice') { moves[i] = 'blizzard'; counter['Normal'] = 0; break; } } } else if (template.baseSpecies === 'Basculin') { ability = 'Adaptability'; } else if (template.id === 'gligar') { ability = 'Immunity'; } else if (template.id === 'lilligant' && hasMove['petaldance']) { ability = 'Own Tempo'; } else if (template.id === 'rampardos' && !hasMove['headsmash']) { ability = 'Sheer Force'; } else if (template.id === 'rhyperior') { ability = 'Solid Rock'; } else if (template.id === 'unfezant') { ability = 'Super Luck'; } else if (template.id === 'venusaurmega') { ability = 'Chlorophyll'; } } // check IVs/EVs for Gyro Ball and TR if (hasMove['gyroball'] || hasMove['trickroom']) { ivs.spe = 0; evs.spe = 0; } item = 'Sitrus Berry'; // First, the extra high-priority items if (template.requiredItems) { if (template.baseSpecies === 'Arceus' && hasMove['judgment'] && !teamDetails.zMove) { // Judgment doesn't change type with Z-Crystals item = template.requiredItems[0]; } else { item = template.requiredItems[this.random(template.requiredItems.length)]; } } 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['switcheroo']) { let 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 (ability === 'Gluttony' || ability === 'Schooling') { item = ['Aguav', 'Figy', 'Iapapa', 'Mago', 'Wiki'][this.random(5)] + ' Berry'; } else if (hasMove['bellydrum']) { if (template.baseStats.spe <= 50 && !teamDetails.zMove && this.random(2)) { item = 'Normalium Z'; } else { item = 'Sitrus Berry'; } } else if (hasMove['hypnosis'] && (!teamDetails.zMove && counter.setupType && template.baseStats.spe < 100)) { item = 'Psychium Z'; } 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 = 'Deep Sea Tooth'; } 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 (template.species === 'Wobbuffet') { item = hasMove['destinybond'] ? 'Custap Berry' : 'Sitrus Berry'; } else if (template.species === 'Raichu-Alola' && hasMove['thunderbolt'] && !teamDetails.zMove && this.random(4) < 1) { item = 'Aloraichium Z'; } 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']) || hasMove['auroraveil']) { item = 'Light Clay'; } else if (hasMove['shellsmash']) { item = (ability === 'Solid Rock' && counter['priority']) ? 'Weakness Policy' : '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 (ability === 'Unburden') { if (hasMove['fakeout']) { item = 'Normal Gem'; } else { item = 'Sitrus Berry'; } } else if (hasMove['acrobatics']) { item = ''; // medium priority } else if (ability === 'Guts') { item = hasType['Fire'] ? 'Toxic Orb' : 'Flame Orb'; } else if (ability === 'Marvel Scale') { item = 'Flame Orb'; } else if (counter.Physical >= 4 && template.baseStats.spe > 55 && !hasMove['fakeout'] && !hasMove['suckerpunch'] && !hasMove['flamecharge'] && !hasMove['rapidspin'] && ability !== 'Sturdy' && ability !== 'Multiscale') { item = 'Life Orb'; } else if (counter.Special >= 4 && template.baseStats.spe > 55 && !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 = 'Air Balloon'; } 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 (counter.Physical + counter.Special >= 4 && ability === 'Regenerator' && template.baseStats[counter.Special >= 2 ? 'atk' : 'spa'] > 99 && template.baseStats.spe <= 80) { item = 'Assault Vest'; } 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 && counter.setupType && ability !== 'Sturdy' && ability !== 'Multiscale') { item = 'Life Orb'; } else if (counter.Special >= 3 && counter.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.damagingMoves.length >= 3 && !!counter['speedsetup'] && template.baseStats.hp + template.baseStats.def + template.baseStats.spd >= 300) { item = 'Weakness Policy'; } 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 = 'Air Balloon'; } 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 (ability === 'Gale Wings' && hasMove['bravebird']) { item = !teamDetails.zMove ? 'Flyinium Z' : 'Sharp Beak'; } 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.34 BST adds a level from 70 up to 99. Results are floored. Uses the Mega's stats if holding a Mega Stone let baseStats = template.baseStats; // If Wishiwashi, use the school-forme's much higher stats if (template.baseSpecies === 'Wishiwashi') baseStats = Dex.getTemplate('wishiwashischool').baseStats; let bst = baseStats.hp + baseStats.atk + baseStats.def + baseStats.spa + baseStats.spd + baseStats.spe; // Adjust levels of mons based on abilities (Pure Power, Sheer Force, etc.) and also Eviolite // For the stat boosted, treat the Pokemon's base stat as if it were multiplied by the boost. (Actual effective base stats are higher.) let templateAbility = (baseTemplate === template ? ability : template.abilities[0]); if (templateAbility === 'Huge Power' || templateAbility === 'Pure Power') { bst += baseStats.atk; } else if (templateAbility === 'Parental Bond') { bst += 0.25 * (counter.Physical > counter.Special ? baseStats.atk : baseStats.spa); } else if (templateAbility === 'Protean') { bst += 0.3 * (counter.Physical > counter.Special ? baseStats.atk : baseStats.spa); } else if (templateAbility === 'Fur Coat') { bst += baseStats.def; } else if (templateAbility === 'Slow Start') { bst -= baseStats.atk / 2 + baseStats.spe / 2; } else if (templateAbility === 'Truant') { bst *= 2 / 3; } if (item === 'Eviolite') { bst += 0.5 * (baseStats.def + baseStats.spd); } let level = 70 + Math.floor(((600 - this.clampIntRange(bst, 300, 600)) / 10.34)); return { name: template.baseSpecies, species: species, moves: moves, ability: ability, evs: evs, ivs: ivs, item: item, level: level, shiny: !this.random(template.id === 'missingno' ? 4 : 1024), }; }, randomFactorySets: require('./factory-sets.json'), randomFactorySet: function (template, slot, teamData, tier) { let speciesId = toId(template.species); // let flags = this.randomFactorySets[tier][speciesId].flags; let setList = this.randomFactorySets[tier][speciesId].sets; let effectivePool, priorityPool; let itemsMax = {'choicespecs':1, 'choiceband':1, 'choicescarf':1}; let movesMax = {'rapidspin':1, 'batonpass':1, 'stealthrock':1, 'defog':1, 'spikes':1, 'toxicspikes':1}; let requiredMoves = {'stealthrock': 'hazardSet', 'rapidspin': 'hazardClear', 'defog': 'hazardClear'}; let weatherAbilitiesRequire = { 'hydration': 'raindance', 'swiftswim': 'raindance', 'leafguard': 'sunnyday', 'solarpower': 'sunnyday', 'chlorophyll': 'sunnyday', 'sandforce': 'sandstorm', 'sandrush': 'sandstorm', 'sandveil': 'sandstorm', 'snowcloak': 'hail', }; let weatherAbilitiesSet = {'drizzle':1, 'drought':1, 'snowwarning':1, 'sandstream':1}; // Build a pool of eligible sets, given the team partners // Also keep track of sets with moves the team requires effectivePool = []; priorityPool = []; for (let i = 0, l = setList.length; i < l; i++) { let curSet = setList[i]; let itemData = this.getItem(curSet.item); if (teamData.megaCount > 0 && itemData.megaStone) continue; // reject 2+ mega stones if (itemsMax[itemData.id] && teamData.has[itemData.id] >= itemsMax[itemData.id]) continue; let abilityData = this.getAbility(curSet.ability); if (weatherAbilitiesRequire[abilityData.id] && teamData.weather !== weatherAbilitiesRequire[abilityData.id]) continue; if (teamData.weather && weatherAbilitiesSet[abilityData.id]) continue; // reject 2+ weather setters let reject = false; let hasRequiredMove = false; let curSetVariants = []; for (let j = 0, m = curSet.moves.length; j < m; j++) { let variantIndex = this.random(curSet.moves[j].length); let moveId = toId(curSet.moves[j][variantIndex]); if (movesMax[moveId] && teamData.has[moveId] >= movesMax[moveId]) { reject = true; break; } if (requiredMoves[moveId] && !teamData.has[requiredMoves[moveId]]) { hasRequiredMove = true; } curSetVariants.push(variantIndex); } if (reject) continue; effectivePool.push({set: curSet, moveVariants: curSetVariants}); if (hasRequiredMove) priorityPool.push({set: curSet, moveVariants: curSetVariants}); } if (priorityPool.length) effectivePool = priorityPool; if (!effectivePool.length) { if (!teamData.forceResult) return false; for (let i = 0; i < setList.length; i++) { effectivePool.push({set: setList[i]}); } } let setData = effectivePool[this.random(effectivePool.length)]; let moves = []; for (let i = 0; i < setData.set.moves.length; i++) { let moveSlot = setData.set.moves[i]; moves.push(setData.moveVariants ? moveSlot[setData.moveVariants[i]] : moveSlot[this.random(moveSlot.length)]); } return { name: setData.set.name || template.baseSpecies, species: setData.set.species, gender: setData.set.gender || template.gender || (this.random(2) ? 'M' : 'F'), item: setData.set.item || '', ability: setData.set.ability || template.abilities['0'], shiny: typeof setData.set.shiny === 'undefined' ? !this.random(1024) : setData.set.shiny, level: 100, happiness: typeof setData.set.happiness === 'undefined' ? 255 : setData.set.happiness, evs: setData.set.evs || {hp: 84, atk: 84, def: 84, spa: 84, spd: 84, spe: 84}, ivs: setData.set.ivs || {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31}, nature: setData.set.nature || 'Serious', moves: moves, }; }, randomFactoryTeam: function (side, depth) { if (!depth) depth = 0; let forceResult = (depth >= 4); // The teams generated depend on the tier choice in such a way that // no exploitable information is leaked from rolling the tier in getTeam(p1). let availableTiers = ['Uber', 'OU', 'UU', 'RU', 'NU', 'PU']; if (!this.factoryTier) this.factoryTier = availableTiers[this.random(availableTiers.length)]; const chosenTier = this.factoryTier; let pokemon = []; let pokemonPool = Object.keys(this.randomFactorySets[chosenTier]); let teamData = {typeCount: {}, typeComboCount: {}, baseFormes: {}, megaCount: 0, has: {}, forceResult: forceResult, weaknesses: {}, resistances: {}}; let requiredMoveFamilies = {'hazardSet': 1, 'hazardClear':1}; let requiredMoves = {'stealthrock': 'hazardSet', 'rapidspin': 'hazardClear', 'defog': 'hazardClear'}; let weatherAbilitiesSet = {'drizzle': 'raindance', 'drought': 'sunnyday', 'snowwarning': 'hail', 'sandstream': 'sandstorm'}; let resistanceAbilities = { 'dryskin': ['Water'], 'waterabsorb': ['Water'], 'stormdrain': ['Water'], 'flashfire': ['Fire'], 'heatproof': ['Fire'], 'lightningrod': ['Electric'], 'motordrive': ['Electric'], 'voltabsorb': ['Electric'], 'sapsipper': ['Grass'], 'thickfat': ['Ice', 'Fire'], 'levitate': ['Ground'], }; while (pokemonPool.length && pokemon.length < 6) { let template = this.getTemplate(this.sampleNoReplace(pokemonPool)); if (!template.exists) continue; let speciesFlags = this.randomFactorySets[chosenTier][template.speciesid].flags; // Limit to one of each species (Species Clause) if (teamData.baseFormes[template.baseSpecies]) continue; // Limit the number of Megas to one if (teamData.megaCount >= 1 && speciesFlags.megaOnly) continue; // Limit 2 of any type let types = template.types; let skip = false; for (let t = 0; t < types.length; t++) { if (teamData.typeCount[types[t]] > 1 && this.random(5)) { skip = true; break; } } if (skip) continue; let set = this.randomFactorySet(template, pokemon.length, teamData, chosenTier); if (!set) continue; // Limit 1 of any type combination let typeCombo = types.slice().sort().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 teamData.typeComboCount) continue; // Okay, the set passes, add it to our team pokemon.push(set); // Now that our Pokemon has passed all checks, we can update team data: for (let t = 0; t < types.length; t++) { if (types[t] in teamData.typeCount) { teamData.typeCount[types[t]]++; } else { teamData.typeCount[types[t]] = 1; } } teamData.typeComboCount[typeCombo] = 1; teamData.baseFormes[template.baseSpecies] = 1; let itemData = this.getItem(set.item); if (itemData.megaStone) teamData.megaCount++; if (itemData.id in teamData.has) { teamData.has[itemData.id]++; } else { teamData.has[itemData.id] = 1; } let abilityData = this.getAbility(set.ability); if (abilityData.id in weatherAbilitiesSet) { teamData.weather = weatherAbilitiesSet[abilityData.id]; } for (let m = 0; m < set.moves.length; m++) { let moveId = toId(set.moves[m]); if (moveId in teamData.has) { teamData.has[moveId]++; } else { teamData.has[moveId] = 1; } if (moveId in requiredMoves) { teamData.has[requiredMoves[moveId]] = 1; } } for (let typeName in this.data.TypeChart) { // Cover any major weakness (3+) with at least one resistance if (teamData.resistances[typeName] >= 1) continue; if (resistanceAbilities[abilityData.id] && resistanceAbilities[abilityData.id].includes(typeName) || !this.getImmunity(typeName, types)) { // Heuristic: assume that Pokémon with these abilities don't have (too) negative typing. teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1; if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0; continue; } let typeMod = this.getEffectiveness(typeName, types); if (typeMod < 0) { teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1; if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0; } else if (typeMod > 0) { teamData.weaknesses[typeName] = (teamData.weaknesses[typeName] || 0) + 1; } } } if (pokemon.length < 6) return this.randomFactoryTeam(side, ++depth); // Quality control if (!teamData.forceResult) { for (let requiredFamily in requiredMoveFamilies) { if (!teamData.has[requiredFamily]) return this.randomFactoryTeam(side, ++depth); } for (let type in teamData.weaknesses) { if (teamData.weaknesses[type] >= 3) return this.randomFactoryTeam(side, ++depth); } } return pokemon; }, randomBSSFactorySets: require('./bss-factory-sets.json'), randomBSSFactorySet: function (template, slot, teamData, tier) { let speciesId = toId(template.species); // let flags = this.randomBSSFactorySets[tier][speciesId].flags; let setList = this.randomBSSFactorySets[tier][speciesId].sets; let effectivePool, priorityPool; // artificially replicate Item Clause let itemsMax = { aguavberry:1, airballoon:1, alakazite:1, assaultvest:1, beedrillite:1, blacksludge:1, blastoisinite:1, blazikenite:1, buginiumz:1, charizarditex:1, charizarditey:1, chestoberry:1, choiceband:1, choicescarf:1, choicespecs:1, darkiniumz:1, dragoniumz:1, eeviumz:1, ejectbutton:1, electricmemory:1, electricseed:1, electriumz:1, eviolite:1, expertbelt:1, fairiumz:1, fightingmemory:1, fightiniumz:1, figyberry:1, firiumz:1, fistplate:1, flameorb:1, flyiniumz:1, focussash:1, gengarite:1, ghostiumz:1, glalitite:1, grassiumz:1, groundiumz:1, groundmemory:1, gyaradosite:1, heatrock:1, heracronite:1, iapapaberry:1, iciumz:1, kangaskhanite:1, keeberry:1, kingsrock:1, leftovers:1, lifeorb:1, lightball:1, lightclay:1, lucarionite:1, lumberry:1, magoberry:1, mawilite:1, medichamite:1, mentalherb:1, metagrossite:1, normaliumz:1, occaberry:1, pidgeotite:1, pinsirite:1, poisoniumz:1, primariumz:1, psychiumz:1, redcard:1, rockiumz:1, rockyhelmet:1, safetygoggles:1, salacberry:1, salamencite:1, scizorite:1, scopelens:1, sharpedonite:1, shucaberry:1, sitrusberry:1, slowbronite:1, smoothrock:1, steeliumz:1, steelixite:1, steelmemory:1, tapuniumz:1, thickclub:1, toxicorb:1, venusaurite:1, wateriumz:1, weaknesspolicy:1, wikiberry:1, }; let movesMax = {'batonpass':1, 'stealthrock':1, 'spikes':1, 'toxicspikes':1, 'doubleedge':1, 'trickroom':1}; let requiredMoves = {}; let weatherAbilitiesRequire = { 'swiftswim': 'raindance', 'sandrush': 'sandstorm', 'sandveil': 'sandstorm', }; let weatherAbilitiesSet = {'drizzle':1, 'sandstream':1}; // Build a pool of eligible sets, given the team partners // Also keep track of sets with moves the team requires effectivePool = []; priorityPool = []; for (let i = 0, l = setList.length; i < l; i++) { let curSet = setList[i]; let itemData = this.getItem(curSet.item); if (teamData.megaCount > 1 && itemData.megaStone) continue; // reject 3+ mega stones if (teamData.zCount > 1 && itemData.zMove) continue; // reject 3+ Z stones if (itemsMax[itemData.id] && teamData.has[itemData.id] >= itemsMax[itemData.id]) continue; let abilityData = this.getAbility(curSet.ability); if (weatherAbilitiesRequire[abilityData.id] && teamData.weather !== weatherAbilitiesRequire[abilityData.id]) continue; if (teamData.weather && weatherAbilitiesSet[abilityData.id]) continue; // reject 2+ weather setters let reject = false; let hasRequiredMove = false; let curSetVariants = []; for (let j = 0, m = curSet.moves.length; j < m; j++) { let variantIndex = this.random(curSet.moves[j].length); let moveId = toId(curSet.moves[j][variantIndex]); if (movesMax[moveId] && teamData.has[moveId] >= movesMax[moveId]) { reject = true; break; } if (requiredMoves[moveId] && !teamData.has[requiredMoves[moveId]]) { hasRequiredMove = true; } curSetVariants.push(variantIndex); } if (reject) continue; effectivePool.push({set: curSet, moveVariants: curSetVariants}); if (hasRequiredMove) priorityPool.push({set: curSet, moveVariants: curSetVariants}); } if (priorityPool.length) effectivePool = priorityPool; if (!effectivePool.length) { if (!teamData.forceResult) return false; for (let i = 0; i < setList.length; i++) { effectivePool.push({set: setList[i]}); } } let setData = effectivePool[this.random(effectivePool.length)]; let moves = []; for (let i = 0; i < setData.set.moves.length; i++) { let moveSlot = setData.set.moves[i]; moves.push(setData.moveVariants ? moveSlot[setData.moveVariants[i]] : moveSlot[this.random(moveSlot.length)]); } return { name: setData.set.name || template.baseSpecies, species: setData.set.species, gender: setData.set.gender || template.gender || (this.random(2) ? 'M' : 'F'), item: setData.set.item || '', ability: setData.set.ability || template.abilities['0'], shiny: typeof setData.set.shiny === 'undefined' ? !this.random(1024) : setData.set.shiny, level: 50, happiness: typeof setData.set.happiness === 'undefined' ? 255 : setData.set.happiness, evs: setData.set.evs || {hp: 84, atk: 84, def: 84, spa: 84, spd: 84, spe: 84}, ivs: setData.set.ivs || {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31}, nature: setData.set.nature || 'Serious', moves: moves, }; }, randomBSSFactoryTeam: function (side, depth) { if (!depth) depth = 0; let forceResult = (depth >= 4); // Make chosen tier always BSS const chosenTier = 'BSS'; let pokemon = []; let pokemonPool = Object.keys(this.randomBSSFactorySets[chosenTier]); let teamData = {typeCount: {}, typeComboCount: {}, baseFormes: {}, megaCount: 0, zCount: 0, has: {}, forceResult: forceResult, weaknesses: {}, resistances: {}}; let requiredMoveFamilies = {}; let requiredMoves = {}; let weatherAbilitiesSet = {'drizzle': 'raindance', 'sandstream': 'sandstorm'}; let resistanceAbilities = { 'dryskin': ['Water'], 'waterabsorb': ['Water'], 'stormdrain': ['Water'], 'flashfire': ['Fire'], 'heatproof': ['Fire'], 'lightningrod': ['Electric'], 'motordrive': ['Electric'], 'voltabsorb': ['Electric'], 'sapsipper': ['Grass'], 'thickfat': ['Ice', 'Fire'], 'levitate': ['Ground'], }; while (pokemonPool.length && pokemon.length < 6) { let template = this.getTemplate(this.sampleNoReplace(pokemonPool)); if (!template.exists) continue; let speciesFlags = this.randomBSSFactorySets[chosenTier][template.speciesid].flags; // Limit to one of each species (Species Clause) if (teamData.baseFormes[template.baseSpecies]) continue; // Limit the number of Megas + Z-moves to 3 if (teamData.megaCount + teamData.zCount >= 3 && speciesFlags.megaOnly) continue; // Limit 2 of any type let types = template.types; let skip = false; for (let t = 0; t < types.length; t++) { if (teamData.typeCount[types[t]] > 1 && this.random(5)) { skip = true; break; } } if (skip) continue; let set = this.randomBSSFactorySet(template, pokemon.length, teamData, chosenTier); if (!set) continue; // Limit 1 of any type combination let typeCombo = types.slice().sort().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 teamData.typeComboCount) continue; // Okay, the set passes, add it to our team pokemon.push(set); // Now that our Pokemon has passed all checks, we can update team data: for (let t = 0; t < types.length; t++) { if (types[t] in teamData.typeCount) { teamData.typeCount[types[t]]++; } else { teamData.typeCount[types[t]] = 1; } } teamData.typeComboCount[typeCombo] = 1; teamData.baseFormes[template.baseSpecies] = 1; // Limit Mega and Z-move let itemData = this.getItem(set.item); if (itemData.megaStone) teamData.megaCount++; if (itemData.id in teamData.has) { teamData.has[itemData.id]++; } else { teamData.has[itemData.id] = 1; } if (itemData.zMove) teamData.zCount++; if (itemData.id in teamData.has) { teamData.has[itemData.id]++; } else { teamData.has[itemData.id] = 1; } let abilityData = this.getAbility(set.ability); if (abilityData.id in weatherAbilitiesSet) { teamData.weather = weatherAbilitiesSet[abilityData.id]; } for (let m = 0; m < set.moves.length; m++) { let moveId = toId(set.moves[m]); if (moveId in teamData.has) { teamData.has[moveId]++; } else { teamData.has[moveId] = 1; } if (moveId in requiredMoves) { teamData.has[requiredMoves[moveId]] = 1; } } for (let typeName in this.data.TypeChart) { // Cover any major weakness (3+) with at least one resistance if (teamData.resistances[typeName] >= 1) continue; if (resistanceAbilities[abilityData.id] && resistanceAbilities[abilityData.id].includes(typeName) || !this.getImmunity(typeName, types)) { // Heuristic: assume that Pokémon with these abilities don't have (too) negative typing. teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1; if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0; continue; } let typeMod = this.getEffectiveness(typeName, types); if (typeMod < 0) { teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1; if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0; } else if (typeMod > 0) { teamData.weaknesses[typeName] = (teamData.weaknesses[typeName] || 0) + 1; } } } if (pokemon.length < 6) return this.randomBSSFactoryTeam(side, ++depth); // Quality control if (!teamData.forceResult) { for (let requiredFamily in requiredMoveFamilies) { if (!teamData.has[requiredFamily]) return this.randomBSSFactoryTeam(side, ++depth); } for (let type in teamData.weaknesses) { if (teamData.weaknesses[type] >= 3) return this.randomBSSFactoryTeam(side, ++depth); } } return pokemon; }, };