Refactor Decision -> Action

"Decision" and "Choice" were always kind of unclear, so Decision is now
Action. It should now be a lot clearer.

Actions are also now strongly typed.
This commit is contained in:
Guangcong Luo 2017-12-02 11:30:36 -06:00
parent dc7c46b427
commit f3dbfbe685
18 changed files with 374 additions and 325 deletions

View File

@ -303,7 +303,7 @@ In addition to room messages, battles have their own messages.
`|request|REQUEST`
> Gives a JSON object containing a request for a decision (to move or
> Gives a JSON object containing a request for a choice (to move or
> switch). To assist in your decision, `REQUEST.active` has information
> about your active Pokémon, and `REQUEST.side` has information about your
> your team as a whole.

View File

@ -420,9 +420,9 @@ exports.BattleAbilities = {
if (target.side.active.length === 2 && target.position === 1) {
// Curse Glitch
const decision = this.willMove(target);
if (decision && decision.move.id === 'curse') {
decision.targetLoc = -1;
const action = this.willMove(target);
if (action && action.move.id === 'curse') {
action.targetLoc = -1;
}
}
}

View File

@ -285,10 +285,10 @@ exports.BattleMovedex = {
flags: {authentic: 1, mystery: 1},
onHit: function (target) {
if (target.side.active.length < 2) return false; // fails in singles
let decision = this.willMove(target);
if (decision) {
let action = this.willMove(target);
if (action) {
this.cancelMove(target);
this.queue.unshift(decision);
this.queue.unshift(action);
this.add('-activate', target, 'move: After You');
} else {
return false;
@ -4415,7 +4415,7 @@ exports.BattleMovedex = {
this.effectData.duration++;
}
},
onOverrideDecision: function (pokemon, target, move) {
onOverrideAction: function (pokemon, target, move) {
if (move.id !== this.effectData.move) return this.effectData.move;
},
onResidualOrder: 13,
@ -5045,11 +5045,11 @@ exports.BattleMovedex = {
flags: {protect: 1, mirror: 1, nonsky: 1},
onPrepareHit: function (target, source, move) {
for (let i = 0; i < this.queue.length; i++) {
let decision = this.queue[i];
if (!decision.move || !decision.pokemon || !decision.pokemon.isActive || decision.pokemon.fainted) continue;
if (decision.pokemon.side === source.side && ['grasspledge', 'waterpledge'].includes(decision.move.id)) {
this.prioritizeQueue(decision);
this.add('-waiting', source, decision.pokemon);
let action = this.queue[i];
if (!action.move || !action.pokemon || !action.pokemon.isActive || action.pokemon.fainted) continue;
if (action.pokemon.side === source.side && ['grasspledge', 'waterpledge'].includes(action.move.id)) {
this.prioritizeAction(action);
this.add('-waiting', source, action.pokemon);
return null;
}
}
@ -6461,11 +6461,11 @@ exports.BattleMovedex = {
flags: {protect: 1, mirror: 1, nonsky: 1},
onPrepareHit: function (target, source, move) {
for (let i = 0; i < this.queue.length; i++) {
let decision = this.queue[i];
if (!decision.move || !decision.pokemon || !decision.pokemon.isActive || decision.pokemon.fainted) continue;
if (decision.pokemon.side === source.side && ['waterpledge', 'firepledge'].includes(decision.move.id)) {
this.prioritizeQueue(decision);
this.add('-waiting', source, decision.pokemon);
let action = this.queue[i];
if (!action.move || !action.pokemon || !action.pokemon.isActive || action.pokemon.fainted) continue;
if (action.pokemon.side === source.side && ['waterpledge', 'firepledge'].includes(action.move.id)) {
this.prioritizeAction(action);
this.add('-waiting', source, action.pokemon);
return null;
}
}
@ -9926,12 +9926,12 @@ exports.BattleMovedex = {
priority: 0,
flags: {protect: 1, authentic: 1},
onTryHit: function (target, pokemon) {
let decision = this.willMove(target);
if (decision) {
let action = this.willMove(target);
if (action) {
let noMeFirst = [
'chatter', 'counter', 'covet', 'focuspunch', 'mefirst', 'metalburst', 'mirrorcoat', 'struggle', 'thief',
];
let move = this.getMoveCopy(decision.move.id);
let move = this.getMoveCopy(action.move.id);
if (move.category !== 'Status' && !noMeFirst.includes(move)) {
pokemon.addVolatile('mefirst');
this.useMove(move, pokemon, target);
@ -12749,7 +12749,7 @@ exports.BattleMovedex = {
this.add('-activate', pokemon, 'move: Pursuit');
alreadyAdded = true;
}
// Run through each decision in queue to check if the Pursuit user is supposed to Mega Evolve this turn.
// Run through each action in queue to check if the Pursuit user is supposed to Mega Evolve this turn.
// If it is, then Mega Evolve before moving.
if (source.canMegaEvo || source.canUltraBurst) {
for (const [actionIndex, action] of this.queue.entries()) {
@ -12784,13 +12784,13 @@ exports.BattleMovedex = {
flags: {protect: 1, mirror: 1},
onHit: function (target) {
if (target.side.active.length < 2) return false; // fails in singles
let decision = this.willMove(target);
if (decision) {
decision.priority = -7.1;
let action = this.willMove(target);
if (action) {
action.priority = -7.1;
this.cancelMove(target);
for (let i = this.queue.length - 1; i >= 0; i--) {
if (this.queue[i].choice === 'residual') {
this.queue.splice(i, 0, decision);
this.queue.splice(i, 0, action);
break;
}
}
@ -13842,10 +13842,10 @@ exports.BattleMovedex = {
flags: {protect: 1, mirror: 1, sound: 1, authentic: 1},
onTry: function () {
for (let i = 0; i < this.queue.length; i++) {
let decision = this.queue[i];
if (!decision.pokemon || !decision.move) continue;
if (decision.move.id === 'round') {
this.prioritizeQueue(decision);
let action = this.queue[i];
if (!action.pokemon || !action.move) continue;
if (action.move.id === 'round') {
this.prioritizeAction(action);
return;
}
}
@ -16573,8 +16573,8 @@ exports.BattleMovedex = {
priority: 1,
flags: {contact: 1, protect: 1, mirror: 1},
onTry: function (source, target) {
let decision = this.willMove(target);
if (!decision || decision.choice !== 'move' || (decision.move.category === 'Status' && decision.move.id !== 'mefirst') || target.volatiles.mustrecharge) {
let action = this.willMove(target);
if (!action || action.choice !== 'move' || (action.move.category === 'Status' && action.move.id !== 'mefirst') || target.volatiles.mustrecharge) {
this.attrLastMove('[still]');
this.add('-fail', source);
return null;
@ -17884,9 +17884,9 @@ exports.BattleMovedex = {
if (target.side.active.length === 2 && target.position === 1) {
// Curse Glitch
const decision = this.willMove(target);
if (decision && decision.move.id === 'curse') {
decision.targetLoc = -1;
const action = this.willMove(target);
if (action && action.move.id === 'curse') {
action.targetLoc = -1;
}
}
},
@ -17926,7 +17926,7 @@ exports.BattleMovedex = {
onStart: function (target, source) {
this.add('-fieldstart', 'move: Trick Room', '[of] ' + source);
},
// Speed modification is changed in Pokemon.getDecisionSpeed() in sim/pokemon.js
// Speed modification is changed in Pokemon.getActionSpeed() in sim/pokemon.js
onResidualOrder: 23,
onEnd: function () {
this.add('-fieldend', 'move: Trick Room');
@ -18413,11 +18413,11 @@ exports.BattleMovedex = {
flags: {protect: 1, mirror: 1, nonsky: 1},
onPrepareHit: function (target, source, move) {
for (let i = 0; i < this.queue.length; i++) {
let decision = this.queue[i];
if (!decision.move || !decision.pokemon || !decision.pokemon.isActive || decision.pokemon.fainted) continue;
if (decision.pokemon.side === source.side && ['firepledge', 'grasspledge'].includes(decision.move.id)) {
this.prioritizeQueue(decision);
this.add('-waiting', source, decision.pokemon);
let action = this.queue[i];
if (!action.move || !action.pokemon || !action.pokemon.isActive || action.pokemon.fainted) continue;
if (action.pokemon.side === source.side && ['firepledge', 'grasspledge'].includes(action.move.id)) {
this.prioritizeAction(action);
this.add('-waiting', source, action.pokemon);
return null;
}
}

View File

@ -18,7 +18,7 @@ exports.BattleScripts = {
runMove: function (move, pokemon, targetLoc, sourceEffect, zMove, externalMove) {
let target = this.getTarget(pokemon, zMove || move, targetLoc);
if (!sourceEffect && toId(move) !== 'struggle' && !zMove) {
let changedMove = this.runEvent('OverrideDecision', pokemon, target, move);
let changedMove = this.runEvent('OverrideAction', pokemon, target, move);
if (changedMove && changedMove !== true) {
move = changedMove;
target = null;

View File

@ -180,7 +180,7 @@ exports.BattleMovedex = {
this.effectData.duration++;
}
},
onOverrideDecision: function (pokemon) {
onOverrideAction: function (pokemon) {
return this.effectData.move;
},
onResidualOrder: 13,

View File

@ -65,7 +65,7 @@ exports.BattleScripts = {
runMove: function (move, pokemon, targetLoc, sourceEffect) {
let target = this.getTarget(pokemon, move, targetLoc);
if (!sourceEffect && toId(move) !== 'struggle') {
let changedMove = this.runEvent('OverrideDecision', pokemon, target, move);
let changedMove = this.runEvent('OverrideAction', pokemon, target, move);
if (changedMove && changedMove !== true) {
move = changedMove;
target = null;

View File

@ -165,7 +165,7 @@ exports.BattleStatuses = {
let move = this.getMove(this.effectData.move);
if (move.id) {
this.debug('Forcing into ' + move.id);
this.changeDecision(pokemon, {move: move.id});
this.changeAction(pokemon, {move: move.id});
}
},
},

View File

@ -238,7 +238,7 @@ exports.BattleMovedex = {
this.effectData.duration++;
}
},
onOverrideDecision: function (pokemon) {
onOverrideAction: function (pokemon) {
return this.effectData.move;
},
onResidualOrder: 13,

View File

@ -39,32 +39,32 @@ exports.BattleItems = {
onModifyPriority: function () {},
onBeforeTurn: function (pokemon) {
if (pokemon.hp <= pokemon.maxhp / 4 || (pokemon.hp <= pokemon.maxhp / 2 && pokemon.ability === 'gluttony')) {
let decision = this.willMove(pokemon);
if (!decision) return;
let action = this.willMove(pokemon);
if (!action) return;
this.insertQueue({
choice: 'event',
event: 'Custap',
priority: decision.priority + 0.1,
pokemon: decision.pokemon,
move: decision.move,
target: decision.target,
priority: action.priority + 0.1,
pokemon: action.pokemon,
move: action.move,
target: action.target,
});
}
},
onCustap: function (pokemon) {
let decision = this.willMove(pokemon);
this.debug('custap decision: ' + decision);
if (decision) {
let action = this.willMove(pokemon);
this.debug('custap action: ' + action);
if (action) {
pokemon.eatItem();
}
},
onEat: function (pokemon) {
let decision = this.willMove(pokemon);
this.debug('custap eaten: ' + decision);
if (decision) {
this.cancelDecision(pokemon);
let action = this.willMove(pokemon);
this.debug('custap eaten: ' + action);
if (action) {
this.cancelAction(pokemon);
this.add('-message', "Custap Berry activated.");
this.runDecision(decision);
this.runAction(action);
}
},
},

View File

@ -413,7 +413,7 @@ exports.BattleMovedex = {
this.effectData.duration++;
}
},
onOverrideDecision: function (pokemon) {
onOverrideAction: function (pokemon) {
return this.effectData.move;
},
onResidualOrder: 13,
@ -1094,8 +1094,8 @@ exports.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: function (source, target) {
let decision = this.willMove(target);
if (!decision || decision.choice !== 'move' || decision.move.category === 'Status' || target.volatiles.mustrecharge) {
let action = this.willMove(target);
if (!action || action.choice !== 'move' || action.move.category === 'Status' || target.volatiles.mustrecharge) {
this.add('-fail', source);
return null;
}

View File

@ -73,7 +73,7 @@ exports.BattleMovedex = {
this.effectData.duration++;
}
},
onOverrideDecision: function (pokemon, target, move) {
onOverrideAction: function (pokemon, target, move) {
if (move.id !== this.effectData.move) return this.effectData.move;
},
onResidualOrder: 13,

View File

@ -22,20 +22,59 @@ const Pokemon = require('./pokemon');
*/
/**
* An object representing a single action that can be chosen.
* A move action
*
* @typedef {Object} Action
* @property {string} choice - a choice
* @property {Pokemon} [pokemon] - the pokemon making the choice
* @property {number} [targetLoc] - location of the target, relative to pokemon's side
* @property {string} [move] - a move to use
* @property {Pokemon} [target] - the target of the choice
* @property {number} [index] - the chosen index in team preview
* @property {number} [priority] - priority of the chosen index
* @property {Side} [side] - the pokemon's side
* @property {?boolean} [mega] - true if megaing or ultra bursting
* @property {?boolean} [zmove] - true if zmoving
* @typedef {Object} MoveAction
* @property {'move' | 'beforeTurnMove'} choice - action type
* @property {number} priority - priority of the action (lower first)
* @property {number} speed - speed of pokemon using move (higher first if priority tie)
* @property {Pokemon} pokemon - the pokemon doing the move
* @property {number} targetLoc - location of the target, relative to pokemon's side
* @property {string} moveid - a move to use (move action only)
* @property {Move} move - a move to use (move action only)
* @property {boolean | 'done'} mega - true if megaing or ultra bursting
* @property {boolean} zmove - true if zmoving
* @property {Effect?} sourceEffect - effect that did the action
*/
/**
* A switch action
*
* @typedef {Object} SwitchAction
* @property {'switch' | 'instaswitch'} choice - action type
* @property {number} priority - priority of the action (lower first)
* @property {number} speed - speed of pokemon switching (higher first if priority tie)
* @property {Pokemon} pokemon - the pokemon doing the switch
* @property {Pokemon} target - pokemon to switch to
*/
/**
* A Team Preview choice action
*
* @typedef {Object} TeamAction
* @property {'team'} choice - action type
* @property {number} priority - priority of the action (lower first)
* @property {1} speed - unused for this action type
* @property {Pokemon} pokemon - the pokemon switching
* @property {number} index - new index
*/
/**
* A generic action not done by a pokemon
*
* @typedef {Object} FieldAction
* @property {'start' | 'residual' | 'pass' | 'beforeTurn'} choice - action type
* @property {number} priority - priority of the action (lower first)
* @property {1} speed - unused for this action type
* @property {null} pokemon - unused for this action type
*/
/**
* A generic action done by a single pokemon
*
* @typedef {Object} PokemonAction
* @property {'megaEvo' | 'shift' | 'runPrimal' | 'runSwitch' | 'event' | 'runUnnerve'} choice - action type
* @property {number} priority - priority of the action (lower first)
* @property {number} speed - speed of pokemon doing action (higher first if priority tie)
* @property {Pokemon} pokemon - the pokemon doing action
*/
/** @typedef {MoveAction | SwitchAction | TeamAction | FieldAction | PokemonAction} Action */
class Battle extends Dex.ModdedDex {
/**
@ -77,7 +116,7 @@ class Battle extends Dex.ModdedDex {
this.reportExactHP = !!format.debug;
this.replayExactHP = !format.team;
/**@type {AnyObject[]} */
/**@type {Action[]} */
this.queue = [];
/**@type {FaintedPokemon[]} */
this.faintQueue = [];
@ -1730,7 +1769,7 @@ class Battle extends Dex.ModdedDex {
this.residualEvent('TeamPreview');
this.addQueue({choice: 'start'});
this.addToQueue({choice: 'start'});
this.midTurn = true;
if (!this.currentRequest) this.go();
}
@ -2432,7 +2471,7 @@ class Battle extends Dex.ModdedDex {
this.cancelMove(pokemon);
} else {
// in gen 3, fainting skips all moves and switches
this.cancelDecision(pokemon);
this.cancelAction(pokemon);
}
}
}
@ -2454,15 +2493,20 @@ class Battle extends Dex.ModdedDex {
}
/**
* @param {AnyObject} decision
* Takes an object describing an action, and fills it out into a full
* Action object.
*
* @param {AnyObject} action
* @param {boolean} [midTurn]
* @return {Action}
*/
resolvePriority(decision, midTurn = false) {
if (!decision) return;
resolveAction(action, midTurn = false) {
if (!action) throw new Error(`Action not passed to resolveAction`);
if (!decision.side && decision.pokemon) decision.side = decision.pokemon.side;
if (!decision.choice && decision.move) decision.choice = 'move';
if (!decision.priority && decision.priority !== 0) {
if (!action.side && action.pokemon) action.side = action.pokemon.side;
if (!action.move && action.moveid) action.move = this.getMoveCopy(action.moveid);
if (!action.choice && action.move) action.choice = 'move';
if (!action.priority && action.priority !== 0) {
let priorities = {
'beforeTurn': 100,
'beforeTurnMove': 99,
@ -2476,77 +2520,85 @@ class Battle extends Dex.ModdedDex {
'team': 102,
'start': 101,
};
if (decision.choice in priorities) {
decision.priority = priorities[decision.choice];
if (action.choice in priorities) {
action.priority = priorities[action.choice];
}
}
if (!midTurn) {
if (decision.choice === 'move') {
if (!decision.zmove && this.getMove(decision.move).beforeTurnCallback) {
this.addQueue({choice: 'beforeTurnMove', pokemon: decision.pokemon, move: decision.move, targetLoc: decision.targetLoc});
if (action.choice === 'move') {
if (!action.zmove && action.move.beforeTurnCallback) {
this.addToQueue({choice: 'beforeTurnMove', pokemon: action.pokemon, move: action.move, targetLoc: action.targetLoc});
}
if (decision.mega) {
if (action.mega) {
// TODO: Check that the Pokémon is not affected by Sky Drop.
// (This is currently being done in `runMegaEvo`).
this.addQueue({
this.addToQueue({
choice: 'megaEvo',
pokemon: decision.pokemon,
pokemon: action.pokemon,
});
}
} else if (decision.choice === 'switch' || decision.choice === 'instaswitch') {
if (decision.pokemon.switchFlag && decision.pokemon.switchFlag !== true) {
decision.pokemon.switchCopyFlag = decision.pokemon.switchFlag;
} else if (action.choice === 'switch' || action.choice === 'instaswitch') {
if (action.pokemon.switchFlag && action.pokemon.switchFlag !== true) {
action.pokemon.switchCopyFlag = action.pokemon.switchFlag;
}
decision.pokemon.switchFlag = false;
if (!decision.speed) decision.speed = decision.pokemon.getDecisionSpeed();
action.pokemon.switchFlag = false;
if (!action.speed) action.speed = action.pokemon.getActionSpeed();
}
}
let deferPriority = this.gen >= 7 && decision.mega && decision.mega !== 'done';
if (decision.move) {
let deferPriority = this.gen >= 7 && action.mega && action.mega !== 'done';
if (action.move) {
let target = null;
action.move = this.getMoveCopy(action.move);
if (!decision.targetLoc) {
target = this.resolveTarget(decision.pokemon, decision.move);
decision.targetLoc = this.getTargetLoc(target, decision.pokemon);
if (!action.targetLoc) {
target = this.resolveTarget(action.pokemon, action.move);
action.targetLoc = this.getTargetLoc(target, action.pokemon);
}
decision.move = this.getMoveCopy(decision.move);
if (!decision.priority && !deferPriority) {
let move = decision.move;
if (decision.zmove) {
if (!action.priority && !deferPriority) {
let move = action.move;
if (action.zmove) {
// @ts-ignore
let zMoveName = this.getZMove(decision.move, decision.pokemon, true);
let zMoveName = this.getZMove(action.move, action.pokemon, true);
let zMove = this.getMove(zMoveName);
if (zMove.exists) {
move = zMove;
}
}
let priority = this.runEvent('ModifyPriority', decision.pokemon, target, move, move.priority);
decision.priority = priority;
let priority = this.runEvent('ModifyPriority', action.pokemon, target, move, move.priority);
action.priority = priority;
// In Gen 6, Quick Guard blocks moves with artificially enhanced priority.
if (this.gen > 5) decision.move.priority = priority;
if (this.gen > 5) action.move.priority = priority;
}
}
if (!decision.pokemon && !decision.speed) decision.speed = 1;
if (!decision.speed && (decision.choice === 'switch' || decision.choice === 'instaswitch') && decision.target) decision.speed = decision.target.getDecisionSpeed();
if (!decision.speed && !deferPriority) decision.speed = decision.pokemon.getDecisionSpeed();
if (!action.speed) {
if ((action.choice === 'switch' || action.choice === 'instaswitch') && action.target) {
action.speed = action.target.getActionSpeed();
} else if (!action.pokemon) {
action.speed = 1;
} else if (!deferPriority) {
action.speed = action.pokemon.getActionSpeed();
}
}
return /** @type {any} */ (action);
}
/**
* Adds the action last in the queue. Mostly used before sortQueue.
*
* @param {AnyObject | AnyObject[]} action
*/
addQueue(action) {
addToQueue(action) {
if (Array.isArray(action)) {
for (let i = 0; i < action.length; i++) {
this.addQueue(action[i]);
this.addToQueue(action[i]);
}
return;
}
if (action.choice === 'pass') return;
this.resolvePriority(action);
this.queue.push(action);
this.queue.push(this.resolveAction(action));
}
sortQueue() {
@ -2554,52 +2606,58 @@ class Battle extends Dex.ModdedDex {
}
/**
* @param {AnyObject | AnyObject[]} decision
* Inserts the passed action into the action queue when it normally
* would have happened (sorting by priority/speed), without
* re-sorting the existing actions.
*
* @param {AnyObject | AnyObject[]} chosenAction
* @param {boolean} [midTurn]
*/
insertQueue(decision, midTurn = false) {
if (Array.isArray(decision)) {
for (let i = 0; i < decision.length; i++) {
this.insertQueue(decision[i]);
insertQueue(chosenAction, midTurn = false) {
if (Array.isArray(chosenAction)) {
for (const subAction of chosenAction) {
this.insertQueue(subAction);
}
return;
}
if (decision.pokemon) decision.pokemon.updateSpeed();
this.resolvePriority(decision, midTurn);
if (chosenAction.pokemon) chosenAction.pokemon.updateSpeed();
const action = this.resolveAction(chosenAction, midTurn);
for (let i = 0; i < this.queue.length; i++) {
if (Battle.comparePriority(decision, this.queue[i]) < 0) {
this.queue.splice(i, 0, decision);
if (Battle.comparePriority(action, this.queue[i]) < 0) {
this.queue.splice(i, 0, action);
return;
}
}
this.queue.push(decision);
this.queue.push(action);
}
/**
* @param {AnyObject} decision
* Makes the passed move action happen next (skipping speed order).
*
* @param {MoveAction} action
* @param {Pokemon} [source]
* @param {Effect} [sourceEffect]
*/
prioritizeQueue(decision, source, sourceEffect) {
prioritizeAction(action, source, sourceEffect) {
if (this.event) {
if (!source) source = this.event.source;
if (!sourceEffect) sourceEffect = this.effect;
}
for (let i = 0; i < this.queue.length; i++) {
if (this.queue[i] === decision) {
for (const [i, curAction] of this.queue.entries()) {
if (curAction === action) {
this.queue.splice(i, 1);
break;
}
}
decision.sourceEffect = sourceEffect;
this.queue.unshift(decision);
action.sourceEffect = sourceEffect;
this.queue.unshift(action);
}
willAct() {
for (let i = 0; i < this.queue.length; i++) {
if (this.queue[i].choice === 'move' || this.queue[i].choice === 'switch' || this.queue[i].choice === 'instaswitch' || this.queue[i].choice === 'shift') {
return this.queue[i];
for (const action of this.queue) {
if (action.choice === 'move' || action.choice === 'switch' || action.choice === 'instaswitch' || action.choice === 'shift') {
return action;
}
}
return null;
@ -2610,9 +2668,9 @@ class Battle extends Dex.ModdedDex {
*/
willMove(pokemon) {
if (pokemon.fainted) return false;
for (let i = 0; i < this.queue.length; i++) {
if (this.queue[i].choice === 'move' && this.queue[i].pokemon === pokemon) {
return this.queue[i];
for (const action of this.queue) {
if (action.choice === 'move' && action.pokemon === pokemon) {
return action;
}
}
return null;
@ -2621,7 +2679,7 @@ class Battle extends Dex.ModdedDex {
/**
* @param {Pokemon} pokemon
*/
cancelDecision(pokemon) {
cancelAction(pokemon) {
let success = false;
this.queue = this.queue.filter(action => {
if (action.pokemon === pokemon && action.priority >= -100) {
@ -2637,8 +2695,8 @@ class Battle extends Dex.ModdedDex {
* @param {Pokemon} pokemon
*/
cancelMove(pokemon) {
for (let i = 0; i < this.queue.length; i++) {
if (this.queue[i].choice === 'move' && this.queue[i].pokemon === pokemon) {
for (const [i, action] of this.queue.entries()) {
if (action.choice === 'move' && action.pokemon === pokemon) {
this.queue.splice(i, 1);
return true;
}
@ -2650,20 +2708,20 @@ class Battle extends Dex.ModdedDex {
* @param {Pokemon} pokemon
*/
willSwitch(pokemon) {
for (let i = 0; i < this.queue.length; i++) {
if ((this.queue[i].choice === 'switch' || this.queue[i].choice === 'instaswitch') && this.queue[i].pokemon === pokemon) {
return this.queue[i];
for (const action of this.queue) {
if ((action.choice === 'switch' || action.choice === 'instaswitch') && action.pokemon === pokemon) {
return action;
}
}
return false;
}
/**
* @param {AnyObject} decision
* @param {Action} action
*/
runDecision(decision) {
runAction(action) {
// returns whether or not we ended in a callback
switch (decision.choice) {
switch (action.choice) {
case 'start': {
// I GIVE UP, WILL WRESTLE WITH EVENT SYSTEM LATER
let format = this.getFormat();
@ -2700,56 +2758,54 @@ class Battle extends Dex.ModdedDex {
}
case 'move':
if (!decision.pokemon.isActive) return false;
if (decision.pokemon.fainted) return false;
if (!action.pokemon.isActive) return false;
if (action.pokemon.fainted) return false;
// @ts-ignore
this.runMove(decision.move, decision.pokemon, decision.targetLoc, decision.sourceEffect, decision.zmove);
this.runMove(action.move, action.pokemon, action.targetLoc, action.sourceEffect, action.zmove);
break;
case 'megaEvo':
// @ts-ignore
this.runMegaEvo(decision.pokemon);
this.runMegaEvo(action.pokemon);
break;
case 'beforeTurnMove': {
if (!decision.pokemon.isActive) return false;
if (decision.pokemon.fainted) return false;
this.debug('before turn callback: ' + decision.move.id);
let target = this.getTarget(decision.pokemon, decision.move, decision.targetLoc);
if (!action.pokemon.isActive) return false;
if (action.pokemon.fainted) return false;
this.debug('before turn callback: ' + action.move.id);
let target = this.getTarget(action.pokemon, action.move, action.targetLoc);
if (!target) return false;
decision.move.beforeTurnCallback.call(this, decision.pokemon, target);
if (!action.move.beforeTurnCallback) throw new Error(`beforeTurnMove has no beforeTurnCallback`);
action.move.beforeTurnCallback.call(this, action.pokemon, target);
break;
}
case 'event':
this.runEvent(decision.event, decision.pokemon);
// @ts-ignore Easier than defining a custom event attribute tbh
this.runEvent(action.event, action.pokemon);
break;
case 'team': {
decision.side.pokemon.splice(decision.index, 0, decision.pokemon);
decision.pokemon.position = decision.index;
action.pokemon.side.pokemon.splice(action.index, 0, action.pokemon);
action.pokemon.position = action.index;
// we return here because the update event would crash since there are no active pokemon yet
return;
}
case 'pass':
if (!decision.priority || decision.priority <= 101) return;
if (decision.pokemon) {
decision.pokemon.switchFlag = false;
}
break;
return;
case 'instaswitch':
case 'switch':
if (decision.choice === 'switch' && decision.pokemon.status && this.data.Abilities.naturalcure) {
this.singleEvent('CheckShow', this.getAbility('naturalcure'), null, decision.pokemon);
if (action.choice === 'switch' && action.pokemon.status && this.data.Abilities.naturalcure) {
this.singleEvent('CheckShow', this.getAbility('naturalcure'), null, action.pokemon);
}
if (decision.pokemon.hp) {
decision.pokemon.beingCalledBack = true;
let lastMove = this.getMove(decision.pokemon.lastMove);
if (action.pokemon.hp) {
action.pokemon.beingCalledBack = true;
let lastMove = this.getMove(action.pokemon.lastMove);
if (lastMove.selfSwitch !== 'copyvolatile') {
this.runEvent('BeforeSwitchOut', decision.pokemon);
this.runEvent('BeforeSwitchOut', action.pokemon);
if (this.gen >= 5) {
this.eachEvent('Update');
}
}
if (!this.runEvent('SwitchOut', decision.pokemon)) {
if (!this.runEvent('SwitchOut', action.pokemon)) {
// Warning: DO NOT interrupt a switch-out
// if you just want to trap a pokemon.
// To trap a pokemon and prevent it from switching out,
@ -2761,14 +2817,14 @@ class Battle extends Dex.ModdedDex {
break;
}
}
decision.pokemon.illusion = null;
this.singleEvent('End', this.getAbility(decision.pokemon.ability), decision.pokemon.abilityData, decision.pokemon);
if (!decision.pokemon.hp && !decision.pokemon.fainted) {
action.pokemon.illusion = null;
this.singleEvent('End', this.getAbility(action.pokemon.ability), action.pokemon.abilityData, action.pokemon);
if (!action.pokemon.hp && !action.pokemon.fainted) {
// a pokemon fainted from Pursuit before it could switch
if (this.gen <= 4) {
// in gen 2-4, the switch still happens
decision.priority = -101;
this.queue.unshift(decision);
action.priority = -101;
this.queue.unshift(action);
this.add('-hint', 'Pursuit target fainted, switch continues in gen 2-4');
break;
}
@ -2776,51 +2832,51 @@ class Battle extends Dex.ModdedDex {
this.debug('A Pokemon can\'t switch between when it runs out of HP and when it faints');
break;
}
if (decision.target.isActive) {
if (action.target.isActive) {
this.add('-hint', 'Switch failed; switch target is already active');
break;
}
if (decision.choice === 'switch' && decision.pokemon.activeTurns === 1) {
let foeActive = decision.pokemon.side.foe.active;
if (action.choice === 'switch' && action.pokemon.activeTurns === 1) {
let foeActive = action.pokemon.side.foe.active;
for (let i = 0; i < foeActive.length; i++) {
if (foeActive[i].isStale >= 2) {
decision.pokemon.isStaleCon++;
decision.pokemon.isStaleSource = 'switch';
action.pokemon.isStaleCon++;
action.pokemon.isStaleSource = 'switch';
break;
}
}
}
this.switchIn(decision.target, decision.pokemon.position);
this.switchIn(action.target, action.pokemon.position);
break;
case 'runUnnerve':
this.singleEvent('PreStart', decision.pokemon.getAbility(), decision.pokemon.abilityData, decision.pokemon);
this.singleEvent('PreStart', action.pokemon.getAbility(), action.pokemon.abilityData, action.pokemon);
break;
case 'runSwitch':
this.runEvent('SwitchIn', decision.pokemon);
if (this.gen <= 2 && !decision.pokemon.side.faintedThisTurn && decision.pokemon.draggedIn !== this.turn) this.runEvent('AfterSwitchInSelf', decision.pokemon);
if (!decision.pokemon.hp) break;
decision.pokemon.isStarted = true;
if (!decision.pokemon.fainted) {
this.singleEvent('Start', decision.pokemon.getAbility(), decision.pokemon.abilityData, decision.pokemon);
decision.pokemon.abilityOrder = this.abilityOrder++;
this.singleEvent('Start', decision.pokemon.getItem(), decision.pokemon.itemData, decision.pokemon);
this.runEvent('SwitchIn', action.pokemon);
if (this.gen <= 2 && !action.pokemon.side.faintedThisTurn && action.pokemon.draggedIn !== this.turn) this.runEvent('AfterSwitchInSelf', action.pokemon);
if (!action.pokemon.hp) break;
action.pokemon.isStarted = true;
if (!action.pokemon.fainted) {
this.singleEvent('Start', action.pokemon.getAbility(), action.pokemon.abilityData, action.pokemon);
action.pokemon.abilityOrder = this.abilityOrder++;
this.singleEvent('Start', action.pokemon.getItem(), action.pokemon.itemData, action.pokemon);
}
delete decision.pokemon.draggedIn;
delete action.pokemon.draggedIn;
break;
case 'runPrimal':
if (!decision.pokemon.transformed) this.singleEvent('Primal', decision.pokemon.getItem(), decision.pokemon.itemData, decision.pokemon);
if (!action.pokemon.transformed) this.singleEvent('Primal', action.pokemon.getItem(), action.pokemon.itemData, action.pokemon);
break;
case 'shift': {
if (!decision.pokemon.isActive) return false;
if (decision.pokemon.fainted) return false;
decision.pokemon.activeTurns--;
this.swapPosition(decision.pokemon, 1);
let foeActive = decision.pokemon.side.foe.active;
if (!action.pokemon.isActive) return false;
if (action.pokemon.fainted) return false;
action.pokemon.activeTurns--;
this.swapPosition(action.pokemon, 1);
let foeActive = action.pokemon.side.foe.active;
for (let i = 0; i < foeActive.length; i++) {
if (foeActive[i].isStale >= 2) {
decision.pokemon.isStaleCon++;
decision.pokemon.isStaleSource = 'switch';
action.pokemon.isStaleCon++;
action.pokemon.isStaleSource = 'switch';
break;
}
}
@ -2837,9 +2893,6 @@ class Battle extends Dex.ModdedDex {
this.residualEvent('Residual');
this.add('upkeep');
break;
case 'skip':
throw new Error("Decision illegally skipped!");
}
// phazing (Roar, etc)
@ -2871,17 +2924,14 @@ class Battle extends Dex.ModdedDex {
// in gen 3 or earlier, switching in fainted pokemon is done after
// every move, rather than only at the end of the turn.
this.checkFainted();
} else if (decision.choice === 'pass') {
} else if (action.choice === 'megaEvo' && this.gen >= 7) {
this.eachEvent('Update');
return false;
} else if (decision.choice === 'megaEvo' && this.gen >= 7) {
this.eachEvent('Update');
// In Gen 7, the decision order is recalculated for a Pokémon that mega evolves.
const moveIndex = this.queue.findIndex(queuedDecision => queuedDecision.pokemon === decision.pokemon && queuedDecision.choice === 'move');
// 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 moveDecision = this.queue.splice(moveIndex, 1)[0];
moveDecision.mega = 'done';
this.insertQueue(moveDecision, true);
const moveAction = /** @type {MoveAction} */ (this.queue.splice(moveIndex, 1)[0]);
moveAction.mega = 'done';
this.insertQueue(moveAction, true);
}
return false;
} else if (this.queue.length && this.queue[0].choice === 'instaswitch') {
@ -2924,16 +2974,16 @@ class Battle extends Dex.ModdedDex {
}
if (!this.midTurn) {
this.queue.push({choice: 'residual', priority: -100});
this.queue.unshift({choice: 'beforeTurn', priority: 100});
this.queue.push(this.resolveAction({choice: 'residual'}));
this.queue.unshift(this.resolveAction({choice: 'beforeTurn'}));
this.midTurn = true;
}
while (this.queue.length) {
let decision = this.queue[0];
let action = this.queue[0];
this.queue.shift();
this.runDecision(decision);
this.runAction(action);
if (this.currentRequest) {
return;
@ -2948,19 +2998,19 @@ class Battle extends Dex.ModdedDex {
}
/**
* Changes a pokemon's decision, and inserts its new decision
* Changes a pokemon's action, and inserts its new action
* in priority order.
*
* You'd normally want the OverrideDecision event (which doesn't
* You'd normally want the OverrideAction event (which doesn't
* change priority order).
*
* @param {Pokemon} pokemon
* @param {AnyObject} decision
* @param {AnyObject} action
*/
changeDecision(pokemon, decision) {
this.cancelDecision(pokemon);
if (!decision.pokemon) decision.pokemon = pokemon;
this.insertQueue(decision);
changeAction(pokemon, action) {
this.cancelAction(pokemon);
if (!action.pokemon) action.pokemon = pokemon;
this.insertQueue(action);
}
/**
@ -2977,7 +3027,7 @@ class Battle extends Dex.ModdedDex {
if (!side.choose(input)) return false;
this.checkDecisions();
this.checkActions();
return true;
}
@ -3009,7 +3059,7 @@ class Battle extends Dex.ModdedDex {
this.LEGACY_API_DO_NOT_USE = oldFlag;
this.add('choice', this.p1.getChoice, this.p2.getChoice);
for (const side of this.sides) {
this.addQueue(side.choice.actions);
this.addToQueue(side.choice.actions);
}
this.sortQueue();
@ -3042,17 +3092,17 @@ class Battle extends Dex.ModdedDex {
/**
* returns true if both decisions are complete
*/
checkDecisions() {
let totalDecisions = 0;
checkActions() {
let totalActions = 0;
if (this.p1.isChoiceDone()) {
if (!this.supportCancel) this.p1.choice.cantUndo = true;
totalDecisions++;
totalActions++;
}
if (this.p2.isChoiceDone()) {
if (!this.supportCancel) this.p2.choice.cantUndo = true;
totalDecisions++;
totalActions++;
}
if (totalDecisions >= this.sides.length) {
if (totalActions >= this.sides.length) {
this.commitDecisions();
return true;
}
@ -3277,7 +3327,6 @@ class Battle extends Dex.ModdedDex {
this.p2 = null;
for (let i = 0; i < this.queue.length; i++) {
delete this.queue[i].pokemon;
delete this.queue[i].side;
}
this.queue = [];

View File

@ -243,7 +243,7 @@ class Pokemon {
}
updateSpeed() {
this.speed = this.getDecisionSpeed();
this.speed = this.getActionSpeed();
}
/**
@ -346,7 +346,7 @@ class Pokemon {
return stat;
}
getDecisionSpeed() {
getActionSpeed() {
let speed = this.getStat('spe', false, false);
if (speed > 10000) speed = 10000;
if (this.battle.getPseudoWeather('trickroom')) {
@ -514,14 +514,17 @@ class Pokemon {
};
}
/**
* @return {string | null}
*/
getLockedMove() {
let lockedMove = this.battle.runEvent('LockMove', this);
if (lockedMove === true) lockedMove = false;
if (lockedMove === true) lockedMove = null;
return lockedMove;
}
/**
* @param {string} [lockedMove]
* @param {string?} [lockedMove]
* @param {boolean} [restrictData]
*/
getMoves(lockedMove, restrictData) {

View File

@ -11,17 +11,17 @@ const Pokemon = require('./pokemon');
/**
* An object representing a single action that can be chosen.
*
* @typedef {Object} Action
* @property {string} choice - a choice
* @property {Pokemon} [pokemon] - the pokemon making the choice
* @property {number} [targetLoc] - location of the target, relative to pokemon's side
* @property {string} [move] - a move to use
* @property {Pokemon} [target] - the target of the choice
* @property {number} [index] - the chosen index in team preview
* @property {number} [priority] - priority of the chosen index
* @property {Side} [side] - the pokemon's side
* @typedef {Object} ChosenAction
* @property {'move' | 'switch' | 'instaswitch' | 'team' | 'shift' | 'pass'} choice - action type
* @property {Pokemon} [pokemon] - the pokemon doing the action
* @property {number} [targetLoc] - relative location of the target to pokemon (move action only)
* @property {string} [moveid] - a move to use (move action only)
* @property {Pokemon} [target] - the target of the action
* @property {number} [index] - the chosen index in Team Preview
* @property {Side} [side] - the action's side
* @property {?boolean} [mega] - true if megaing or ultra bursting
* @property {?boolean} [zmove] - true if zmoving
* @property {number} [priority] - priority of the action
*/
/**
@ -30,7 +30,7 @@ const Pokemon = require('./pokemon');
* @typedef {Object} Choice
* @property {boolean} cantUndo - true if the choice can't be cancelled because of the maybeTrapped issue
* @property {string} error - contains error text in the case of a choice error
* @property {Action[]} actions - array of chosen actions
* @property {ChosenAction[]} actions - array of chosen actions
* @property {number} forcedSwitchesLeft - number of switches left that need to be performed
* @property {number} forcedPassesLeft - number of passes left that need to be performed
* @property {Set<number>} switchIns - indexes of pokemon chosen to switch in
@ -128,7 +128,7 @@ class Side {
if (action.targetLoc && this.active.length > 1) details += ` ${action.targetLoc}`;
if (action.mega) details += ` mega`;
if (action.zmove) details += ` zmove`;
return `move ${toId(action.move)}${details}`;
return `move ${action.moveid}${details}`;
case 'switch':
case 'instaswitch':
return `switch ${action.target.position + 1}`;
@ -307,7 +307,7 @@ class Side {
if (!targetLoc) targetLoc = 0;
// Parse moveText (name or index)
// If the move is not found, the decision is invalid without requiring further inspection.
// If the move is not found, the action is invalid without requiring further inspection.
const requestMoves = pokemon.getRequestData().moves;
let moveid = '';
@ -390,11 +390,11 @@ class Side {
choice: 'move',
pokemon: pokemon,
targetLoc: lockedMoveTarget || 0,
move: lockedMove,
moveid: toId(lockedMove),
});
return true;
} else if (!moves.length && !zMove) {
// Override decision and use Struggle if there are no enabled moves with PP
// Override action and use Struggle if there are no enabled moves with PP
// Gen 4 and earlier announce a Pokemon has no moves left before the turn begins, and only to that player's side.
if (this.battle.gen <= 4) this.send('-activate', pokemon, 'move: Struggle');
moveid = 'struggle';
@ -445,7 +445,7 @@ class Side {
choice: 'move',
pokemon: pokemon,
targetLoc: targetLoc,
move: moveid,
moveid: moveid,
mega: mega || ultra,
zmove: zMove,
});
@ -458,7 +458,7 @@ class Side {
if (ultra) this.choice.ultra = true;
if (zMove) this.choice.zMove = true;
if (this.battle.LEGACY_API_DO_NOT_USE && !this.battle.checkDecisions()) return this;
if (this.battle.LEGACY_API_DO_NOT_USE && !this.battle.checkActions()) return this;
return true;
}
@ -534,7 +534,7 @@ class Side {
target: targetPokemon,
});
if (this.battle.LEGACY_API_DO_NOT_USE && !this.battle.checkDecisions()) return this;
if (this.battle.LEGACY_API_DO_NOT_USE && !this.battle.checkActions()) return this;
return true;
}
@ -586,7 +586,7 @@ class Side {
});
}
if (this.battle.LEGACY_API_DO_NOT_USE && !this.battle.checkDecisions()) return this;
if (this.battle.LEGACY_API_DO_NOT_USE && !this.battle.checkActions()) return this;
return true;
}
@ -610,7 +610,7 @@ class Side {
pokemon: pokemon,
});
if (this.battle.LEGACY_API_DO_NOT_USE && !this.battle.checkDecisions()) return this;
if (this.battle.LEGACY_API_DO_NOT_USE && !this.battle.checkActions()) return this;
return true;
}
@ -765,7 +765,7 @@ class Side {
this.choice.actions.push({
choice: 'pass',
});
if (this.battle.LEGACY_API_DO_NOT_USE && !this.battle.checkDecisions()) return this;
if (this.battle.LEGACY_API_DO_NOT_USE && !this.battle.checkActions()) return this;
return true;
}

View File

@ -36,7 +36,7 @@ for every move:
[ModifyPriority]
move's [BeforeTurn]
runDecision() - runs runSwitch, runAfterSwitch, and runMove in priority order, then residual at end {
runAction() - runs runSwitch, runAfterSwitch, and runMove in priority order, then residual at end {
runSwitch() {
[BeforeSwitch]
switch

View File

@ -13,7 +13,6 @@ const RULE_FLAGS = {
preview: 4,
sleepClause: 8,
cancel: 16,
partialDecisions: 32,
};
function capitalize(word) {
@ -86,7 +85,6 @@ class TestTools {
if (options.preview) format.ruleset.push('Team Preview');
if (options.sleepClause) format.ruleset.push('Sleep Clause Mod');
if (options.cancel) format.ruleset.push('Cancel Mod');
// if (options.partialDecisions) format.ruleset.push('Partial Decisions');
this.dex.installFormat(formatId, format);
return format;
@ -115,7 +113,6 @@ class TestTools {
prng
);
battle.LEGACY_API_DO_NOT_USE = true;
if (options && options.partialDecisions) battle.supportPartialDecisions = true;
if (teams) {
for (let i = 0; i < teams.length; i++) {
assert(Array.isArray(teams[i]), "Team provided is not an array");

View File

@ -14,15 +14,15 @@ describe('Choice parser', function () {
[{species: "Rhydon", ability: 'prankster', moves: ['splash']}],
]);
const validDecision = 'team 1';
assert(battle.choose('p1', validDecision));
const validChoice = 'team 1';
assert(battle.choose('p1', validChoice));
battle.p1.clearChoice();
assert(battle.choose('p2', validDecision));
assert(battle.choose('p2', validChoice));
battle.p1.clearChoice();
const badDecisions = ['move 1', 'move 2 mega', 'switch 1', 'pass', 'shift'];
for (const badDecision of badDecisions) {
assert.false(battle.choose('p1', badDecision), `Decision '${badDecision}' should be rejected`);
const badChoices = ['move 1', 'move 2 mega', 'switch 1', 'pass', 'shift'];
for (const badChoice of badChoices) {
assert.false(battle.choose('p1', badChoice), `Choice '${badChoice}' should be rejected`);
}
});
@ -68,13 +68,13 @@ describe('Choice parser', function () {
battle.commitDecisions();
const badDecisions = ['move 1', 'move 2 mega', 'team 1', 'pass', 'shift'];
for (const badDecision of badDecisions) {
assert.false(battle.choose('p1', badDecision), `Decision '${badDecision}' should be rejected`);
const badChoices = ['move 1', 'move 2 mega', 'team 1', 'pass', 'shift'];
for (const badChoice of badChoices) {
assert.false(battle.choose('p1', badChoice), `Choice '${badChoice}' should be rejected`);
}
const validDecision = 'switch Bulbasaur';
assert(battle.choose('p1', validDecision));
const validChoice = 'switch Bulbasaur';
assert(battle.choose('p1', validChoice));
battle.p1.clearChoice();
});
});
@ -94,12 +94,12 @@ describe('Choice parser', function () {
]);
battle.commitDecisions(); // Both p1 active Pokémon faint
const badDecisions = ['move 1', 'move 2 mega', 'team 1', 'shift'];
for (const badDecision of badDecisions) {
assert.false(battle.choose('p1', badDecision), `Decision '${badDecision}' should be rejected`);
const badChoices = ['move 1', 'move 2 mega', 'team 1', 'shift'];
for (const badChoice of badChoices) {
assert.false(battle.choose('p1', badChoice), `Choice '${badChoice}' should be rejected`);
}
assert(battle.choose('p1', `pass, switch 3`), `Decision 'pass, switch 3' should be valid`);
assert(battle.choose('p1', `pass, switch 3`), `Choice 'pass, switch 3' should be valid`);
});
it('should reject choice details for `pass` choices', function () {
@ -150,15 +150,15 @@ describe('Choice parser', function () {
{species: "Charmander", ability: 'blaze', moves: ['tackle', 'growl']},
]);
const validDecisions = ['move 1', 'move 2', 'switch 2'];
for (const decision of validDecisions) {
assert(battle.choose('p1', decision), `Decision '${decision}' should be valid`);
const validChoices = ['move 1', 'move 2', 'switch 2'];
for (const action of validChoices) {
assert(battle.choose('p1', action), `Choice '${action}' should be valid`);
battle.p1.clearChoice();
}
const badDecisions = ['move 1 zmove', 'move 2 mega', 'team 1', 'pass', 'shift'];
for (const badDecision of badDecisions) {
assert.false(battle.choose('p1', badDecision), `Decision '${badDecision}' should be rejected`);
const badChoices = ['move 1 zmove', 'move 2 mega', 'team 1', 'pass', 'shift'];
for (const badChoice of badChoices) {
assert.false(battle.choose('p1', badChoice), `Choice '${badChoice}' should be rejected`);
}
});
});
@ -183,7 +183,7 @@ describe('Choice parser', function () {
assert.false.fainted(p1.active[1]);
assert(battle.choose('p1', 'move smog 2'));
assert.strictEqual(battle.p1.getChoice(true), `pass, move smog 2`, `Decision mismatch`);
assert.strictEqual(battle.p1.getChoice(true), `pass, move smog 2`, `Choice mismatch`);
});
});
@ -202,18 +202,18 @@ describe('Choice parser', function () {
{species: "Golem", ability: 'sturdy', moves: ['defensecurl']},
]);
const validDecisions = ['move 1', 'switch 4'];
const validChoices = ['move 1', 'switch 4'];
validDecisions.forEach(decision => {
const choiceString = `move 1, ${decision}, move 1 1`;
assert(battle.choose('p1', choiceString), `Decision '${choiceString}' should be valid`);
validChoices.forEach(action => {
const choiceString = `move 1, ${action}, move 1 1`;
assert(battle.choose('p1', choiceString), `Choice '${choiceString}' should be valid`);
battle.p1.clearChoice();
});
const badDecisions = ['move 1 zmove', 'move 2 mega', 'team 1', 'pass', 'shift'];
for (const badDecision of badDecisions) {
const choiceString = `move 1, ${badDecision}, move 1 1`;
assert.false(battle.choose('p1', choiceString), `Decision '${choiceString}' should be rejected`);
const badChoices = ['move 1 zmove', 'move 2 mega', 'team 1', 'pass', 'shift'];
for (const badChoice of badChoices) {
const choiceString = `move 1, ${badChoice}, move 1 1`;
assert.false(battle.choose('p1', choiceString), `Choice '${choiceString}' should be rejected`);
}
});
@ -232,18 +232,18 @@ describe('Choice parser', function () {
{species: "Magnezone", ability: 'magnetpull', moves: ['discharge']},
]);
const validDecisions = ['move 1', 'switch 4', 'shift'];
const validChoices = ['move 1', 'switch 4', 'shift'];
validDecisions.forEach(decision => {
const choiceString = `${decision}, move 1, move 1 1`;
assert(battle.choose('p1', choiceString), `Decision '${choiceString}' should be valid`);
validChoices.forEach(action => {
const choiceString = `${action}, move 1, move 1 1`;
assert(battle.choose('p1', choiceString), `Choice '${choiceString}' should be valid`);
battle.p1.clearChoice();
});
const badDecisions = ['move 1 zmove', 'move 2 mega', 'team 1', 'pass'];
for (const badDecision of badDecisions) {
const choiceString = `${badDecision}, move 1, move 1 1`;
assert.false(battle.choose('p1', choiceString), `Decision '${choiceString}' should be rejected`);
const badChoices = ['move 1 zmove', 'move 2 mega', 'team 1', 'pass'];
for (const badChoice of badChoices) {
const choiceString = `${badChoice}, move 1, move 1 1`;
assert.false(battle.choose('p1', choiceString), `Choice '${choiceString}' should be rejected`);
}
});
@ -262,18 +262,18 @@ describe('Choice parser', function () {
{species: "Magnezone", ability: 'magnetpull', moves: ['discharge']},
]);
const validDecisions = ['move 1 1', 'switch 4', 'shift'];
const validChoices = ['move 1 1', 'switch 4', 'shift'];
validDecisions.forEach(decision => {
const choiceString = `move 1, move 1, ${decision}`;
assert(battle.choose('p1', choiceString), `Decision '${choiceString}' should be valid`);
validChoices.forEach(action => {
const choiceString = `move 1, move 1, ${action}`;
assert(battle.choose('p1', choiceString), `Choice '${choiceString}' should be valid`);
battle.p1.clearChoice();
});
const badDecisions = ['move 1 zmove', 'move 2 mega', 'team 1', 'pass', 'shift blah'];
for (const badDecision of badDecisions) {
const choiceString = `move 1, move 1, ${badDecision}`;
assert.false(battle.choose('p1', choiceString), `Decision '${choiceString}' should be rejected`);
const badChoices = ['move 1 zmove', 'move 2 mega', 'team 1', 'pass', 'shift blah'];
for (const badChoice of badChoices) {
const choiceString = `move 1, move 1, ${badChoice}`;
assert.false(battle.choose('p1', choiceString), `Choice '${choiceString}' should be rejected`);
}
});
@ -295,18 +295,18 @@ describe('Choice parser', function () {
p1.choosePass().chooseSwitch(4).chooseDefault(); // Forretress switches in to slot #2
assert.species(p1.active[1], 'Forretress');
const validDecisions = ['move spikes', 'move 1'];
validDecisions.forEach(decision => {
battle.choose('p1', decision);
const validChoices = ['move spikes', 'move 1'];
validChoices.forEach(action => {
battle.choose('p1', action);
assert.strictEqual(battle.p1.getChoice(true), `pass, move spikes, pass`);
battle.p1.clearChoice();
battle.choose('p1', `pass, ${decision}, pass`);
battle.choose('p1', `pass, ${action}, pass`);
assert.strictEqual(battle.p1.getChoice(true), `pass, move spikes, pass`);
battle.p1.clearChoice();
battle.choose('p1', `pass, ${decision}`);
battle.choose('p1', `pass, ${action}`);
assert.strictEqual(battle.p1.getChoice(true), `pass, move spikes, pass`);
battle.p1.clearChoice();
battle.choose('p1', `${decision}, pass`);
battle.choose('p1', `${action}, pass`);
assert.strictEqual(battle.p1.getChoice(true), `pass, move spikes, pass`);
battle.p1.clearChoice();
});

View File

@ -98,13 +98,13 @@ const TRIPLES_TEAMS = {
let battle;
describe('Decisions', function () {
describe('Choices', function () {
afterEach(function () {
battle.destroy();
});
describe('Generic', function () {
it('should wait for players to send their decisions and run them as soon as they are all received', function (done) {
it('should wait for players to send their choicess and run them as soon as they are all received', function (done) {
battle = common.createBattle();
battle.join('p1', 'Guest 1', 1, [{species: "Mew", ability: 'synchronize', moves: ['recover']}]);
battle.join('p2', 'Guest 2', 1, [{species: "Rhydon", ability: 'prankster', moves: ['sketch']}]);
@ -499,7 +499,7 @@ describe('Decisions', function () {
}
});
it('should autocomplete a single-slot decision in Singles for no Illusion', function () {
it('should autocomplete a single-slot action in Singles for no Illusion', function () {
// Backwards-compatibility with the client. It should be useful for 3rd party bots/clients (Android?)
for (let i = 0; i < 5; i++) {
battle = common.createBattle({preview: true}, SINGLES_TEAMS.full);
@ -736,11 +736,11 @@ describe('Decisions', function () {
});
});
describe('Decision extensions', function () {
describe('Choice extensions', function () {
describe('Undo', function () {
const MODES = ['revoke', 'override'];
for (const mode of MODES) {
it(`should disallow to ${mode} decisions after every player has sent an unrevoked decision`, function () {
it(`should disallow to ${mode} decisions after every player has sent an unrevoked action`, function () {
battle = common.createBattle({cancel: true});
battle.join('p1', 'Guest 1', 1, [{species: "Bulbasaur", ability: 'overgrow', moves: ['tackle', 'growl']}]);
battle.join('p2', 'Guest 2', 1, [{species: "Charmander", ability: 'blaze', moves: ['tackle', 'growl']}]);
@ -1113,7 +1113,7 @@ describe('Decision extensions', function () {
['Deoxys-Attack', 'Chikorita'].forEach((species, index) => assert.species(battle.p1.active[index], species));
});
it(`should support to ${mode} team order decision on team preview requests`, function () {
it(`should support to ${mode} team order action on team preview requests`, function () {
battle = common.createBattle({preview: true, cancel: true}, [
[{species: 'Bulbasaur', ability: 'overgrow', moves: ['tackle']}, {species: 'Ivysaur', ability: 'overgrow', moves: ['tackle']}],
[{species: 'Charmander', ability: 'blaze', moves: ['scratch']}, {species: 'Charmeleon', ability: 'blaze', moves: ['scratch']}],
@ -1140,7 +1140,7 @@ describe('Decision extensions', function () {
['Bulbasaur', 'Ivysaur'].forEach((species, index) => assert.species(battle.p1.pokemon[index], species));
});
it(`should disallow to ${mode} team order decision on team preview requests by default`, function () {
it(`should disallow to ${mode} team order action on team preview requests by default`, function () {
battle = common.createBattle({preview: true}, [
[{species: 'Bulbasaur', ability: 'overgrow', moves: ['tackle']}, {species: 'Ivysaur', ability: 'overgrow', moves: ['tackle']}],
[{species: 'Charmander', ability: 'blaze', moves: ['scratch']}, {species: 'Charmeleon', ability: 'blaze', moves: ['scratch']}],
@ -1157,7 +1157,7 @@ describe('Decision extensions', function () {
});
});
describe('Decision internals', function () {
describe('Choice internals', function () {
afterEach(function () {
battle.destroy();
});
@ -1248,7 +1248,7 @@ describe('Decision internals', function () {
assert.strictEqual(p1.active[1].name, 'Ekans');
});
it('should empty the decisions list when undoing a move', function () {
it('should empty the actions list when undoing a move', function () {
battle = common.createBattle({gameType: 'doubles', cancel: true});
const p1 = battle.join('p1', 'Guest 1', 1, [
{species: "Pineco", ability: 'sturdy', moves: ['selfdestruct']},
@ -1271,7 +1271,7 @@ describe('Decision internals', function () {
assert.fainted(p1.active[1]);
});
it('should empty the decisions list when undoing a switch', function () {
it('should empty the actions list when undoing a switch', function () {
battle = common.createBattle({gameType: 'doubles', cancel: true});
const p1 = battle.join('p1', 'Guest 1', 1, [
{species: "Pineco", ability: 'sturdy', moves: ['selfdestruct']},
@ -1295,7 +1295,7 @@ describe('Decision internals', function () {
assert.species(p1.active[1], 'Koffing');
});
it('should empty the decisions list when undoing a pass', function () {
it('should empty the actions list when undoing a pass', function () {
battle = common.createBattle({gameType: 'doubles', cancel: true});
const p1 = battle.join('p1', 'Guest 1', 1, [
{species: "Pineco", ability: 'sturdy', moves: ['selfdestruct']},
@ -1319,7 +1319,7 @@ describe('Decision internals', function () {
assert.species(p1.active[1], 'Koffing');
});
it('should empty the decisions list when undoing a shift', function () {
it('should empty the actions list when undoing a shift', function () {
battle = common.createBattle({gameType: 'triples', cancel: true});
battle.supportCancel = true;
const p1 = battle.join('p1', 'Guest 1', 1, [