Skip to content

Commit 09ecf84

Browse files
Bertie690SirzBenjieDayKev
authored
refactor: decouple setOrder array from MovePhase (#6779)
* [Refactor/Test] Cleaned up and generalized `setTurnOrder` to apply to non-move effects * fixed type erorrs * fixed setting set order to undefined * Update turn-command-manager.ts * Fixed typo * Fixed metronome test * clear set order on new turn start moved to other pr * ran boime * Update global.d.ts * Fix fragmented `biome.jsonc` config * Fix docs in `field-helper.ts` * Remove extra `*` because this is not a valid TSDoc comment --------- Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
1 parent 38743a3 commit 09ecf84

15 files changed

+103
-78
lines changed

biome.jsonc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,12 +331,12 @@
331331
"noNonNullAssertion": "off" // tedious in some tests
332332
},
333333
"nursery": {
334+
// TODO: Enable for normal test folder files as well
334335
"noFloatingPromises": "error"
335336
}
336337
}
337338
}
338339
},
339-
340340
// Prevent forcing type-only imports inside `overrides.ts` (where they serve as placeholders for actual runtime symbols)
341341
{
342342
"includes": ["**/src/overrides.ts"],

global.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ declare global {
88
* ⚠️ Should not be used in production code, as it is only populated during test runs!
99
*/
1010
var server: SetupServerApi;
11+
// Override for `Array.isArray` to not remove `readonly`-ness from arrays known to be readonly
12+
interface ArrayConstructor {
13+
isArray<T>(arg: readonly T[]): arg is readonly T[];
14+
}
1115
}
1216

1317
// Global augments for `typedoc` to prevent TS from erroring when editing the config JS file

src/battle-scene.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { InvertPostFX } from "#app/pipelines/invert";
2323
import { SpritePipeline } from "#app/pipelines/sprite";
2424
import { SceneBase } from "#app/scene-base";
2525
import { startingWave } from "#app/starting-wave";
26+
import { TurnCommandManager } from "#app/turn-command-manager";
2627
import { UiInputs } from "#app/ui-inputs";
2728
import { pokemonPrevolutions } from "#balance/pokemon-evolutions";
2829
import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#balance/starters";
@@ -249,6 +250,8 @@ export class BattleScene extends SceneBase {
249250

250251
/** Manager for the phases active in the battle scene */
251252
public readonly phaseManager: PhaseManager;
253+
/** A manager for the commands and moves used in the current battle. */
254+
public readonly turnCommandManager: TurnCommandManager = new TurnCommandManager();
252255
public field: Phaser.GameObjects.Container;
253256
public fieldUI: Phaser.GameObjects.Container;
254257
public charSprite: CharSprite;
@@ -1118,6 +1121,7 @@ export class BattleScene extends SceneBase {
11181121
this.gameData = new GameData();
11191122
}
11201123

1124+
this.turnCommandManager.resetTurnOrder();
11211125
this.gameMode = getGameMode(GameModes.CLASSIC);
11221126

11231127
this.disableMenu = false;

src/dynamic-queue-manager.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import type { PokemonMove } from "#app/data/moves/pokemon-move";
22
import type { Pokemon } from "#app/field/pokemon";
3+
import { globalScene } from "#app/global-scene";
34
import type { Phase } from "#app/phase";
45
import type { MovePhase } from "#app/phases/move-phase";
56
import { MovePhasePriorityQueue } from "#app/queues/move-phase-priority-queue";
67
import { PokemonPhasePriorityQueue } from "#app/queues/pokemon-phase-priority-queue";
78
import { PostSummonPhasePriorityQueue } from "#app/queues/post-summon-phase-priority-queue";
89
import type { PriorityQueue } from "#app/queues/priority-queue";
9-
import type { BattlerIndex } from "#enums/battler-index";
1010
import type { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier";
1111
import type { DynamicPhase, PhaseConditionFunc, PhaseString } from "#types/phase-types";
1212

@@ -55,6 +55,9 @@ export class DynamicQueueManager {
5555
for (const queue of this.dynamicPhaseMap.values()) {
5656
queue.clear();
5757
}
58+
// TODO: Remove in a later PR - this is both unwieldly for tests
59+
// and would force MEs to reset the turn order at start of every single turn (which is dumb)
60+
globalScene.turnCommandManager.resetTurnOrder();
5861
}
5962

6063
/**
@@ -147,14 +150,6 @@ export class DynamicQueueManager {
147150
this.getMovePhaseQueue().cancelMove(condition);
148151
}
149152

150-
/**
151-
* Sets the move order to a static array rather than a dynamic queue
152-
* @param order - The order of {@linkcode BattlerIndex}s
153-
*/
154-
public setMoveOrder(order: BattlerIndex[]): void {
155-
this.getMovePhaseQueue().setMoveOrder(order);
156-
}
157-
158153
/**
159154
* @returns An in-order array of {@linkcode Pokemon}, representing the turn order as played out in the most recent turn
160155
*/

src/queues/move-phase-priority-queue.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import type { Pokemon } from "#app/field/pokemon";
33
import { globalScene } from "#app/global-scene";
44
import type { MovePhase } from "#app/phases/move-phase";
55
import { PokemonPhasePriorityQueue } from "#app/queues/pokemon-phase-priority-queue";
6-
import type { BattlerIndex } from "#enums/battler-index";
76
import type { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier";
87
import type { MovePriorityInBracket } from "#enums/move-priority-in-bracket";
98
import type { PhaseConditionFunc } from "#types/phase-types";
@@ -79,10 +78,6 @@ export class MovePhasePriorityQueue extends PokemonPhasePriorityQueue<MovePhase>
7978
}
8079
}
8180

82-
public setMoveOrder(order: BattlerIndex[]) {
83-
this.setOrder = order;
84-
}
85-
8681
public override pop(): MovePhase | undefined {
8782
this.reorder();
8883
const phase = this.queue.shift();
@@ -101,7 +96,6 @@ export class MovePhasePriorityQueue extends PokemonPhasePriorityQueue<MovePhase>
10196
}
10297

10398
public override clear(): void {
104-
this.setOrder = undefined;
10599
this.lastTurnOrder = [];
106100
super.clear();
107101
}
Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,10 @@
11
import { PriorityQueue } from "#app/queues/priority-queue";
22
import { sortInSpeedOrder } from "#app/utils/speed-order";
3-
import type { BattlerIndex } from "#enums/battler-index";
43
import type { DynamicPhase } from "#types/phase-types";
54

65
/** A generic speed-based priority queue of {@linkcode DynamicPhase}s. */
76
export class PokemonPhasePriorityQueue<T extends DynamicPhase> extends PriorityQueue<T> {
8-
protected setOrder: BattlerIndex[] | undefined;
97
protected override reorder(): void {
10-
const setOrder = this.setOrder;
11-
if (setOrder) {
12-
this.queue.sort(
13-
(a, b) =>
14-
setOrder.indexOf(a.getPokemon().getBattlerIndex()) - setOrder.indexOf(b.getPokemon().getBattlerIndex()),
15-
);
16-
} else {
17-
this.queue = sortInSpeedOrder(this.queue);
18-
}
8+
this.queue = sortInSpeedOrder(this.queue);
199
}
2010
}

src/turn-command-manager.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { BattlerIndex } from "#enums/battler-index";
2+
3+
// TODO: Add more methods and move `lastTurnOrder` here
4+
/** A manager for the commands and moves used in the current battle. */
5+
export class TurnCommandManager {
6+
/**
7+
* A preset order of {@linkcode BattlerIndex}es, used by tests and MEs to "fix" speed order-related effects.
8+
*/
9+
public setOrder: readonly BattlerIndex[] | undefined;
10+
11+
/** Reset a prior set turn order. */
12+
public resetTurnOrder(): void {
13+
this.setOrder = undefined;
14+
}
15+
}

src/utils/speed-order.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ function getPokemon(p: Pokemon | HasPokemon): Pokemon {
5555
* @param groupedPokemonList - A grouped array of objects to sort; will be mutated in place
5656
*/
5757
function sortBySpeed<T extends Pokemon | HasPokemon>(groupedPokemonList: T[][]): void {
58+
const { setOrder } = globalScene.turnCommandManager;
59+
60+
// If a set turn order was provided, use that in ascending order.
61+
if (setOrder) {
62+
groupedPokemonList.sort((a, b) => {
63+
const aIndex = getPokemon(a[0]).getBattlerIndex();
64+
const bIndex = getPokemon(b[0]).getBattlerIndex();
65+
return setOrder.indexOf(aIndex) - setOrder.indexOf(bIndex);
66+
});
67+
return;
68+
}
69+
5870
groupedPokemonList.sort((a, b) => {
5971
const aSpeed = getPokemon(a[0]).getEffectiveStat(Stat.SPD);
6072
const bSpeed = getPokemon(b[0]).getEffectiveStat(Stat.SPD);

test/framework/game-manager.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -525,19 +525,27 @@ export class GameManager {
525525
}
526526

527527
/**
528-
* Modifies the queue manager to return move phases in a particular order
529-
* Used to manually modify Pokemon turn order.
530-
* Note: This *DOES NOT* account for priority.
531-
* @param order - The turn order to set as an array of {@linkcode BattlerIndex}es.
528+
* Override the turn order of the battle's current combatants.
529+
*
530+
* Affects all processes that check speed order.
531+
* @param order - The turn order to set, as an array of {@linkcode BattlerIndex}es
532532
* @example
533533
* ```ts
534-
* await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2]);
534+
* game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2]);
535535
* ```
536+
* @throws Fails test immediately if `order` does not contain all non-fainted combatants' `BattlerIndex`es.
537+
* @remarks
538+
* This does not affect other relative orderings like move priority, nor does it change
539+
* the battlers' innate speed stats (for the purposes of Electro Ball, etc).
536540
*/
537-
async setTurnOrder(order: BattlerIndex[]): Promise<void> {
538-
await this.phaseInterceptor.to("TurnStartPhase", false);
541+
// TODO: Move to `FieldHelper`
542+
// TODO: Bulk-remove `await`s from existing test files in a follow-up PR
543+
public setTurnOrder(order: BattlerIndex[]): void {
544+
expect(order, "Turn order passed to `setTurnOrder` lacked values for one or more Pokemon!").toEqualUnsorted(
545+
this.scene.getField(true).map(p => p.getBattlerIndex()),
546+
);
539547

540-
this.scene.phaseManager.dynamicQueueManager.setMoveOrder(order);
548+
this.scene.turnCommandManager.setOrder = order;
541549
}
542550

543551
/**

test/helpers/field-helper.ts

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon";
88
import { GameManagerHelper } from "#test/helpers/game-manager-helper";
99
import type { MoveHelper } from "#test/helpers/move-helper";
1010
import { getEnumStr } from "#test/utils/string-utils";
11+
import { sortInSpeedOrder } from "#utils/speed-order";
1112
import { expect, type MockInstance, vi } from "vitest";
1213

1314
/** Helper to manage pokemon */
@@ -64,36 +65,27 @@ export class FieldHelper extends GameManagerHelper {
6465

6566
/**
6667
* Helper function to return all on-field {@linkcode Pokemon} in speed order (fastest first).
67-
* @param indices - Whether to only return {@linkcode BattlerIndex}es instead of full Pokemon objects
68-
* (such as for comparison with other speed order-related mechanisms); default `false`
69-
* @returns An array containing all on-field {@linkcode Pokemon} in order of descending Speed. \
70-
* Speed ties are returned in increasing order of index.
71-
*
72-
* @remarks
73-
* This does not account for Trick Room as it does not modify the _speed_ of Pokemon on the field,
74-
* only their turn order.
68+
* @param indices - (Default `false`) Whether to only return {@linkcode BattlerIndex}es instead of full Pokemon objects
69+
* (such as for comparison with other speed order-related mechanisms)
70+
* @param ignoreOverride - (Default `true`) Whether to ignore preset turn orders and speed-reversing effects (like Trick Room)
71+
* @returns An array containing all on-field `Pokemon` in order of **descending** speed.
7572
*/
76-
public getSpeedOrder(indices?: false): Pokemon[];
77-
73+
public getSpeedOrder(indices?: false, ignoreOverride?: boolean): Pokemon[];
7874
/**
7975
* Helper function to return all on-field {@linkcode Pokemon} in speed order (fastest first).
80-
* @param indices - Whether to only return {@linkcode BattlerIndex}es instead of full Pokemon objects
81-
* (such as for comparison with other speed order-related mechanisms); default `false`
82-
* @returns An array containing the {@linkcode BattlerIndex}es of all on-field {@linkcode Pokemon} on the field in order of descending Speed. \
83-
* Speed ties are returned in increasing order of index.
84-
*
85-
* @remarks
86-
* This does not account for Trick Room as it does not modify the _speed_ of Pokemon on the field,
87-
* only their turn order.
76+
* @param indices - (Default `false`) Whether to only return {@linkcode BattlerIndex}es instead of full Pokemon objects
77+
* (such as for comparison with other speed order-related mechanisms)
78+
* @param ignoreOverride - (Default `true`) Whether to ignore preset turn orders and speed-reversing effects (like Trick Room)
79+
* @returns An array containing the `BattlerIndex`es of all on-field `Pokemon` in order of **descending** speed.
8880
*/
89-
public getSpeedOrder(indices: true): BattlerIndex[];
90-
public getSpeedOrder(indices = false): BattlerIndex[] | Pokemon[] {
91-
const ret = this.game.scene
92-
.getField(true)
93-
.sort(
94-
(pA, pB) =>
95-
pB.getEffectiveStat(Stat.SPD) - pA.getEffectiveStat(Stat.SPD) || pA.getBattlerIndex() - pB.getBattlerIndex(),
96-
);
81+
public getSpeedOrder(indices: true, ignoreOverride?: boolean): BattlerIndex[];
82+
public getSpeedOrder(indices = false, ignoreOverride = true): BattlerIndex[] | Pokemon[] {
83+
let ret = this.game.scene.getField(true);
84+
if (ignoreOverride) {
85+
ret.sort((pA, pB) => pB.getEffectiveStat(Stat.SPD) - pA.getEffectiveStat(Stat.SPD));
86+
} else {
87+
ret = sortInSpeedOrder(ret);
88+
}
9789

9890
return indices ? ret.map(p => p.getBattlerIndex()) : ret;
9991
}

0 commit comments

Comments
 (0)