mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-03-21 17:25:10 -05:00
Refactor queue to new BattleQueue class
Previously, battle queue stuff was just strewn around `battle.ts`. This gives it a new home: `battle-queue.ts`. This was intended to make `battle.ts` slightly more tractable, although the difference is so small that maybe I shouldn't bother. Oh, well, every little bit helps.
This commit is contained in:
parent
b91779476f
commit
9659511ff1
|
|
@ -107,7 +107,7 @@ let BattleAbilities = {
|
|||
let boosted = true;
|
||||
for (const target of this.getAllActive()) {
|
||||
if (target === pokemon) continue;
|
||||
if (this.willMove(target)) {
|
||||
if (this.queue.willMove(target)) {
|
||||
boosted = false;
|
||||
break;
|
||||
}
|
||||
|
|
@ -439,7 +439,7 @@ let BattleAbilities = {
|
|||
|
||||
if (target.side.active.length === 2 && target.position === 1) {
|
||||
// Curse Glitch
|
||||
const action = this.willMove(target);
|
||||
const action = this.queue.willMove(target);
|
||||
if (action && action.move.id === 'curse') {
|
||||
action.targetLoc = -1;
|
||||
}
|
||||
|
|
@ -2389,7 +2389,7 @@ let BattleAbilities = {
|
|||
continue;
|
||||
}
|
||||
// pokemon isn't switching this turn
|
||||
if (curPoke !== pokemon && !this.willSwitch(curPoke)) {
|
||||
if (curPoke !== pokemon && !this.queue.willSwitch(curPoke)) {
|
||||
// this.add('-message', "" + curPoke + " skipped: not switching");
|
||||
continue;
|
||||
}
|
||||
|
|
@ -4300,7 +4300,7 @@ let BattleAbilities = {
|
|||
shortDesc: "This Pokemon skips every other turn instead of using a move.",
|
||||
onStart(pokemon) {
|
||||
pokemon.removeVolatile('truant');
|
||||
if (pokemon.activeTurns && (pokemon.moveThisTurnResult !== undefined || !this.willMove(pokemon))) {
|
||||
if (pokemon.activeTurns && (pokemon.moveThisTurnResult !== undefined || !this.queue.willMove(pokemon))) {
|
||||
pokemon.addVolatile('truant');
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -576,7 +576,7 @@ let BattleItems = {
|
|||
spritenum: 41,
|
||||
onSwitchIn(pokemon) {
|
||||
if (pokemon.isActive && pokemon.baseTemplate.species === 'Kyogre') {
|
||||
this.insertQueue({pokemon: pokemon, choice: 'runPrimal'});
|
||||
this.queue.insertChoice({choice: 'runPrimal', pokemon: pokemon});
|
||||
}
|
||||
},
|
||||
onPrimal(pokemon) {
|
||||
|
|
@ -5007,7 +5007,7 @@ let BattleItems = {
|
|||
spritenum: 390,
|
||||
onSwitchIn(pokemon) {
|
||||
if (pokemon.isActive && pokemon.baseTemplate.species === 'Groudon') {
|
||||
this.insertQueue({pokemon: pokemon, choice: 'runPrimal'});
|
||||
this.queue.insertChoice({choice: 'runPrimal', pokemon: pokemon});
|
||||
}
|
||||
},
|
||||
onPrimal(pokemon) {
|
||||
|
|
@ -7759,7 +7759,7 @@ let BattleItems = {
|
|||
basePower: 10,
|
||||
},
|
||||
onSourceModifyAccuracy(accuracy, target) {
|
||||
if (typeof accuracy === 'number' && !this.willMove(target)) {
|
||||
if (typeof accuracy === 'number' && !this.queue.willMove(target)) {
|
||||
this.debug('Zoom Lens boosting accuracy');
|
||||
return accuracy * 1.2;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ let BattleMovedex = {
|
|||
// If both are true, counter will deal twice the last damage dealt in battle, no matter what was the move.
|
||||
// That means that, if opponent switches, counter will use last counter damage * 2.
|
||||
let lastUsedMove = target.side.lastMove && this.dex.getMove(target.side.lastMove.id);
|
||||
if (lastUsedMove && lastUsedMove.basePower > 0 && ['Normal', 'Fighting'].includes(lastUsedMove.type) && this.lastDamage > 0 && !this.willMove(target)) {
|
||||
if (lastUsedMove && lastUsedMove.basePower > 0 && ['Normal', 'Fighting'].includes(lastUsedMove.type) && this.lastDamage > 0 && !this.queue.willMove(target)) {
|
||||
return 2 * this.lastDamage;
|
||||
}
|
||||
this.debug("Gen 1 Counter failed due to conditions not met");
|
||||
|
|
@ -304,7 +304,7 @@ let BattleMovedex = {
|
|||
return duration;
|
||||
},
|
||||
onStart(pokemon) {
|
||||
if (!this.willMove(pokemon)) {
|
||||
if (!this.queue.willMove(pokemon)) {
|
||||
this.effectData.duration++;
|
||||
}
|
||||
let moves = pokemon.moves;
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ let BattleMovedex = {
|
|||
}
|
||||
this.effectData.move = target.lastMove.id;
|
||||
this.add('-start', target, 'Encore');
|
||||
if (!this.willMove(target)) {
|
||||
if (!this.queue.willMove(target)) {
|
||||
this.effectData.duration++;
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ let BattleScripts = {
|
|||
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
|
||||
// USE this.queue.cancelMove INSTEAD
|
||||
this.debug('' + pokemon.id + ' INCONSISTENT STATE, ALREADY MOVED: ' + pokemon.moveThisTurn);
|
||||
this.clearActiveMove(true);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ let BattleStatuses = {
|
|||
let move = this.dex.getMove(this.effectData.move);
|
||||
if (move.id) {
|
||||
this.debug('Forcing into ' + move.id);
|
||||
this.changeAction(pokemon, {move: move.id});
|
||||
this.queue.changeAction(pokemon, {choice: 'move', moveid: move.id});
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ let BattleMovedex = {
|
|||
},
|
||||
noCopy: true,
|
||||
onStart(pokemon) {
|
||||
if (!this.willMove(pokemon)) {
|
||||
if (!this.queue.willMove(pokemon)) {
|
||||
this.effectData.duration++;
|
||||
}
|
||||
if (!pokemon.lastMove) {
|
||||
|
|
@ -365,7 +365,7 @@ let BattleMovedex = {
|
|||
}
|
||||
this.effectData.move = target.lastMove.id;
|
||||
this.add('-start', target, 'Encore');
|
||||
if (!this.willMove(target)) {
|
||||
if (!this.queue.willMove(target)) {
|
||||
this.effectData.duration++;
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -50,9 +50,9 @@ let BattleItems = {
|
|||
onModifyPriority() {},
|
||||
onBeforeTurn(pokemon) {
|
||||
if (pokemon.hp <= pokemon.maxhp / 4 || (pokemon.hp <= pokemon.maxhp / 2 && pokemon.ability === 'gluttony')) {
|
||||
let action = this.willMove(pokemon);
|
||||
let action = this.queue.willMove(pokemon);
|
||||
if (!action) return;
|
||||
this.insertQueue({
|
||||
this.queue.insertChoice({
|
||||
choice: 'event',
|
||||
event: 'Custap',
|
||||
priority: action.priority + 0.1,
|
||||
|
|
@ -63,10 +63,10 @@ let BattleItems = {
|
|||
}
|
||||
},
|
||||
onCustap(pokemon) {
|
||||
let action = this.willMove(pokemon);
|
||||
let action = this.queue.willMove(pokemon);
|
||||
this.debug('custap action: ' + action);
|
||||
if (action && pokemon.eatItem()) {
|
||||
this.cancelAction(pokemon);
|
||||
this.queue.cancelAction(pokemon);
|
||||
this.add('-message', "Custap Berry activated.");
|
||||
this.runAction(action);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -382,7 +382,7 @@ let BattleMovedex = {
|
|||
},
|
||||
noCopy: true,
|
||||
onStart(pokemon) {
|
||||
if (!this.willMove(pokemon)) {
|
||||
if (!this.queue.willMove(pokemon)) {
|
||||
this.effectData.duration++;
|
||||
}
|
||||
if (!pokemon.lastMove) {
|
||||
|
|
@ -527,7 +527,7 @@ let BattleMovedex = {
|
|||
}
|
||||
this.effectData.move = target.lastMove.id;
|
||||
this.add('-start', target, 'Encore');
|
||||
if (!this.willMove(target)) {
|
||||
if (!this.queue.willMove(target)) {
|
||||
this.effectData.duration++;
|
||||
}
|
||||
},
|
||||
|
|
@ -1182,7 +1182,7 @@ let BattleMovedex = {
|
|||
inherit: true,
|
||||
desc: "Power doubles if the user moves after the target this turn. Switching in counts as an action.",
|
||||
basePowerCallback(pokemon, target) {
|
||||
if (this.willMove(target)) {
|
||||
if (this.queue.willMove(target)) {
|
||||
return 50;
|
||||
}
|
||||
return 100;
|
||||
|
|
@ -1543,7 +1543,7 @@ let BattleMovedex = {
|
|||
inherit: true,
|
||||
desc: "Fails if the target did not select a physical or special attack for use this turn, or if the target moves before the user.",
|
||||
onTry(source, target) {
|
||||
let action = this.willMove(target);
|
||||
let action = this.queue.willMove(target);
|
||||
if (!action || action.choice !== 'move' || action.move.category === 'Status' || target.volatiles.mustrecharge) {
|
||||
this.add('-fail', source);
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -767,7 +767,7 @@ let BattleMovedex = {
|
|||
desc: "The user and its party members are protected from attacks with original priority greater than 0 made by other Pokemon, including allies, during this turn. This attack has a 1/X chance of being successful, where X starts at 1 and doubles each time this move is successfully used. X resets to 1 if this attack fails or if the user's last used move is not Detect, Endure, Protect, Quick Guard, or Wide Guard. If X is 256 or more, this move has a 1/(2^32) chance of being successful. Fails if the user moves last this turn or if this move is already in effect for the user's side.",
|
||||
stallingMove: true,
|
||||
onTryHitSide(side, source) {
|
||||
return this.willAct() && this.runEvent('StallMove', source);
|
||||
return this.queue.willAct() && this.runEvent('StallMove', source);
|
||||
},
|
||||
onHitSide(side, source) {
|
||||
source.addVolatile('stall');
|
||||
|
|
@ -1230,7 +1230,7 @@ let BattleMovedex = {
|
|||
desc: "The user and its party members are protected from damaging attacks made by other Pokemon, including allies, during this turn that target all adjacent foes or all adjacent Pokemon. This attack has a 1/X chance of being successful, where X starts at 1 and doubles each time this move is successfully used. X resets to 1 if this attack fails or if the user's last used move is not Detect, Endure, Protect, Quick Guard, or Wide Guard. If X is 256 or more, this move has a 1/(2^32) chance of being successful. Fails if the user moves last this turn or if this move is already in effect for the user's side.",
|
||||
stallingMove: true,
|
||||
onTryHitSide(side, source) {
|
||||
return this.willAct() && this.runEvent('StallMove', source);
|
||||
return this.queue.willAct() && this.runEvent('StallMove', source);
|
||||
},
|
||||
onHitSide(side, source) {
|
||||
source.addVolatile('stall');
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ let BattleMovedex = {
|
|||
}
|
||||
this.effectData.move = target.lastMove.id;
|
||||
this.add('-start', target, 'Encore');
|
||||
if (!this.willMove(target)) {
|
||||
if (!this.queue.willMove(target)) {
|
||||
this.effectData.duration++;
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -817,11 +817,11 @@ let BattleMovedex = {
|
|||
inherit: true,
|
||||
onHit(target) {
|
||||
if (target.side.active.length < 2) return false; // fails in singles
|
||||
let action = this.willMove(target);
|
||||
let action = this.queue.willMove(target);
|
||||
if (!action) return false;
|
||||
|
||||
action.priority = -7.1;
|
||||
this.cancelMove(target);
|
||||
this.queue.cancelMove(target);
|
||||
for (let i = this.queue.length - 1; i >= 0; i--) {
|
||||
if (this.queue[i].choice === 'residual') {
|
||||
this.queue.splice(i, 0, action);
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ let BattleItems = {
|
|||
"zoomlens": {
|
||||
inherit: true,
|
||||
onSourceModifyAccuracy(accuracy, target) {
|
||||
if (typeof accuracy === 'number' && !this.willMove(target)) {
|
||||
if (typeof accuracy === 'number' && !this.queue.willMove(target)) {
|
||||
this.debug('Zoom Lens boosting accuracy');
|
||||
return accuracy * 1.6;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -625,7 +625,7 @@ let BattleMovedex = {
|
|||
bide: {
|
||||
inherit: true,
|
||||
onTryHit(pokemon) {
|
||||
return this.willAct() && this.runEvent('StallMove', pokemon);
|
||||
return this.queue.willAct() && this.runEvent('StallMove', pokemon);
|
||||
},
|
||||
effect: {
|
||||
duration: 2,
|
||||
|
|
|
|||
|
|
@ -324,7 +324,7 @@ let BattleMovedex = {
|
|||
stallingMove: true,
|
||||
volatileStatus: 'backoffgrrr',
|
||||
onTryHit(target, source, move) {
|
||||
return !!this.willAct() && this.runEvent('StallMove', target);
|
||||
return !!this.queue.willAct() && this.runEvent('StallMove', target);
|
||||
},
|
||||
onHit(pokemon) {
|
||||
pokemon.addVolatile('stall');
|
||||
|
|
@ -447,7 +447,7 @@ let BattleMovedex = {
|
|||
this.add('-anim', target, 'Dark Void', target);
|
||||
},
|
||||
onHit(target, source, move) {
|
||||
let wouldMove = this.cancelMove(target);
|
||||
let wouldMove = this.queue.cancelMove(target);
|
||||
// Generate a new team
|
||||
let team = this.teamGenerator.getTeam({name: target.side.name, inBattle: true});
|
||||
let set = team.shift();
|
||||
|
|
@ -2495,7 +2495,7 @@ let BattleMovedex = {
|
|||
this.attrLastMove('[still]');
|
||||
},
|
||||
onTryHit(pokemon) {
|
||||
return !!this.willAct() && this.runEvent('StallMove', pokemon);
|
||||
return !!this.queue.willAct() && this.runEvent('StallMove', pokemon);
|
||||
},
|
||||
onPrepareHit(target, source) {
|
||||
this.add('-anim', source, "King's Shield", source);
|
||||
|
|
@ -2839,7 +2839,7 @@ let BattleMovedex = {
|
|||
onPrepareHit(target, source) {
|
||||
this.add('-anim', source, "Protect", source);
|
||||
this.add('-anim', source, "Quiver Dance", source);
|
||||
let result = !!this.willAct() && this.runEvent('StallMove', source);
|
||||
let result = !!this.queue.willAct() && this.runEvent('StallMove', source);
|
||||
return result;
|
||||
},
|
||||
onHit(target, source) {
|
||||
|
|
@ -3493,7 +3493,7 @@ let BattleMovedex = {
|
|||
stallingMove: true,
|
||||
volatileStatus: 'lilypadshield',
|
||||
onTryHit(target, source, move) {
|
||||
return !!this.willAct() && this.runEvent('StallMove', target);
|
||||
return !!this.queue.willAct() && this.runEvent('StallMove', target);
|
||||
},
|
||||
onHit(pokemon) {
|
||||
pokemon.addVolatile('stall');
|
||||
|
|
@ -3675,7 +3675,7 @@ let BattleMovedex = {
|
|||
},
|
||||
onHit(target, source) {
|
||||
source.addVolatile('rage', source);
|
||||
if (this.willAct() && this.runEvent('StallMove', source)) {
|
||||
if (this.queue.willAct() && this.runEvent('StallMove', source)) {
|
||||
this.debug('Rageeeee endure');
|
||||
source.addVolatile('endure', source);
|
||||
source.addVolatile('stall');
|
||||
|
|
@ -4281,7 +4281,7 @@ let BattleMovedex = {
|
|||
this.add('-message', `${source.active[0].name}'s replacement is going to switch out next turn!`);
|
||||
},
|
||||
onBeforeTurn(pokemon) {
|
||||
this.insertQueue({choice: 'event', event: 'SSBRotate', pokemon: pokemon, priority: -69});
|
||||
this.queue.insertChoice({choice: 'event', event: 'SSBRotate', pokemon: pokemon, priority: -69});
|
||||
},
|
||||
// @ts-ignore unsupported custom event
|
||||
onSSBRotate(/** @type {Pokemon} */ pokemon) {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ let BattleScripts = {
|
|||
/* 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
|
||||
// USE this.queue.cancelMove INSTEAD
|
||||
this.debug('' + pokemon.id + ' INCONSISTENT STATE, ALREADY MOVED: ' + pokemon.moveThisTurn);
|
||||
this.clearActiveMove(true);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -280,9 +280,9 @@ let BattleMovedex = {
|
|||
flags: {authentic: 1, mystery: 1},
|
||||
onHit(target) {
|
||||
if (target.side.active.length < 2) return false; // fails in singles
|
||||
let action = this.willMove(target);
|
||||
let action = this.queue.willMove(target);
|
||||
if (action) {
|
||||
this.prioritizeAction(action);
|
||||
this.queue.prioritizeAction(action);
|
||||
this.add('-activate', target, 'move: After You');
|
||||
} else {
|
||||
return false;
|
||||
|
|
@ -1041,7 +1041,7 @@ let BattleMovedex = {
|
|||
stallingMove: true,
|
||||
volatileStatus: 'banefulbunker',
|
||||
onTryHit(target, source, move) {
|
||||
return !!this.willAct() && this.runEvent('StallMove', target);
|
||||
return !!this.queue.willAct() && this.runEvent('StallMove', target);
|
||||
},
|
||||
onHit(pokemon) {
|
||||
pokemon.addVolatile('stall');
|
||||
|
|
@ -1615,7 +1615,7 @@ let BattleMovedex = {
|
|||
accuracy: 100,
|
||||
basePower: 85,
|
||||
basePowerCallback(pokemon, target, move) {
|
||||
if (target.newlySwitched || this.willMove(target)) {
|
||||
if (target.newlySwitched || this.queue.willMove(target)) {
|
||||
this.debug('Bolt Beak damage boost');
|
||||
return move.basePower * 2;
|
||||
}
|
||||
|
|
@ -2842,12 +2842,12 @@ let BattleMovedex = {
|
|||
flags: {protect: 1, mirror: 1},
|
||||
onHit(target) {
|
||||
if (['battlebond', 'comatose', 'disguise', 'multitype', 'powerconstruct', 'rkssystem', 'schooling', 'shieldsdown', 'stancechange', 'zenmode'].includes(target.ability)) return;
|
||||
if (target.newlySwitched || this.willMove(target)) return;
|
||||
if (target.newlySwitched || this.queue.willMove(target)) return;
|
||||
target.addVolatile('gastroacid');
|
||||
},
|
||||
onAfterSubDamage(damage, target) {
|
||||
if (['battlebond', 'comatose', 'disguise', 'multitype', 'powerconstruct', 'rkssystem', 'schooling', 'shieldsdown', 'stancechange', 'zenmode'].includes(target.ability)) return;
|
||||
if (target.newlySwitched || this.willMove(target)) return;
|
||||
if (target.newlySwitched || this.queue.willMove(target)) return;
|
||||
target.addVolatile('gastroacid');
|
||||
},
|
||||
secondary: null,
|
||||
|
|
@ -3105,7 +3105,7 @@ let BattleMovedex = {
|
|||
flags: {},
|
||||
sideCondition: 'craftyshield',
|
||||
onTryHitSide(side, source) {
|
||||
return !!this.willAct();
|
||||
return !!this.queue.willAct();
|
||||
},
|
||||
effect: {
|
||||
duration: 1,
|
||||
|
|
@ -3558,7 +3558,7 @@ let BattleMovedex = {
|
|||
stallingMove: true,
|
||||
volatileStatus: 'protect',
|
||||
onPrepareHit(pokemon) {
|
||||
return !!this.willAct() && this.runEvent('StallMove', pokemon);
|
||||
return !!this.queue.willAct() && this.runEvent('StallMove', pokemon);
|
||||
},
|
||||
onHit(pokemon) {
|
||||
pokemon.addVolatile('stall');
|
||||
|
|
@ -3682,7 +3682,7 @@ let BattleMovedex = {
|
|||
noCopy: true, // doesn't get copied by Baton Pass
|
||||
onStart(pokemon, source, effect) {
|
||||
// The target hasn't taken its turn, or Cursed Body activated and the move was not used through Dancer or Instruct
|
||||
if (this.willMove(pokemon) || (pokemon === this.activePokemon && this.activeMove && !this.activeMove.isExternal)) {
|
||||
if (this.queue.willMove(pokemon) || (pokemon === this.activePokemon && this.activeMove && !this.activeMove.isExternal)) {
|
||||
this.effectData.duration--;
|
||||
}
|
||||
if (!pokemon.lastMove) {
|
||||
|
|
@ -4594,7 +4594,7 @@ let BattleMovedex = {
|
|||
flags: {protect: 1, mirror: 1, mystery: 1},
|
||||
volatileStatus: 'electrify',
|
||||
onTryHit(target) {
|
||||
if (!this.willMove(target) && target.activeTurns) return false;
|
||||
if (!this.queue.willMove(target) && target.activeTurns) return false;
|
||||
},
|
||||
effect: {
|
||||
duration: 1,
|
||||
|
|
@ -4753,7 +4753,7 @@ let BattleMovedex = {
|
|||
}
|
||||
this.effectData.move = move.id;
|
||||
this.add('-start', target, 'Encore');
|
||||
if (!this.willMove(target)) {
|
||||
if (!this.queue.willMove(target)) {
|
||||
this.effectData.duration++;
|
||||
}
|
||||
},
|
||||
|
|
@ -4828,7 +4828,7 @@ let BattleMovedex = {
|
|||
stallingMove: true,
|
||||
volatileStatus: 'endure',
|
||||
onTryHit(pokemon) {
|
||||
return this.willAct() && this.runEvent('StallMove', pokemon);
|
||||
return this.queue.willAct() && this.runEvent('StallMove', pokemon);
|
||||
},
|
||||
onHit(pokemon) {
|
||||
pokemon.addVolatile('stall');
|
||||
|
|
@ -5407,7 +5407,7 @@ let BattleMovedex = {
|
|||
// @ts-ignore
|
||||
if (action.pokemon.side === source.side && ['grasspledge', 'waterpledge'].includes(action.move.id)) {
|
||||
// @ts-ignore
|
||||
this.prioritizeAction(action);
|
||||
this.queue.prioritizeAction(action);
|
||||
this.add('-waiting', source, action.pokemon);
|
||||
return null;
|
||||
}
|
||||
|
|
@ -5522,7 +5522,7 @@ let BattleMovedex = {
|
|||
accuracy: 100,
|
||||
basePower: 85,
|
||||
basePowerCallback(pokemon, target, move) {
|
||||
if (target.newlySwitched || this.willMove(target)) {
|
||||
if (target.newlySwitched || this.queue.willMove(target)) {
|
||||
this.debug('Fishious Rend damage boost');
|
||||
return move.basePower * 2;
|
||||
}
|
||||
|
|
@ -7633,7 +7633,7 @@ let BattleMovedex = {
|
|||
// @ts-ignore
|
||||
if (action.pokemon.side === source.side && ['waterpledge', 'firepledge'].includes(action.move.id)) {
|
||||
// @ts-ignore
|
||||
this.prioritizeAction(action);
|
||||
this.queue.prioritizeAction(action);
|
||||
this.add('-waiting', source, action.pokemon);
|
||||
return null;
|
||||
}
|
||||
|
|
@ -7808,12 +7808,12 @@ let BattleMovedex = {
|
|||
let applies = false;
|
||||
if (pokemon.removeVolatile('bounce') || pokemon.removeVolatile('fly')) {
|
||||
applies = true;
|
||||
this.cancelMove(pokemon);
|
||||
this.queue.cancelMove(pokemon);
|
||||
pokemon.removeVolatile('twoturnmove');
|
||||
}
|
||||
if (pokemon.volatiles['skydrop']) {
|
||||
applies = true;
|
||||
this.cancelMove(pokemon);
|
||||
this.queue.cancelMove(pokemon);
|
||||
|
||||
if (pokemon.volatiles['skydrop'].source) {
|
||||
this.add('-end', pokemon.volatiles['twoturnmove'].source, 'Sky Drop', '[interrupt]');
|
||||
|
|
@ -8634,7 +8634,7 @@ let BattleMovedex = {
|
|||
flags: {authentic: 1},
|
||||
volatileStatus: 'helpinghand',
|
||||
onTryHit(target) {
|
||||
if (!target.newlySwitched && !this.willMove(target)) return false;
|
||||
if (!target.newlySwitched && !this.queue.willMove(target)) return false;
|
||||
},
|
||||
effect: {
|
||||
duration: 1,
|
||||
|
|
@ -10057,7 +10057,7 @@ let BattleMovedex = {
|
|||
stallingMove: true,
|
||||
volatileStatus: 'kingsshield',
|
||||
onTryHit(pokemon) {
|
||||
return !!this.willAct() && this.runEvent('StallMove', pokemon);
|
||||
return !!this.queue.willAct() && this.runEvent('StallMove', pokemon);
|
||||
},
|
||||
onHit(pokemon) {
|
||||
pokemon.addVolatile('stall');
|
||||
|
|
@ -11369,7 +11369,7 @@ let BattleMovedex = {
|
|||
stallingMove: true,
|
||||
volatileStatus: 'maxguard',
|
||||
onPrepareHit(pokemon) {
|
||||
return !!this.willAct() && this.runEvent('StallMove', pokemon);
|
||||
return !!this.queue.willAct() && this.runEvent('StallMove', pokemon);
|
||||
},
|
||||
onHit(pokemon) {
|
||||
pokemon.addVolatile('stall');
|
||||
|
|
@ -11767,7 +11767,7 @@ let BattleMovedex = {
|
|||
priority: 0,
|
||||
flags: {protect: 1, authentic: 1},
|
||||
onTryHit(target, pokemon) {
|
||||
const action = this.willMove(target);
|
||||
const action = this.queue.willMove(target);
|
||||
if (!action) return false;
|
||||
|
||||
const noMeFirst = [
|
||||
|
|
@ -13134,7 +13134,7 @@ let BattleMovedex = {
|
|||
stallingMove: true,
|
||||
volatileStatus: 'obstruct',
|
||||
onTryHit(pokemon) {
|
||||
return !!this.willAct() && this.runEvent('StallMove', pokemon);
|
||||
return !!this.queue.willAct() && this.runEvent('StallMove', pokemon);
|
||||
},
|
||||
onHit(pokemon) {
|
||||
pokemon.addVolatile('stall');
|
||||
|
|
@ -13487,7 +13487,7 @@ let BattleMovedex = {
|
|||
accuracy: 100,
|
||||
basePower: 50,
|
||||
basePowerCallback(pokemon, target, move) {
|
||||
if (target.newlySwitched || this.willMove(target)) {
|
||||
if (target.newlySwitched || this.queue.willMove(target)) {
|
||||
this.debug('Payback NOT boosted');
|
||||
return move.basePower;
|
||||
}
|
||||
|
|
@ -14309,7 +14309,7 @@ let BattleMovedex = {
|
|||
stallingMove: true,
|
||||
volatileStatus: 'protect',
|
||||
onPrepareHit(pokemon) {
|
||||
return !!this.willAct() && this.runEvent('StallMove', pokemon);
|
||||
return !!this.queue.willAct() && this.runEvent('StallMove', pokemon);
|
||||
},
|
||||
onHit(pokemon) {
|
||||
pokemon.addVolatile('stall');
|
||||
|
|
@ -14746,7 +14746,7 @@ let BattleMovedex = {
|
|||
this.debug('Pursuit start');
|
||||
let alreadyAdded = false;
|
||||
for (const source of this.effectData.sources) {
|
||||
if (!this.cancelMove(source) || !source.hp) continue;
|
||||
if (!this.queue.cancelMove(source) || !source.hp) continue;
|
||||
if (!alreadyAdded) {
|
||||
this.add('-activate', pokemon, 'move: Pursuit');
|
||||
alreadyAdded = true;
|
||||
|
|
@ -14805,7 +14805,7 @@ let BattleMovedex = {
|
|||
flags: {protect: 1, mirror: 1},
|
||||
onHit(target) {
|
||||
if (target.side.active.length < 2) return false; // fails in singles
|
||||
let action = this.willMove(target);
|
||||
let action = this.queue.willMove(target);
|
||||
if (!action) return false;
|
||||
|
||||
action.order = 201;
|
||||
|
|
@ -14849,7 +14849,7 @@ let BattleMovedex = {
|
|||
flags: {snatch: 1},
|
||||
sideCondition: 'quickguard',
|
||||
onTryHitSide(side, source) {
|
||||
return !!this.willAct();
|
||||
return !!this.queue.willAct();
|
||||
},
|
||||
onHitSide(side, source) {
|
||||
source.addVolatile('stall');
|
||||
|
|
@ -15864,7 +15864,7 @@ let BattleMovedex = {
|
|||
// @ts-ignore
|
||||
if (action.move.id === 'round') {
|
||||
// @ts-ignore
|
||||
this.prioritizeAction(action);
|
||||
this.queue.prioritizeAction(action);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -16613,9 +16613,9 @@ let BattleMovedex = {
|
|||
onHit(pokemon, source, move) {
|
||||
if (pokemon.side !== source.side && move.category === 'Physical') {
|
||||
pokemon.volatiles['shelltrap'].gotHit = true;
|
||||
let action = this.willMove(pokemon);
|
||||
let action = this.queue.willMove(pokemon);
|
||||
if (action) {
|
||||
this.prioritizeAction(action);
|
||||
this.queue.prioritizeAction(action);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -17304,7 +17304,7 @@ let BattleMovedex = {
|
|||
if (pokemon.hasItem('ironball') || pokemon.volatiles['ingrain'] || this.field.getPseudoWeather('gravity')) applies = false;
|
||||
if (pokemon.removeVolatile('fly') || pokemon.removeVolatile('bounce')) {
|
||||
applies = true;
|
||||
this.cancelMove(pokemon);
|
||||
this.queue.cancelMove(pokemon);
|
||||
pokemon.removeVolatile('twoturnmove');
|
||||
}
|
||||
if (pokemon.volatiles['magnetrise']) {
|
||||
|
|
@ -17320,7 +17320,7 @@ let BattleMovedex = {
|
|||
},
|
||||
onRestart(pokemon) {
|
||||
if (pokemon.removeVolatile('fly') || pokemon.removeVolatile('bounce')) {
|
||||
this.cancelMove(pokemon);
|
||||
this.queue.cancelMove(pokemon);
|
||||
this.add('-start', pokemon, 'Smack Down');
|
||||
}
|
||||
},
|
||||
|
|
@ -17926,7 +17926,7 @@ let BattleMovedex = {
|
|||
stallingMove: true,
|
||||
volatileStatus: 'spikyshield',
|
||||
onTryHit(target, source, move) {
|
||||
return !!this.willAct() && this.runEvent('StallMove', target);
|
||||
return !!this.queue.willAct() && this.runEvent('StallMove', target);
|
||||
},
|
||||
onHit(pokemon) {
|
||||
pokemon.addVolatile('stall');
|
||||
|
|
@ -18821,7 +18821,7 @@ let BattleMovedex = {
|
|||
priority: 1,
|
||||
flags: {contact: 1, protect: 1, mirror: 1},
|
||||
onTry(source, target) {
|
||||
let action = this.willMove(target);
|
||||
let action = this.queue.willMove(target);
|
||||
if (!action || action.choice !== 'move' || (action.move.category === 'Status' && action.move.id !== 'mefirst') || target.volatiles.mustrecharge) {
|
||||
this.add('-fail', source);
|
||||
this.attrLastMove('[still]');
|
||||
|
|
@ -19388,7 +19388,7 @@ let BattleMovedex = {
|
|||
effect: {
|
||||
duration: 3,
|
||||
onStart(target) {
|
||||
if (target.activeTurns && !this.willMove(target)) {
|
||||
if (target.activeTurns && !this.queue.willMove(target)) {
|
||||
this.effectData.duration++;
|
||||
}
|
||||
this.add('-start', target, 'move: Taunt');
|
||||
|
|
@ -20194,7 +20194,7 @@ let BattleMovedex = {
|
|||
|
||||
if (target.side.active.length === 2 && target.position === 1) {
|
||||
// Curse Glitch
|
||||
const action = this.willMove(target);
|
||||
const action = this.queue.willMove(target);
|
||||
if (action && action.move.id === 'curse') {
|
||||
action.targetLoc = -1;
|
||||
}
|
||||
|
|
@ -20753,7 +20753,7 @@ let BattleMovedex = {
|
|||
// @ts-ignore
|
||||
if (action.pokemon.side === source.side && ['firepledge', 'grasspledge'].includes(action.move.id)) {
|
||||
// @ts-ignore
|
||||
this.prioritizeAction(action);
|
||||
this.queue.prioritizeAction(action);
|
||||
this.add('-waiting', source, action.pokemon);
|
||||
return null;
|
||||
}
|
||||
|
|
@ -21009,7 +21009,7 @@ let BattleMovedex = {
|
|||
flags: {snatch: 1},
|
||||
sideCondition: 'wideguard',
|
||||
onTryHitSide(side, source) {
|
||||
return !!this.willAct();
|
||||
return !!this.queue.willAct();
|
||||
},
|
||||
onHitSide(side, source) {
|
||||
source.addVolatile('stall');
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ let BattleScripts = {
|
|||
/* 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
|
||||
// USE this.queue.cancelMove INSTEAD
|
||||
this.debug('' + pokemon.id + ' INCONSISTENT STATE, ALREADY MOVED: ' + pokemon.moveThisTurn);
|
||||
this.clearActiveMove(true);
|
||||
return;
|
||||
|
|
|
|||
367
sim/battle-queue.ts
Normal file
367
sim/battle-queue.ts
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
/**
|
||||
* Simulator Battle Action Queue
|
||||
* Pokemon Showdown - http://pokemonshowdown.com/
|
||||
*
|
||||
* The action queue is the core of the battle simulation. A rough overview of
|
||||
* the core battle loop:
|
||||
*
|
||||
* - chosen moves/switches are added to the action queue
|
||||
* - the action queue is sorted in speed/priority order
|
||||
* - we go through the action queue
|
||||
* - repeat
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import {Battle} from './battle';
|
||||
|
||||
/** A move action */
|
||||
export interface MoveAction {
|
||||
/** action type */
|
||||
choice: 'move' | 'beforeTurnMove';
|
||||
order: 3 | 5 | 200 | 201 | 199;
|
||||
/** priority of the action (lower first) */
|
||||
priority: number;
|
||||
/** fractional priority of the action (lower first) */
|
||||
fractionalPriority: number;
|
||||
/** speed of pokemon using move (higher first if priority tie) */
|
||||
speed: number;
|
||||
/** the pokemon doing the move */
|
||||
pokemon: Pokemon;
|
||||
/** location of the target, relative to pokemon's side */
|
||||
targetLoc: number;
|
||||
/** original target pokemon, for target-tracking moves */
|
||||
originalTarget: Pokemon;
|
||||
/** a move to use (move action only) */
|
||||
moveid: ID;
|
||||
/** a move to use (move action only) */
|
||||
move: Move;
|
||||
/** true if megaing or ultra bursting */
|
||||
mega: boolean | 'done';
|
||||
/** if zmoving, the name of the zmove */
|
||||
zmove?: string;
|
||||
/** if dynamaxed, the name of the max move */
|
||||
maxMove?: string;
|
||||
/** effect that called the move (eg Instruct) if any */
|
||||
sourceEffect?: Effect | null;
|
||||
}
|
||||
|
||||
/** A switch action */
|
||||
export interface SwitchAction {
|
||||
/** action type */
|
||||
choice: 'switch' | 'instaswitch';
|
||||
order: 3 | 103;
|
||||
/** priority of the action (lower first) */
|
||||
priority: number;
|
||||
/** speed of pokemon switching (higher first if priority tie) */
|
||||
speed: number;
|
||||
/** the pokemon doing the switch */
|
||||
pokemon: Pokemon;
|
||||
/** pokemon to switch to */
|
||||
target: Pokemon;
|
||||
/** effect that called the switch (eg U */
|
||||
sourceEffect: Effect | null;
|
||||
}
|
||||
|
||||
/** A Team Preview choice action */
|
||||
export interface TeamAction {
|
||||
/** action type */
|
||||
choice: 'team';
|
||||
/** priority of the action (lower first) */
|
||||
priority: number;
|
||||
/** unused for this action type */
|
||||
speed: 1;
|
||||
/** the pokemon switching */
|
||||
pokemon: Pokemon;
|
||||
/** new index */
|
||||
index: number;
|
||||
}
|
||||
|
||||
/** A generic action not done by a pokemon */
|
||||
export interface FieldAction {
|
||||
/** action type */
|
||||
choice: 'start' | 'residual' | 'pass' | 'beforeTurn';
|
||||
/** priority of the action (lower first) */
|
||||
priority: number;
|
||||
/** unused for this action type */
|
||||
speed: 1;
|
||||
/** unused for this action type */
|
||||
pokemon: null;
|
||||
}
|
||||
|
||||
/** A generic action done by a single pokemon */
|
||||
export interface PokemonAction {
|
||||
/** action type */
|
||||
choice: 'megaEvo' | 'shift' | 'runPrimal' | 'runSwitch' | 'event' | 'runUnnerve' | 'runDynamax';
|
||||
/** priority of the action (lower first) */
|
||||
priority: number;
|
||||
/** speed of pokemon doing action (higher first if priority tie) */
|
||||
speed: number;
|
||||
/** the pokemon doing action */
|
||||
pokemon: Pokemon;
|
||||
}
|
||||
|
||||
export type Action = MoveAction | SwitchAction | TeamAction | FieldAction | PokemonAction;
|
||||
|
||||
/**
|
||||
* An ActionChoice is like an Action and has the same structure, but it doesn't need to be fully filled out.
|
||||
*
|
||||
* Any Action or ChosenAction qualifies as an ActionChoice.
|
||||
*
|
||||
* The `[k: string]: any` part is required so TypeScript won't warn about unnecessary properties.
|
||||
*/
|
||||
export interface ActionChoice {
|
||||
choice: string;
|
||||
[k: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kind of like a priority queue, although not sorted mid-turn in Gen 1-7.
|
||||
*
|
||||
* Sort order is documented in `BattleQueue.comparePriority`.
|
||||
*/
|
||||
export class BattleQueue extends Array<Action> {
|
||||
battle: Battle;
|
||||
constructor(battle: Battle) {
|
||||
super();
|
||||
this.battle = battle;
|
||||
}
|
||||
/**
|
||||
* Takes an ActionChoice, and fills it out into a full Action object.
|
||||
*
|
||||
* Returns an array of Actions because some ActionChoices (like mega moves)
|
||||
* resolve to two Actions (mega evolution + use move)
|
||||
*/
|
||||
resolveAction(action: ActionChoice, midTurn: boolean = false): Action[] {
|
||||
if (!action) throw new Error(`Action not passed to resolveAction`);
|
||||
if (action.choice === 'pass') return [];
|
||||
const actions = [action];
|
||||
|
||||
if (!action.side && action.pokemon) action.side = action.pokemon.side;
|
||||
if (!action.move && action.moveid) action.move = this.battle.dex.getActiveMove(action.moveid);
|
||||
if (!action.order) {
|
||||
const orders: {[choice: string]: number} = {
|
||||
team: 1,
|
||||
start: 2,
|
||||
instaswitch: 3,
|
||||
beforeTurn: 4,
|
||||
beforeTurnMove: 5,
|
||||
|
||||
runUnnerve: 100,
|
||||
runSwitch: 101,
|
||||
runPrimal: 102,
|
||||
switch: 103,
|
||||
megaEvo: 104,
|
||||
runDynamax: 105,
|
||||
|
||||
shift: 106,
|
||||
|
||||
// default is 200 (for moves)
|
||||
|
||||
residual: 300,
|
||||
};
|
||||
if (action.choice in orders) {
|
||||
action.order = orders[action.choice];
|
||||
} else {
|
||||
action.order = 200;
|
||||
if (!['move', 'event'].includes(action.choice)) {
|
||||
throw new Error(`Unexpected orderless action ${action.choice}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!midTurn) {
|
||||
if (action.choice === 'move') {
|
||||
if (!action.maxMove && !action.zmove && action.move.beforeTurnCallback) {
|
||||
actions.unshift(...this.resolveAction({
|
||||
choice: 'beforeTurnMove', pokemon: action.pokemon, move: action.move, targetLoc: action.targetLoc,
|
||||
}));
|
||||
}
|
||||
if (action.mega) {
|
||||
// TODO: Check that the Pokémon is not affected by Sky Drop.
|
||||
// (This is currently being done in `runMegaEvo`).
|
||||
actions.unshift(...this.resolveAction({
|
||||
choice: 'megaEvo',
|
||||
pokemon: action.pokemon,
|
||||
}));
|
||||
}
|
||||
if (action.maxMove && !action.pokemon.volatiles['dynamax']) {
|
||||
actions.unshift(...this.resolveAction({
|
||||
choice: 'runDynamax',
|
||||
pokemon: action.pokemon,
|
||||
}));
|
||||
}
|
||||
action.fractionalPriority = this.battle.runEvent('FractionalPriority', action.pokemon, null, action.move, 0);
|
||||
} else if (['switch', 'instaswitch'].includes(action.choice)) {
|
||||
if (typeof action.pokemon.switchFlag === 'string') {
|
||||
action.sourceEffect = this.battle.dex.getMove(action.pokemon.switchFlag as ID) as any;
|
||||
}
|
||||
action.pokemon.switchFlag = false;
|
||||
}
|
||||
}
|
||||
|
||||
const deferPriority = this.battle.gen === 7 && action.mega && action.mega !== 'done';
|
||||
if (action.move) {
|
||||
let target = null;
|
||||
action.move = this.battle.dex.getActiveMove(action.move);
|
||||
|
||||
if (!action.targetLoc) {
|
||||
target = this.battle.getRandomTarget(action.pokemon, action.move);
|
||||
// TODO: what actually happens here?
|
||||
if (target) action.targetLoc = this.battle.getTargetLoc(target, action.pokemon);
|
||||
}
|
||||
action.originalTarget = this.battle.getAtLoc(action.pokemon, action.targetLoc);
|
||||
}
|
||||
if (!deferPriority) this.battle.getActionSpeed(action);
|
||||
return actions as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the passed action happen next (skipping speed order).
|
||||
*/
|
||||
prioritizeAction(action: MoveAction | SwitchAction, source?: Pokemon, sourceEffect?: Effect) {
|
||||
for (const [i, curAction] of this.entries()) {
|
||||
if (curAction === action) {
|
||||
this.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
action.sourceEffect = sourceEffect;
|
||||
action.order = 3;
|
||||
this.unshift(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes a pokemon's action, and inserts its new action
|
||||
* in priority order.
|
||||
*
|
||||
* You'd normally want the OverrideAction event (which doesn't
|
||||
* change priority order).
|
||||
*/
|
||||
changeAction(pokemon: Pokemon, action: ActionChoice) {
|
||||
this.cancelAction(pokemon);
|
||||
if (!action.pokemon) action.pokemon = pokemon;
|
||||
this.insertChoice(action);
|
||||
}
|
||||
|
||||
addChoice(choices: ActionChoice | ActionChoice[]) {
|
||||
if (!Array.isArray(choices)) choices = [choices];
|
||||
for (const choice of choices) {
|
||||
this.push(...this.resolveAction(choice));
|
||||
}
|
||||
}
|
||||
|
||||
willAct() {
|
||||
for (const action of this) {
|
||||
if (['move', 'switch', 'instaswitch', 'shift'].includes(action.choice)) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
willMove(pokemon: Pokemon) {
|
||||
if (pokemon.fainted) return false;
|
||||
for (const action of this) {
|
||||
if (action.choice === 'move' && action.pokemon === pokemon) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
cancelAction(pokemon: Pokemon) {
|
||||
const oldLength = this.length;
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
if (this[i].pokemon === pokemon) {
|
||||
this.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return this.length !== oldLength;
|
||||
}
|
||||
|
||||
cancelMove(pokemon: Pokemon) {
|
||||
for (const [i, action] of this.entries()) {
|
||||
if (action.choice === 'move' && action.pokemon === pokemon) {
|
||||
this.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
willSwitch(pokemon: Pokemon) {
|
||||
for (const action of this) {
|
||||
if (['switch', 'instaswitch'].includes(action.choice) && action.pokemon === pokemon) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the passed action into the action queue when it normally
|
||||
* would have happened (sorting by priority/speed), without
|
||||
* re-sorting the existing actions.
|
||||
*/
|
||||
insertChoice(choices: ActionChoice | ActionChoice[], midTurn: boolean = false) {
|
||||
if (Array.isArray(choices)) {
|
||||
for (const choice of choices) {
|
||||
this.insertChoice(choice);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const choice = choices;
|
||||
|
||||
if (choice.pokemon) {
|
||||
choice.pokemon.updateSpeed();
|
||||
}
|
||||
const actions = this.resolveAction(choice, midTurn);
|
||||
for (const [i, curAction] of this.entries()) {
|
||||
if (BattleQueue.comparePriority(actions[0], curAction) < 0) {
|
||||
this.splice(i, 0, ...actions);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.push(...actions);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.splice(0);
|
||||
}
|
||||
|
||||
debug() {
|
||||
return this.map(action =>
|
||||
// @ts-ignore
|
||||
`${action.order || ''}:${action.priority || ''}:${action.speed || ''}:${action.subOrder || ''} - ${action.choice}${action.pokemon ? ' ' + action.pokemon : ''}${action.move ? ' ' + action.move : ''}`
|
||||
).join('\n') + '\n';
|
||||
}
|
||||
|
||||
sort(): this;
|
||||
sort(DO_NOT_USE_COMPARATORS?: never) {
|
||||
if (DO_NOT_USE_COMPARATORS) throw new Error(`Battle queues can't be sorted with a custom comparator`);
|
||||
// this.log.push('SORT ' + this.debugQueue());
|
||||
this.battle.speedSort(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default sort order for actions, but also event listeners.
|
||||
*
|
||||
* 1. Order, low to high (default last)
|
||||
* 2. Priority, high to low (default 0)
|
||||
* 3. Speed, high to low (default 0)
|
||||
* 4. SubOrder, low to high (default 0)
|
||||
* 5. AbilityOrder, switch-in order for abilities
|
||||
*/
|
||||
static comparePriority(a: AnyObject, b: AnyObject) {
|
||||
return -((b.order || 4294967296) - (a.order || 4294967296)) ||
|
||||
((b.priority || 0) - (a.priority || 0)) ||
|
||||
((b.speed || 0) - (a.speed || 0)) ||
|
||||
-((b.subOrder || 0) - (a.subOrder || 0)) ||
|
||||
((a.thing && b.thing) ? -(b.thing.abilityOrder - a.thing.abilityOrder) : 0) ||
|
||||
0;
|
||||
}
|
||||
}
|
||||
|
||||
export default BattleQueue;
|
||||
295
sim/battle.ts
295
sim/battle.ts
|
|
@ -12,6 +12,7 @@ import {Pokemon} from './pokemon';
|
|||
import {PRNG, PRNGSeed} from './prng';
|
||||
import {Side} from './side';
|
||||
import {State} from './state';
|
||||
import {BattleQueue, Action} from './battle-queue';
|
||||
|
||||
/** A Pokemon that has fainted. */
|
||||
interface FaintedPokemon {
|
||||
|
|
@ -88,7 +89,7 @@ export class Battle {
|
|||
reportPercentages: boolean;
|
||||
supportCancel: boolean;
|
||||
|
||||
queue: Actions.Action[];
|
||||
queue: BattleQueue;
|
||||
readonly faintQueue: FaintedPokemon[];
|
||||
|
||||
readonly log: string[];
|
||||
|
|
@ -165,7 +166,7 @@ export class Battle {
|
|||
this.reportPercentages = false;
|
||||
this.supportCancel = false;
|
||||
|
||||
this.queue = [];
|
||||
this.queue = new BattleQueue(this);
|
||||
this.faintQueue = [];
|
||||
|
||||
this.log = [];
|
||||
|
|
@ -1088,6 +1089,14 @@ export class Battle {
|
|||
}
|
||||
}
|
||||
|
||||
clearRequest() {
|
||||
this.requestState = '';
|
||||
for (const side of this.sides) {
|
||||
side.activeRequest = null;
|
||||
side.clearChoice();
|
||||
}
|
||||
}
|
||||
|
||||
getMaxTeamSize() {
|
||||
const teamLengthData = this.format.teamLength;
|
||||
return (teamLengthData && teamLengthData.battle) || 6;
|
||||
|
|
@ -1231,7 +1240,7 @@ export class Battle {
|
|||
const oldActive = side.active[pos];
|
||||
if (oldActive) {
|
||||
// if a pokemon is forced out by Whirlwind/etc or Eject Button/Pack, it no longer takes its turn
|
||||
this.cancelAction(oldActive);
|
||||
this.queue.cancelAction(oldActive);
|
||||
|
||||
let newMove = null;
|
||||
if (this.gen === 4 && sourceEffect) {
|
||||
|
|
@ -1262,8 +1271,8 @@ export class Battle {
|
|||
}
|
||||
this.add('switch', pokemon, pokemon.getDetails);
|
||||
if (sourceEffect) this.log[this.log.length - 1] += `|[from]${sourceEffect.fullname}`;
|
||||
this.insertQueue({pokemon, choice: 'runUnnerve'});
|
||||
this.insertQueue({pokemon, choice: 'runSwitch'});
|
||||
this.queue.insertChoice({choice: 'runUnnerve', pokemon});
|
||||
this.queue.insertChoice({choice: 'runSwitch', pokemon});
|
||||
}
|
||||
|
||||
canSwitch(side: Side) {
|
||||
|
|
@ -1311,7 +1320,7 @@ export class Battle {
|
|||
pokemon.position = pos;
|
||||
side.pokemon[pokemon.position] = pokemon;
|
||||
side.pokemon[oldActive.position] = oldActive;
|
||||
this.cancelMove(oldActive);
|
||||
this.queue.cancelMove(oldActive);
|
||||
oldActive.clearVolatile();
|
||||
}
|
||||
side.active[pos] = pokemon;
|
||||
|
|
@ -1332,7 +1341,7 @@ export class Battle {
|
|||
this.singleEvent('Start', pokemon.getItem(), pokemon.itemData, pokemon);
|
||||
}
|
||||
} else {
|
||||
this.insertQueue({pokemon, choice: 'runSwitch'});
|
||||
this.queue.insertChoice({choice: 'runSwitch', pokemon});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1365,6 +1374,7 @@ export class Battle {
|
|||
nextTurn() {
|
||||
this.turn++;
|
||||
this.lastMoveThisTurn = null;
|
||||
|
||||
const trappedBySide: boolean[] = [];
|
||||
const stalenessBySide: ('internal' | 'external' | undefined)[] = [];
|
||||
for (const side of this.sides) {
|
||||
|
|
@ -1546,12 +1556,12 @@ export class Battle {
|
|||
}
|
||||
|
||||
start() {
|
||||
// deserialized should use restart instead
|
||||
// Deserialized games should use restart()
|
||||
if (this.deserialized) return;
|
||||
// need all players to start
|
||||
if (!this.sides.every(side => !!side)) return;
|
||||
if (!this.sides.every(side => !!side)) throw new Error(`Missing sides: ${this.sides}`);
|
||||
|
||||
if (this.started) return;
|
||||
if (this.started) throw new Error(`Battle already started`);
|
||||
|
||||
this.started = true;
|
||||
this.sides[1].foe = this.sides[0];
|
||||
|
|
@ -1588,7 +1598,7 @@ export class Battle {
|
|||
|
||||
this.residualEvent('TeamPreview');
|
||||
|
||||
this.addToQueue({choice: 'start'});
|
||||
this.queue.addChoice({choice: 'start'});
|
||||
this.midTurn = true;
|
||||
if (!this.requestState) this.go();
|
||||
}
|
||||
|
|
@ -1763,7 +1773,7 @@ export class Battle {
|
|||
this.faintMessages(true);
|
||||
if (this.gen <= 2) {
|
||||
target.faint();
|
||||
if (this.gen <= 1) this.queue = [];
|
||||
if (this.gen <= 1) this.queue.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2325,16 +2335,16 @@ export class Battle {
|
|||
if (this.gen <= 1) {
|
||||
// in gen 1, fainting skips the rest of the turn
|
||||
// residuals don't exist in gen 1
|
||||
this.queue = [];
|
||||
this.queue.clear();
|
||||
} else if (this.gen <= 3 && this.gameType === 'singles') {
|
||||
// in gen 3 or earlier, fainting in singles skips to residuals
|
||||
for (const pokemon of this.getAllActive()) {
|
||||
if (this.gen <= 2) {
|
||||
// in gen 2, fainting skips moves only
|
||||
this.cancelMove(pokemon);
|
||||
this.queue.cancelMove(pokemon);
|
||||
} else {
|
||||
// in gen 3, fainting skips all moves and switches
|
||||
this.cancelAction(pokemon);
|
||||
this.queue.cancelAction(pokemon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2370,92 +2380,6 @@ export class Battle {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an object describing an action, and fills it out into a full
|
||||
* Action object.
|
||||
*/
|
||||
resolveAction(action: AnyObject, midTurn: boolean = false): Actions.Action {
|
||||
if (!action) throw new Error(`Action not passed to resolveAction`);
|
||||
|
||||
if (!action.side && action.pokemon) action.side = action.pokemon.side;
|
||||
if (!action.move && action.moveid) action.move = this.dex.getActiveMove(action.moveid);
|
||||
if (!action.choice && action.move) action.choice = 'move';
|
||||
if (!action.order) {
|
||||
const orders: {[choice: string]: number} = {
|
||||
team: 1,
|
||||
start: 2,
|
||||
instaswitch: 3,
|
||||
beforeTurn: 4,
|
||||
beforeTurnMove: 5,
|
||||
|
||||
runUnnerve: 100,
|
||||
runSwitch: 101,
|
||||
runPrimal: 102,
|
||||
switch: 103,
|
||||
megaEvo: 104,
|
||||
runDynamax: 105,
|
||||
|
||||
shift: 106,
|
||||
|
||||
// default is 200 (for moves)
|
||||
|
||||
residual: 300,
|
||||
};
|
||||
if (action.choice in orders) {
|
||||
action.order = orders[action.choice];
|
||||
} else {
|
||||
action.order = 200;
|
||||
if (!['move', 'event'].includes(action.choice)) {
|
||||
throw new Error(`Unexpected orderless action ${action.choice}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!midTurn) {
|
||||
if (action.choice === 'move') {
|
||||
if (!action.maxMove && !action.zmove && action.move.beforeTurnCallback) {
|
||||
this.addToQueue({
|
||||
choice: 'beforeTurnMove', pokemon: action.pokemon, move: action.move, targetLoc: action.targetLoc,
|
||||
});
|
||||
}
|
||||
if (action.mega) {
|
||||
// TODO: Check that the Pokémon is not affected by Sky Drop.
|
||||
// (This is currently being done in `runMegaEvo`).
|
||||
this.addToQueue({
|
||||
choice: 'megaEvo',
|
||||
pokemon: action.pokemon,
|
||||
});
|
||||
}
|
||||
if (action.maxMove && !action.pokemon.volatiles['dynamax']) {
|
||||
this.addToQueue({
|
||||
choice: 'runDynamax',
|
||||
pokemon: action.pokemon,
|
||||
});
|
||||
}
|
||||
action.fractionalPriority = this.runEvent('FractionalPriority', action.pokemon, null, action.move, 0);
|
||||
} else if (['switch', 'instaswitch'].includes(action.choice)) {
|
||||
if (typeof action.pokemon.switchFlag === 'string') {
|
||||
action.sourceEffect = this.dex.getMove(action.pokemon.switchFlag as ID) as any;
|
||||
}
|
||||
action.pokemon.switchFlag = false;
|
||||
}
|
||||
}
|
||||
|
||||
const deferPriority = this.gen === 7 && action.mega && action.mega !== 'done';
|
||||
if (action.move) {
|
||||
let target = null;
|
||||
action.move = this.dex.getActiveMove(action.move);
|
||||
|
||||
if (!action.targetLoc) {
|
||||
target = this.getRandomTarget(action.pokemon, action.move);
|
||||
// TODO: what actually happens here?
|
||||
if (target) action.targetLoc = this.getTargetLoc(target, action.pokemon);
|
||||
}
|
||||
action.originalTarget = this.getAtLoc(action.pokemon, action.targetLoc);
|
||||
}
|
||||
if (!deferPriority) this.getActionSpeed(action);
|
||||
return action as any;
|
||||
}
|
||||
|
||||
getActionSpeed(action: AnyObject) {
|
||||
if (action.choice === 'move') {
|
||||
let move = action.move;
|
||||
|
|
@ -2493,122 +2417,7 @@ export class Battle {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the action last in the queue. Mostly used before sortQueue.
|
||||
*/
|
||||
addToQueue(action: AnyObject | AnyObject[]) {
|
||||
if (Array.isArray(action)) {
|
||||
for (const curAction of action) {
|
||||
this.addToQueue(curAction);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.choice === 'pass') return;
|
||||
this.queue.push(this.resolveAction(action));
|
||||
}
|
||||
|
||||
sortQueue() {
|
||||
// this.log.push('SORT ' + this.debugQueue());
|
||||
this.speedSort(this.queue);
|
||||
}
|
||||
|
||||
debugQueue() {
|
||||
return this.queue.map(action =>
|
||||
// @ts-ignore
|
||||
`${action.order || ''}:${action.priority || ''}:${action.speed || ''}:${action.subOrder || ''} - ${action.choice}${action.pokemon ? ' ' + action.pokemon : ''}${action.move ? ' ' + action.move : ''}`
|
||||
).join('\n') + '\n';
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the passed action into the action queue when it normally
|
||||
* would have happened (sorting by priority/speed), without
|
||||
* re-sorting the existing actions.
|
||||
*/
|
||||
insertQueue(chosenAction: AnyObject | AnyObject[], midTurn: boolean = false) {
|
||||
if (Array.isArray(chosenAction)) {
|
||||
for (const subAction of chosenAction) {
|
||||
this.insertQueue(subAction);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (chosenAction.pokemon) {
|
||||
chosenAction.pokemon.updateSpeed();
|
||||
}
|
||||
const action = this.resolveAction(chosenAction, midTurn);
|
||||
for (const [i, curAction] of this.queue.entries()) {
|
||||
if (this.comparePriority(action, curAction) < 0) {
|
||||
this.queue.splice(i, 0, action);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.queue.push(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the passed action happen next (skipping speed order).
|
||||
*/
|
||||
prioritizeAction(action: Actions.MoveAction | Actions.SwitchAction, source?: Pokemon, sourceEffect?: Effect) {
|
||||
if (this.event && !sourceEffect) sourceEffect = this.effect;
|
||||
for (const [i, curAction] of this.queue.entries()) {
|
||||
if (curAction === action) {
|
||||
this.queue.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
action.sourceEffect = sourceEffect;
|
||||
action.order = 3;
|
||||
this.queue.unshift(action);
|
||||
}
|
||||
|
||||
willAct() {
|
||||
for (const action of this.queue) {
|
||||
if (['move', 'switch', 'instaswitch', 'shift'].includes(action.choice)) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
willMove(pokemon: Pokemon) {
|
||||
if (pokemon.fainted) return false;
|
||||
for (const action of this.queue) {
|
||||
if (action.choice === 'move' && action.pokemon === pokemon) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
cancelAction(pokemon: Pokemon) {
|
||||
const oldLength = this.queue.length;
|
||||
this.queue = this.queue.filter(action =>
|
||||
action.pokemon !== pokemon
|
||||
);
|
||||
return this.queue.length !== oldLength;
|
||||
}
|
||||
|
||||
cancelMove(pokemon: Pokemon) {
|
||||
for (const [i, action] of this.queue.entries()) {
|
||||
if (action.choice === 'move' && action.pokemon === pokemon) {
|
||||
this.queue.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
willSwitch(pokemon: Pokemon) {
|
||||
for (const action of this.queue) {
|
||||
if (['switch', 'instaswitch'].includes(action.choice) && action.pokemon === pokemon) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
runAction(action: Actions.Action) {
|
||||
runAction(action: Action) {
|
||||
const pokemonOriginalHP = action.pokemon?.hp;
|
||||
// returns whether or not we ended in a callback
|
||||
switch (action.choice) {
|
||||
|
|
@ -2800,13 +2609,13 @@ export class Battle {
|
|||
} else if (action.choice === 'megaEvo' && this.gen === 7) {
|
||||
this.eachEvent('Update');
|
||||
// In Gen 7, the action order is recalculated for a Pokémon that mega evolves.
|
||||
const moveIndex = this.queue.findIndex(queuedAction =>
|
||||
queuedAction.pokemon === action.pokemon && queuedAction.choice === 'move'
|
||||
);
|
||||
if (moveIndex >= 0) {
|
||||
const moveAction = this.queue.splice(moveIndex, 1)[0] as Actions.MoveAction;
|
||||
moveAction.mega = 'done';
|
||||
this.insertQueue(moveAction, true);
|
||||
for (const [i, queuedAction] of this.queue.entries()) {
|
||||
if (queuedAction.pokemon === action.pokemon && queuedAction.choice === 'move') {
|
||||
this.queue.splice(i, 1);
|
||||
queuedAction.mega = 'done';
|
||||
this.queue.insertChoice(queuedAction, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (this.queue.length && this.queue[0].choice === 'instaswitch') {
|
||||
|
|
@ -2850,7 +2659,7 @@ export class Battle {
|
|||
for (const queueAction of this.queue) {
|
||||
if (queueAction.pokemon) this.getActionSpeed(queueAction);
|
||||
}
|
||||
this.sortQueue();
|
||||
this.queue.sort();
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -2861,34 +2670,20 @@ export class Battle {
|
|||
if (this.requestState) this.requestState = '';
|
||||
|
||||
if (!this.midTurn) {
|
||||
this.queue.push(this.resolveAction({choice: 'residual'}));
|
||||
this.queue.unshift(this.resolveAction({choice: 'beforeTurn'}));
|
||||
this.queue.insertChoice({choice: 'beforeTurn'});
|
||||
this.queue.addChoice({choice: 'residual'});
|
||||
this.midTurn = true;
|
||||
}
|
||||
|
||||
while (this.queue.length) {
|
||||
const action = this.queue[0];
|
||||
this.queue.shift();
|
||||
const action = this.queue.shift()!;
|
||||
this.runAction(action);
|
||||
if (this.requestState || this.ended) return;
|
||||
}
|
||||
|
||||
this.nextTurn();
|
||||
this.midTurn = false;
|
||||
this.queue = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes a pokemon's action, and inserts its new action
|
||||
* in priority order.
|
||||
*
|
||||
* You'd normally want the OverrideAction event (which doesn't
|
||||
* change priority order).
|
||||
*/
|
||||
changeAction(pokemon: Pokemon, action: AnyObject) {
|
||||
this.cancelAction(pokemon);
|
||||
if (!action.pokemon) action.pokemon = pokemon;
|
||||
this.insertQueue(action);
|
||||
this.queue.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2927,8 +2722,8 @@ export class Battle {
|
|||
commitDecisions() {
|
||||
this.updateSpeed();
|
||||
|
||||
const oldQueue = this.queue;
|
||||
this.queue = [];
|
||||
const oldQueue = this.queue.slice();
|
||||
this.queue.clear();
|
||||
if (!this.allChoicesDone()) throw new Error("Not all choices done");
|
||||
|
||||
for (const side of this.sides) {
|
||||
|
|
@ -2936,10 +2731,11 @@ export class Battle {
|
|||
if (choice) this.inputLog.push(`>${side.id} ${choice}`);
|
||||
}
|
||||
for (const side of this.sides) {
|
||||
this.addToQueue(side.choice.actions);
|
||||
this.queue.addChoice(side.choice.actions);
|
||||
}
|
||||
this.clearRequest();
|
||||
|
||||
this.sortQueue();
|
||||
this.queue.sort();
|
||||
this.queue.push(...oldQueue);
|
||||
|
||||
this.requestState = '';
|
||||
|
|
@ -3137,7 +2933,7 @@ export class Battle {
|
|||
if (!didSomething) return;
|
||||
this.inputLog.push(`>player ${slot} ` + JSON.stringify(options));
|
||||
this.add('player', side.id, side.name, side.avatar, options.rating || '');
|
||||
this.start();
|
||||
if (this.sides.every(battleSide => !!battleSide)) this.start();
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
|
|
@ -3408,7 +3204,8 @@ export class Battle {
|
|||
delete action.pokemon;
|
||||
}
|
||||
|
||||
this.queue = [];
|
||||
this.queue.battle = null!;
|
||||
this.queue = null!;
|
||||
// in case the garbage collector really sucks, at least deallocate the log
|
||||
// @ts-ignore - readonly
|
||||
this.log = [];
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
type Battle = import('./battle').Battle
|
||||
type Action = import('./battle-queue').Action
|
||||
type Field = import('./field').Field
|
||||
type ModdedDex = import('./dex').ModdedDex
|
||||
type Pokemon = import('./pokemon').Pokemon
|
||||
|
|
@ -1118,8 +1119,7 @@ interface BattleScriptsData {
|
|||
hitStepTypeImmunity?: (this: Battle, targets: Pokemon[], pokemon: Pokemon, move: ActiveMove) => boolean[]
|
||||
isAdjacent?: (this: Battle, pokemon1: Pokemon, pokemon2: Pokemon) => boolean
|
||||
moveHit?: (this: Battle, target: Pokemon | null, pokemon: Pokemon, move: ActiveMove, moveData?: ActiveMove, isSecondary?: boolean, isSelf?: boolean) => number | undefined | false
|
||||
resolveAction?: (this: Battle, action: AnyObject, midTurn?: boolean) => Actions.Action
|
||||
runAction?: (this: Battle, action: Actions.Action) => void
|
||||
runAction?: (this: Battle, action: Action) => void
|
||||
runMegaEvo?: (this: Battle, pokemon: Pokemon) => boolean
|
||||
runMove?: (this: Battle, moveOrMoveName: Move | string, pokemon: Pokemon, targetLoc: number, sourceEffect?: Effect | null, zMove?: string, externalMove?: boolean, maxMove?: string, originalTarget?: Pokemon) => void
|
||||
runMoveEffects?: (this: Battle, damage: SpreadMoveDamage, targets: SpreadMoveTargets, source: Pokemon, move: ActiveMove, moveData: ActiveMove, isSecondary?: boolean, isSelf?: boolean) => SpreadMoveDamage
|
||||
|
|
@ -1213,96 +1213,6 @@ interface PlayerOptions {
|
|||
seed?: PRNGSeed;
|
||||
}
|
||||
|
||||
namespace Actions {
|
||||
/** A move action */
|
||||
export interface MoveAction {
|
||||
/** action type */
|
||||
choice: 'move' | 'beforeTurnMove';
|
||||
order: 3 | 5 | 200 | 201 | 199;
|
||||
/** priority of the action (lower first) */
|
||||
priority: number;
|
||||
/** fractional priority of the action (lower first) */
|
||||
fractionalPriority: number;
|
||||
/** speed of pokemon using move (higher first if priority tie) */
|
||||
speed: number;
|
||||
/** the pokemon doing the move */
|
||||
pokemon: Pokemon;
|
||||
/** location of the target, relative to pokemon's side */
|
||||
targetLoc: number;
|
||||
/** original target pokemon, for target-tracking moves */
|
||||
originalTarget: Pokemon;
|
||||
/** a move to use (move action only) */
|
||||
moveid: ID
|
||||
/** a move to use (move action only) */
|
||||
move: Move;
|
||||
/** true if megaing or ultra bursting */
|
||||
mega: boolean | 'done';
|
||||
/** if zmoving, the name of the zmove */
|
||||
zmove?: string;
|
||||
/** if dynamaxed, the name of the max move */
|
||||
maxMove?: string;
|
||||
/** effect that called the move (eg Instruct) if any */
|
||||
sourceEffect?: Effect | null;
|
||||
}
|
||||
|
||||
/** A switch action */
|
||||
export interface SwitchAction {
|
||||
/** action type */
|
||||
choice: 'switch' | 'instaswitch';
|
||||
order: 3 | 103;
|
||||
/** priority of the action (lower first) */
|
||||
priority: number;
|
||||
/** speed of pokemon switching (higher first if priority tie) */
|
||||
speed: number;
|
||||
/** the pokemon doing the switch */
|
||||
pokemon: Pokemon;
|
||||
/** pokemon to switch to */
|
||||
target: Pokemon;
|
||||
/** effect that called the switch (eg U */
|
||||
sourceEffect: Effect | null;
|
||||
}
|
||||
|
||||
/** A Team Preview choice action */
|
||||
export interface TeamAction {
|
||||
/** action type */
|
||||
choice: 'team';
|
||||
/** priority of the action (lower first) */
|
||||
priority: number;
|
||||
/** unused for this action type */
|
||||
speed: 1;
|
||||
/** the pokemon switching */
|
||||
pokemon: Pokemon;
|
||||
/** new index */
|
||||
index: number;
|
||||
}
|
||||
|
||||
/** A generic action not done by a pokemon */
|
||||
export interface FieldAction {
|
||||
/** action type */
|
||||
choice: 'start' | 'residual' | 'pass' | 'beforeTurn';
|
||||
/** priority of the action (lower first) */
|
||||
priority: number;
|
||||
/** unused for this action type */
|
||||
speed: 1;
|
||||
/** unused for this action type */
|
||||
pokemon: null;
|
||||
}
|
||||
|
||||
/** A generic action done by a single pokemon */
|
||||
export interface PokemonAction {
|
||||
/** action type */
|
||||
choice: 'megaEvo' | 'shift' | 'runPrimal' | 'runSwitch' | 'event' | 'runUnnerve' | 'runDynamax';
|
||||
/** priority of the action (lower first) */
|
||||
priority: number;
|
||||
/** speed of pokemon doing action (higher first if priority tie) */
|
||||
speed: number;
|
||||
/** the pokemon doing action */
|
||||
pokemon: Pokemon;
|
||||
}
|
||||
|
||||
export type Action = MoveAction | SwitchAction | TeamAction | FieldAction | PokemonAction;
|
||||
}
|
||||
|
||||
namespace RandomTeamsTypes {
|
||||
export interface TeamDetails {
|
||||
megaStone?: number;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {Pokemon} from './pokemon';
|
|||
import {State} from './state';
|
||||
|
||||
/** A single action that can be chosen. */
|
||||
interface ChosenAction {
|
||||
export interface ChosenAction {
|
||||
choice: 'move' | 'switch' | 'instaswitch' | 'team' | 'shift' | 'pass'; // action type
|
||||
pokemon?: Pokemon; // the pokemon doing the action
|
||||
targetLoc?: number; // relative location of the target to pokemon (move action only)
|
||||
|
|
|
|||
|
|
@ -32,12 +32,11 @@ type Referable = Battle | Field | Side | Pokemon | PureEffect | Ability | Item |
|
|||
// keys which we skip during default (de)serialization and (the keys which)
|
||||
// need special treatment from these sets are then handled manually.
|
||||
|
||||
// Battle inherits from Dex, but all of Dex's fields are redundant - we can
|
||||
// just recreate the Dex from the format.
|
||||
const BATTLE = new Set([
|
||||
'dex', 'gen', 'ruleTable', 'id', 'log', 'inherit', 'format',
|
||||
'zMoveTable', 'teamGenerator', 'NOT_FAIL', 'FAIL', 'SILENT_FAIL',
|
||||
'field', 'sides', 'prng', 'hints', 'deserialized', 'maxMoveTable',
|
||||
'queue',
|
||||
]);
|
||||
const FIELD = new Set(['id', 'battle']);
|
||||
const SIDE = new Set(['battle', 'team', 'pokemon', 'choice', 'activeRequest']);
|
||||
|
|
@ -69,6 +68,7 @@ export const State = new class {
|
|||
// We treat log specially because we only set it back on Battle after everything
|
||||
// else has been deserialized to avoid anything accidentally `add`-ing to it.
|
||||
state.log = battle.log;
|
||||
state.queue = this.serializeWithRefs([...battle.queue], battle);
|
||||
state.formatid = battle.format.id;
|
||||
return state;
|
||||
}
|
||||
|
|
@ -145,6 +145,8 @@ export const State = new class {
|
|||
}
|
||||
}
|
||||
battle.prng = new PRNG(state.prng);
|
||||
const queue = this.deserializeWithRefs(state.queue, battle);
|
||||
battle.queue.push(...queue);
|
||||
// @ts-ignore - readonly
|
||||
battle.hints = new Set(state.hints);
|
||||
// @ts-ignore - readonly
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user