pokemon-showdown/data/scripts.js
LegoFigure11 39d8197458 BSSF: Minor Set updates, code cleanup (#3625)
* Magnet Pull Magnzone now gets HP Fire 100% of the time
* Change Ditto's IVs to allow players to know it's Hidden Power at Team
Preview
* Dittos are now Sassy to give a better download matchup in the case of
a 'double down" - mirror matchup remains unchanged
* Removed weather restriction on Chlorophyll to bump up Venusaur usage
* Removed unused weather abilities

Also updated the link to the VR thread in the BSS format description
2017-06-12 19:30:03 +04:00

3812 lines
150 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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 (pokemon.level >= target.level && (move.ohko === true || !target.hasType(move.ohko))) {
// TODO: Research dependency of accuracy on user typing
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.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.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;
},
};