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