From 0f3e3cc6a1248ef8fdf9cf0fce5973a36212d7e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= Date: Wed, 6 Aug 2025 22:14:26 +0100 Subject: [PATCH 1/7] Standardize action orders --- data/mods/gen4/items.ts | 1 + data/moves.ts | 20 ++++++------- sim/battle-actions.ts | 2 +- sim/battle-queue.ts | 62 +++++++++++++++++++---------------------- 4 files changed, 40 insertions(+), 45 deletions(-) diff --git a/data/mods/gen4/items.ts b/data/mods/gen4/items.ts index 1a5577d4aa..2e51c35d4a 100644 --- a/data/mods/gen4/items.ts +++ b/data/mods/gen4/items.ts @@ -80,6 +80,7 @@ export const Items: import('../../../sim/dex-items').ModdedItemDataTable = { this.queue.insertChoice({ choice: 'event', event: 'Custap', + order: action.order, priority: action.priority + 0.1, pokemon: action.pokemon, move: action.move, diff --git a/data/moves.ts b/data/moves.ts index 4eed533c68..5df601e916 100644 --- a/data/moves.ts +++ b/data/moves.ts @@ -214,7 +214,7 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { if (this.activePerHalf === 1) return false; // fails in singles const action = this.queue.willMove(target); if (action) { - this.queue.prioritizeAction(action); + this.queue.prioritizeMove(action); this.add('-activate', target, 'move: After You'); } else { return false; @@ -5606,7 +5606,7 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { continue; } if (action.pokemon.isAlly(source) && ['grasspledge', 'waterpledge'].includes(action.move.id)) { - this.queue.prioritizeAction(action, move); + this.queue.prioritizeMove(action, move); this.add('-waiting', source, action.pokemon); return null; } @@ -7884,7 +7884,7 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { continue; } if (action.pokemon.isAlly(source) && ['waterpledge', 'firepledge'].includes(action.move.id)) { - this.queue.prioritizeAction(action, move); + this.queue.prioritizeMove(action, move); this.add('-waiting', source, action.pokemon); return null; } @@ -10009,7 +10009,7 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { pp: 15, priority: 0, flags: { protect: 1, bypasssub: 1, allyanim: 1, failinstruct: 1 }, - onHit(target, source) { + onHit(target, source, move) { if (!target.lastMove || target.volatiles['dynamax']) return false; const lastMove = target.lastMove; const moveSlot = target.getMoveData(lastMove.id); @@ -10022,12 +10022,12 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { return false; } this.add('-singleturn', target, 'move: Instruct', `[of] ${source}`); - this.queue.prioritizeAction(this.queue.resolveAction({ + this.queue.prioritizeMove({ choice: 'move', pokemon: target, moveid: target.lastMove.id, targetLoc: target.lastMoveTargetLoc!, - })[0] as MoveAction); + } as MoveAction); }, secondary: null, target: "normal", @@ -14990,7 +14990,7 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { const action = this.queue.willMove(target); if (!action) return false; - action.order = 201; + this.queue.prioritizeMove(action, undefined, true); this.add('-activate', target, 'move: Quash'); }, secondary: null, @@ -16088,7 +16088,7 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { for (const action of this.queue.list as MoveAction[]) { if (!action.pokemon || !action.move || action.maxMove || action.zmove) continue; if (action.move.id === 'round') { - this.queue.prioritizeAction(action, move); + this.queue.prioritizeMove(action, move); return; } } @@ -16903,7 +16903,7 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { this.effectState.gotHit = true; const action = this.queue.willMove(pokemon); if (action) { - this.queue.prioritizeAction(action); + this.queue.prioritizeMove(action); } } }, @@ -21338,7 +21338,7 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { continue; } if (otherMoveUser.isAlly(source) && ['firepledge', 'grasspledge'].includes(otherMove.id)) { - this.queue.prioritizeAction(action, move); + this.queue.prioritizeMove(action, move); this.add('-waiting', source, otherMoveUser); return null; } diff --git a/sim/battle-actions.ts b/sim/battle-actions.ts index 6467865baa..bf0194f7d5 100644 --- a/sim/battle-actions.ts +++ b/sim/battle-actions.ts @@ -313,7 +313,7 @@ export class BattleActions { this.battle.add('-hint', `Some effects can force a Pokemon to use ${move.name} again in a row.`); } - // TODO: Refactor to use BattleQueue#prioritizeAction in onAnyAfterMove handlers + // TODO: Refactor to use BattleQueue#prioritizeMove in onAnyAfterMove handlers // Dancer's activation order is completely different from any other event, so it's handled separately if (move.flags['dance'] && moveDidSomething && !move.isExternal) { const dancers = []; diff --git a/sim/battle-queue.ts b/sim/battle-queue.ts index 30f1d710c4..e566007457 100644 --- a/sim/battle-queue.ts +++ b/sim/battle-queue.ts @@ -19,7 +19,7 @@ import type { Battle } from './battle'; export interface MoveAction { /** action type */ choice: 'move' | 'beforeTurnMove' | 'priorityChargeMove'; - order: 3 | 5 | 200 | 201 | 199 | 106; + order: 5 | 105 | 199 | 200 | 201; /** priority of the action (lower first) */ priority: number; /** fractional priority of the action (lower first) */ @@ -50,7 +50,7 @@ export interface MoveAction { export interface SwitchAction { /** action type */ choice: 'switch' | 'instaswitch' | 'revivalblessing'; - order: 3 | 6 | 103; + order: 3 | 6 | 101; /** priority of the action (lower first) */ priority: number; /** speed of pokemon switching (higher first if priority tie) */ @@ -127,6 +127,26 @@ export interface ActionChoice { export class BattleQueue { battle: Battle; list: Action[]; + static readonly orders: { [choice: string]: number } = { + team: 1, + start: 2, + instaswitch: 3, + beforeTurn: 4, + beforeTurnMove: 5, + revivalblessing: 6, + + runSwitch: 100, + switch: 101, + megaEvo: 102, megaEvoX: 102, megaEvoY: 102, + runDynamax: 103, + terastallize: 104, + priorityChargeMove: 105, + + shift: 200, + move: 200, + + residual: 300, + }; constructor(battle: Battle) { this.battle = battle; this.list = []; @@ -165,35 +185,9 @@ export class BattleQueue { 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, - revivalblessing: 6, - - runSwitch: 101, - switch: 103, - megaEvo: 104, - megaEvoX: 104, - megaEvoY: 104, - runDynamax: 105, - terastallize: 106, - priorityChargeMove: 107, - - shift: 200, - // 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}`); - } + action.order = BattleQueue.orders[action.choice]; + if (!action.order) { + throw new Error(`Unexpected orderless action ${action.choice}`); } } if (!midTurn) { @@ -268,7 +262,7 @@ export class BattleQueue { /** * Makes the passed action happen next (skipping speed order). */ - prioritizeAction(action: MoveAction | SwitchAction, sourceEffect?: Effect) { + prioritizeMove(action: MoveAction, sourceEffect?: Effect, desprioritize = false) { for (const [i, curAction] of this.list.entries()) { if (curAction === action) { this.list.splice(i, 1); @@ -276,8 +270,8 @@ export class BattleQueue { } } action.sourceEffect = sourceEffect; - action.order = 3; - this.list.unshift(action); + action.order = desprioritize ? 201 : 199; + this.insertChoice(action); } /** From c4b59850c34d19976abbca96779a6368a110f53a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= Date: Thu, 7 Aug 2025 00:10:59 +0100 Subject: [PATCH 2/7] I don't trust effectOrder here --- data/moves.ts | 18 +++++++++--------- sim/battle-queue.ts | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/data/moves.ts b/data/moves.ts index 5df601e916..b4a4aef40b 100644 --- a/data/moves.ts +++ b/data/moves.ts @@ -214,7 +214,7 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { if (this.activePerHalf === 1) return false; // fails in singles const action = this.queue.willMove(target); if (action) { - this.queue.prioritizeMove(action); + this.queue.prioritizeAction(action); this.add('-activate', target, 'move: After You'); } else { return false; @@ -5606,7 +5606,7 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { continue; } if (action.pokemon.isAlly(source) && ['grasspledge', 'waterpledge'].includes(action.move.id)) { - this.queue.prioritizeMove(action, move); + this.queue.prioritizeAction(action, move); this.add('-waiting', source, action.pokemon); return null; } @@ -7884,7 +7884,7 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { continue; } if (action.pokemon.isAlly(source) && ['waterpledge', 'firepledge'].includes(action.move.id)) { - this.queue.prioritizeMove(action, move); + this.queue.prioritizeAction(action, move); this.add('-waiting', source, action.pokemon); return null; } @@ -10022,12 +10022,12 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { return false; } this.add('-singleturn', target, 'move: Instruct', `[of] ${source}`); - this.queue.prioritizeMove({ + this.queue.prioritizeAction(this.queue.resolveAction({ choice: 'move', pokemon: target, moveid: target.lastMove.id, targetLoc: target.lastMoveTargetLoc!, - } as MoveAction); + })[0] as MoveAction); }, secondary: null, target: "normal", @@ -14990,7 +14990,7 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { const action = this.queue.willMove(target); if (!action) return false; - this.queue.prioritizeMove(action, undefined, true); + this.queue.prioritizeAction(action, undefined, true); this.add('-activate', target, 'move: Quash'); }, secondary: null, @@ -16088,7 +16088,7 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { for (const action of this.queue.list as MoveAction[]) { if (!action.pokemon || !action.move || action.maxMove || action.zmove) continue; if (action.move.id === 'round') { - this.queue.prioritizeMove(action, move); + this.queue.prioritizeAction(action, move); return; } } @@ -16903,7 +16903,7 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { this.effectState.gotHit = true; const action = this.queue.willMove(pokemon); if (action) { - this.queue.prioritizeMove(action); + this.queue.prioritizeAction(action); } } }, @@ -21338,7 +21338,7 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { continue; } if (otherMoveUser.isAlly(source) && ['firepledge', 'grasspledge'].includes(otherMove.id)) { - this.queue.prioritizeMove(action, move); + this.queue.prioritizeAction(action, move); this.add('-waiting', source, otherMoveUser); return null; } diff --git a/sim/battle-queue.ts b/sim/battle-queue.ts index e566007457..87e00de71a 100644 --- a/sim/battle-queue.ts +++ b/sim/battle-queue.ts @@ -67,6 +67,7 @@ export interface SwitchAction { export interface TeamAction { /** action type */ choice: 'team'; + order: 1; /** priority of the action (lower first) */ priority: number; /** unused for this action type */ @@ -81,6 +82,7 @@ export interface TeamAction { export interface FieldAction { /** action type */ choice: 'start' | 'residual' | 'pass' | 'beforeTurn'; + order: 2 | 4 | 300; /** priority of the action (lower first) */ priority: number; /** unused for this action type */ @@ -93,6 +95,7 @@ export interface FieldAction { export interface PokemonAction { /** action type */ choice: 'megaEvo' | 'megaEvoX' | 'megaEvoY' | 'shift' | 'runSwitch' | 'event' | 'runDynamax' | 'terastallize'; + order: 100 | 102 | 103 | 104 | 200 | number; /** priority of the action (lower first) */ priority: number; /** speed of pokemon doing action (higher first if priority tie) */ @@ -262,7 +265,7 @@ export class BattleQueue { /** * Makes the passed action happen next (skipping speed order). */ - prioritizeMove(action: MoveAction, sourceEffect?: Effect, desprioritize = false) { + prioritizeAction(action: MoveAction, sourceEffect?: Effect, desprioritize = false) { for (const [i, curAction] of this.list.entries()) { if (curAction === action) { this.list.splice(i, 1); @@ -271,7 +274,14 @@ export class BattleQueue { } action.sourceEffect = sourceEffect; action.order = desprioritize ? 201 : 199; - this.insertChoice(action); + + for (const [i, curAction] of this.list.entries()) { + if(curAction.order >= action.order) { + this.list.splice(i, 0, action); + return; + } + } + this.list.push(action); } /** From 9cff1224f00e8af18253b3a3347a6a1a6e1e6268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= Date: Thu, 7 Aug 2025 00:19:55 +0100 Subject: [PATCH 3/7] Fixes --- data/moves.ts | 2 +- sim/battle-actions.ts | 2 +- sim/battle-queue.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/moves.ts b/data/moves.ts index b4a4aef40b..147a434d29 100644 --- a/data/moves.ts +++ b/data/moves.ts @@ -10027,7 +10027,7 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { pokemon: target, moveid: target.lastMove.id, targetLoc: target.lastMoveTargetLoc!, - })[0] as MoveAction); + })[0] as MoveAction, move); }, secondary: null, target: "normal", diff --git a/sim/battle-actions.ts b/sim/battle-actions.ts index bf0194f7d5..6467865baa 100644 --- a/sim/battle-actions.ts +++ b/sim/battle-actions.ts @@ -313,7 +313,7 @@ export class BattleActions { this.battle.add('-hint', `Some effects can force a Pokemon to use ${move.name} again in a row.`); } - // TODO: Refactor to use BattleQueue#prioritizeMove in onAnyAfterMove handlers + // TODO: Refactor to use BattleQueue#prioritizeAction in onAnyAfterMove handlers // Dancer's activation order is completely different from any other event, so it's handled separately if (move.flags['dance'] && moveDidSomething && !move.isExternal) { const dancers = []; diff --git a/sim/battle-queue.ts b/sim/battle-queue.ts index 87e00de71a..c58dc5e608 100644 --- a/sim/battle-queue.ts +++ b/sim/battle-queue.ts @@ -276,7 +276,7 @@ export class BattleQueue { action.order = desprioritize ? 201 : 199; for (const [i, curAction] of this.list.entries()) { - if(curAction.order >= action.order) { + if (curAction.order >= action.order) { this.list.splice(i, 0, action); return; } From 3ed9b5f400e048fa04c0474c66d8572c577eac2f Mon Sep 17 00:00:00 2001 From: andrebastosdias Date: Tue, 7 Apr 2026 18:28:36 +0100 Subject: [PATCH 4/7] BattleQueue can't be static --- sim/battle-queue.ts | 4 ++-- sim/global-types.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sim/battle-queue.ts b/sim/battle-queue.ts index dd56222cf2..2aa3e5e12c 100644 --- a/sim/battle-queue.ts +++ b/sim/battle-queue.ts @@ -136,7 +136,7 @@ export interface ActionChoice { export class BattleQueue { battle: Battle; list: Action[]; - static readonly orders: { [choice: string]: number } = { + readonly orders: { [choice: string]: number } = { team: 1, start: 2, instaswitch: 3, @@ -194,7 +194,7 @@ export class BattleQueue { 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) { - action.order = BattleQueue.orders[action.choice]; + action.order = this.orders[action.choice]; if (!action.order) { throw new Error(`Unexpected orderless action ${action.choice}`); } diff --git a/sim/global-types.ts b/sim/global-types.ts index dab8ed3233..1a9bd60055 100644 --- a/sim/global-types.ts +++ b/sim/global-types.ts @@ -333,6 +333,7 @@ interface ModdedBattlePokemon { interface ModdedBattleQueue extends Partial { inherit?: true; + orders: { [choice: string]: number }; resolveAction?: (this: BattleQueue, action: ActionChoice, midTurn?: boolean) => Action[]; } From f683da3cf776d8992b67aea81ae043f403871307 Mon Sep 17 00:00:00 2001 From: andrebastosdias Date: Tue, 7 Apr 2026 18:39:26 +0100 Subject: [PATCH 5/7] Make BattleActions.orders optional --- sim/global-types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sim/global-types.ts b/sim/global-types.ts index 1a9bd60055..2d83399267 100644 --- a/sim/global-types.ts +++ b/sim/global-types.ts @@ -333,7 +333,7 @@ interface ModdedBattlePokemon { interface ModdedBattleQueue extends Partial { inherit?: true; - orders: { [choice: string]: number }; + orders?: { [choice: string]: number }; resolveAction?: (this: BattleQueue, action: ActionChoice, midTurn?: boolean) => Action[]; } From 877cb13277b365ae533fd9b3abe7ccd688e44867 Mon Sep 17 00:00:00 2001 From: andrebastosdias Date: Wed, 8 Apr 2026 12:06:57 +0100 Subject: [PATCH 6/7] Fix action prioritization before Gen 8 --- data/mods/gen7/moves.ts | 18 ------------------ sim/battle-queue.ts | 9 ++++++--- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/data/mods/gen7/moves.ts b/data/mods/gen7/moves.ts index 62c55d6c4a..2151e62ee7 100644 --- a/data/mods/gen7/moves.ts +++ b/data/mods/gen7/moves.ts @@ -851,24 +851,6 @@ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = { inherit: true, isNonstandard: null, }, - quash: { - inherit: true, - onHit(target) { - if (this.activePerHalf === 1) return false; // fails in singles - const action = this.queue.willMove(target); - if (!action) return false; - - action.priority = -7.1; - this.queue.cancelMove(target); - for (let i = this.queue.list.length - 1; i >= 0; i--) { - if (this.queue.list[i].choice === 'residual') { - this.queue.list.splice(i, 0, action); - break; - } - } - this.add('-activate', target, 'move: Quash'); - }, - }, rage: { inherit: true, isNonstandard: null, diff --git a/sim/battle-queue.ts b/sim/battle-queue.ts index 2aa3e5e12c..ef8617023b 100644 --- a/sim/battle-queue.ts +++ b/sim/battle-queue.ts @@ -271,7 +271,7 @@ export class BattleQueue { /** * Makes the passed action happen next (skipping speed order). */ - prioritizeAction(action: MoveAction, sourceEffect?: Effect, desprioritize = false) { + prioritizeAction(action: MoveAction, sourceEffect?: Effect, decreasePriority = false) { for (const [i, curAction] of this.list.entries()) { if (curAction === action) { this.list.splice(i, 1); @@ -279,10 +279,13 @@ export class BattleQueue { } } action.sourceEffect = sourceEffect; - action.order = desprioritize ? 201 : 199; + action.order = decreasePriority ? 201 : 199; + // before Gen 8, prioritization follows insertion order + // in Gens 8+, the queue is sorted after every action for (const [i, curAction] of this.list.entries()) { - if (curAction.order >= action.order) { + if ((!decreasePriority && curAction.order >= action.order) || + (decreasePriority && curAction.order > action.order)) { this.list.splice(i, 0, action); return; } From a4106d5b8e138d1d1c19f77be45428b75ee2e788 Mon Sep 17 00:00:00 2001 From: andrebastosdias Date: Thu, 9 Apr 2026 19:23:34 +0100 Subject: [PATCH 7/7] Fix small thing --- data/moves.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/moves.ts b/data/moves.ts index dac5e6a984..a1bfe9e870 100644 --- a/data/moves.ts +++ b/data/moves.ts @@ -15563,7 +15563,7 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = { for (const action of this.queue.list as MoveAction[]) { if (!action.pokemon || !action.move || action.maxMove || action.zmove) continue; if (action.move.id === 'round') { - this.queue.prioritizeAction(action, move); + this.queue.prioritizeAction(action); return; } }