diff --git a/biome.jsonc b/biome.jsonc index 2433ba52010..186cd9f3a74 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -236,7 +236,7 @@ }, "overrides": [ { - "includes": ["**/test/**/*.test.ts"], + "includes": ["**/test/**/*.ts"], "linter": { "rules": { "performance": { @@ -245,8 +245,16 @@ }, "style": { "noNonNullAssertion": "off" // tedious in some tests - }, + } + } + } + }, + { + "includes": ["**/test/**/*.test.ts"], + "linter": { + "rules": { "nursery": { + // TODO: Enable for normal test folder files as well "noFloatingPromises": "error" } } diff --git a/src/dynamic-queue-manager.ts b/src/dynamic-queue-manager.ts index 7c65a79d743..1fb24d5644e 100644 --- a/src/dynamic-queue-manager.ts +++ b/src/dynamic-queue-manager.ts @@ -7,7 +7,6 @@ import { MovePhasePriorityQueue } from "#app/queues/move-phase-priority-queue"; import { PokemonPhasePriorityQueue } from "#app/queues/pokemon-phase-priority-queue"; import { PostSummonPhasePriorityQueue } from "#app/queues/post-summon-phase-priority-queue"; import type { PriorityQueue } from "#app/queues/priority-queue"; -import type { BattlerIndex } from "#enums/battler-index"; import type { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier"; // TODO: might be easier to define which phases should be dynamic instead @@ -76,8 +75,8 @@ export class DynamicQueueManager { } /** - * Returns the highest-priority (generally by speed) {@linkcode Phase} of the specified type - * @param type - The {@linkcode PhaseString | type} to pop + * Returns the highest-priority (generally by speed) {@linkcode Phase} of the specified type. + * @param type - The {@linkcode PhaseString | type} of phase to access * @returns The popped {@linkcode Phase}, or `undefined` if none of the specified type exist */ public popNextPhase(type: PhaseString): Phase | undefined { @@ -90,7 +89,7 @@ export class DynamicQueueManager { * @param condition - An optional {@linkcode PhaseConditionFunc} to add conditions to the search * @returns Whether a matching phase exists */ - public exists(type: T, condition?: PhaseConditionFunc): boolean { + public exists(type: T, condition: PhaseConditionFunc = () => true): boolean { return !!this.dynamicPhaseMap.get(type)?.has(condition); } @@ -141,21 +140,13 @@ export class DynamicQueueManager { } /** - * Finds and cancels a {@linkcode MovePhase} meeting the condition - * @param phaseCondition - The {@linkcode PhaseConditionFunc | condition} function + * Find and cancel a {@linkcode MovePhase} meeting the condition + * @param phaseCondition - The {@linkcode PhaseConditionFunc | condition} function to filter phases by */ public cancelMovePhase(condition: PhaseConditionFunc<"MovePhase">): void { this.getMovePhaseQueue().cancelMove(condition); } - /** - * Sets the move order to a static array rather than a dynamic queue - * @param order - The order of {@linkcode BattlerIndex}s - */ - public setMoveOrder(order: BattlerIndex[]): void { - this.getMovePhaseQueue().setMoveOrder(order); - } - /** * @returns An in-order array of {@linkcode Pokemon}, representing the turn order as played out in the most recent turn */ @@ -176,7 +167,7 @@ export class DynamicQueueManager { /** * Internal helper to determine if a phase is dynamic. * @param phase - The {@linkcode Phase} to check - * @returns Whether `phase` is dynamic + * @returns Whether `phase` is dynamic. * @privateRemarks * Currently, this checks that `phase` has a `getPokemon` method * and is not blacklisted in `nonDynamicPokemonPhases`. diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index cbad6caaafa..930b3657db6 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3218,9 +3218,9 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * Check whether the specified Pokémon is an opponent + * Check whether 2 Pokémon oppose one another during battle. * @param target - The {@linkcode Pokemon} to compare against - * @returns `true` if the two pokemon are allies, `false` otherwise + * @returns Whether this Pokemon is an opponent of `target` (one is player and the other enemy). */ public isOpponent(target: Pokemon): boolean { return this.isPlayer() !== target.isPlayer(); diff --git a/src/phase-tree.ts b/src/phase-tree.ts index 69bb72ca4f0..2ea14c32a6b 100644 --- a/src/phase-tree.ts +++ b/src/phase-tree.ts @@ -96,10 +96,11 @@ export class PhaseTree { } /** - * Removes and returns the first {@linkcode Phase} from the topmost level of the tree + * Remove and return the first {@linkcode Phase} from the topmost level of the tree. * @returns - The next {@linkcode Phase}, or `undefined` if the Tree is empty */ public getNextPhase(): Phase | undefined { + // Clear out all empty levels from the tree this.currentLevel = this.levels.length - 1; while (this.currentLevel > 0 && this.levels[this.currentLevel].length === 0) { this.deferredActive = false; @@ -113,10 +114,10 @@ export class PhaseTree { } /** - * Finds a particular {@linkcode Phase} in the Tree by searching in pop order + * Find the first {@linkcode Phase} in the Tree matching the given conditions. * @param phaseType - The {@linkcode PhaseString | type} of phase to search for - * @param phaseFilter - A {@linkcode PhaseConditionFunc} to specify conditions for the phase - * @returns The matching {@linkcode Phase}, or `undefined` if none exists + * @param phaseFilter - An optional {@linkcode PhaseConditionFunc} to add conditions to the search + * @returns The first `Phase` that matches the criteria, or `undefined` if none exists */ public find

(phaseType: P, phaseFilter?: PhaseConditionFunc

): PhaseMap[P] | undefined { for (let i = this.levels.length - 1; i >= 0; i--) { @@ -129,26 +130,24 @@ export class PhaseTree { } /** - * Finds a particular {@linkcode Phase} in the Tree by searching in pop order + * Find all {@linkcode Phase}s in the Tree matching the given conditions. * @param phaseType - The {@linkcode PhaseString | type} of phase to search for - * @param phaseFilter - A {@linkcode PhaseConditionFunc} to specify conditions for the phase - * @returns The matching {@linkcode Phase}, or `undefined` if none exists + * @param phaseFilter - An optional {@linkcode PhaseConditionFunc} to add conditions to the search + * @returns An array containing all `Phase`s matching the criteria. */ public findAll

(phaseType: P, phaseFilter?: PhaseConditionFunc

): PhaseMap[P][] { const phases: PhaseMap[P][] = []; for (let i = this.levels.length - 1; i >= 0; i--) { const level = this.levels[i]; - const levelPhases = level.filter((p): p is PhaseMap[P] => p.is(phaseType) && (!phaseFilter || phaseFilter(p))); - phases.push(...levelPhases); + phases.push(...level.filter((p): p is PhaseMap[P] => p.is(phaseType) && (!phaseFilter || phaseFilter(p)))); } return phases; } /** - * Clears the Tree - * @param leaveFirstLevel - If `true`, leaves the top level of the tree intact - * - * @privateremarks + * Clear all Phases from the Tree. + * @param leaveFirstLevel - Whether to leave the top level of the tree intact; default `false` + * @privateRemarks * The parameter on this method exists because {@linkcode PhaseManager.clearPhaseQueue} previously (probably by mistake) ignored `phaseQueuePrepend`. * * This is (probably by mistake) relied upon by certain ME functions. @@ -181,25 +180,17 @@ export class PhaseTree { */ public removeAll(phaseType: PhaseString): void { for (let i = 0; i < this.levels.length; i++) { - const level = this.levels[i].filter(phase => !phase.is(phaseType)); - this.levels[i] = level; + this.levels[i] = this.levels[i].filter(phase => !phase.is(phaseType)); } } /** - * Determines if a particular phase exists in the Tree + * Check whether a particular Phase exists in the Tree * @param phaseType - The {@linkcode PhaseString | type} of phase to search for - * @param phaseFilter - A {@linkcode PhaseConditionFunc} to specify conditions for the phase + * @param phaseFilter - An optional {@linkcode PhaseConditionFunc} to specify conditions for the phase * @returns Whether a matching phase exists */ public exists

(phaseType: P, phaseFilter?: PhaseConditionFunc

): boolean { - for (const level of this.levels) { - for (const phase of level) { - if (phase.is(phaseType) && (!phaseFilter || phaseFilter(phase))) { - return true; - } - } - } - return false; + return this.levels.some(level => level.some(phase => phase.is(phaseType) && (!phaseFilter || phaseFilter(phase)))); } } diff --git a/src/queues/move-phase-priority-queue.ts b/src/queues/move-phase-priority-queue.ts index 5f0b20c3c2e..4a8304293ad 100644 --- a/src/queues/move-phase-priority-queue.ts +++ b/src/queues/move-phase-priority-queue.ts @@ -3,7 +3,6 @@ import type { Pokemon } from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import type { MovePhase } from "#app/phases/move-phase"; import { PokemonPhasePriorityQueue } from "#app/queues/pokemon-phase-priority-queue"; -import type { BattlerIndex } from "#enums/battler-index"; import type { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier"; import type { PhaseConditionFunc } from "#types/phase-types"; @@ -17,18 +16,18 @@ export class MovePhasePriorityQueue extends PokemonPhasePriorityQueue } public cancelMove(condition: PhaseConditionFunc<"MovePhase">): void { - this.queue.find(p => condition(p))?.cancel(); + this.queue.find(condition)?.cancel(); } public setTimingModifier(condition: PhaseConditionFunc<"MovePhase">, modifier: MovePhaseTimingModifier): void { - const phase = this.queue.find(p => condition(p)); + const phase = this.queue.find(condition); if (phase != null) { phase.timingModifier = modifier; } } public setMoveForPhase(condition: PhaseConditionFunc<"MovePhase">, move: PokemonMove) { - const phase = this.queue.find(p => condition(p)); + const phase = this.queue.find(condition); if (phase != null) { phase.move = move; } @@ -47,7 +46,7 @@ export class MovePhasePriorityQueue extends PokemonPhasePriorityQueue mp => mp.targets.length === 1 && mp.targets[0] === removedPokemon.getBattlerIndex() - && mp.pokemon.isPlayer() !== allyPokemon.isPlayer(), + && mp.pokemon.isOpponent(allyPokemon), ) .forEach(targetingMovePhase => { if (targetingMovePhase && targetingMovePhase.targets[0] !== allyPokemon.getBattlerIndex()) { @@ -57,10 +56,6 @@ export class MovePhasePriorityQueue extends PokemonPhasePriorityQueue } } - public setMoveOrder(order: BattlerIndex[]) { - this.setOrder = order; - } - public override pop(): MovePhase | undefined { this.reorder(); const phase = this.queue.shift(); @@ -79,25 +74,20 @@ export class MovePhasePriorityQueue extends PokemonPhasePriorityQueue } public override clear(): void { - this.setOrder = undefined; this.lastTurnOrder = []; super.clear(); } private sortPostSpeed(): void { - this.queue.sort((a: MovePhase, b: MovePhase) => { - const priority = [a, b].map(movePhase => { - const move = movePhase.move.getMove(); - return move.getPriority(movePhase.pokemon, true); - }); - - const timingModifiers = [a, b].map(movePhase => movePhase.timingModifier); - - if (timingModifiers[0] !== timingModifiers[1]) { - return timingModifiers[1] - timingModifiers[0]; - } - - return priority[1] - priority[0]; - }); + this.queue.sort( + (a: MovePhase, b: MovePhase) => + // formatting + b.timingModifier - a.timingModifier || getPriorityForMP(b) - getPriorityForMP(a), + ); } } + +function getPriorityForMP(mp: MovePhase): number { + const move = mp.move.getMove(); + return move.getPriority(mp.pokemon, true); +} diff --git a/src/queues/pokemon-phase-priority-queue.ts b/src/queues/pokemon-phase-priority-queue.ts index 3098c5be435..ab93a04ff21 100644 --- a/src/queues/pokemon-phase-priority-queue.ts +++ b/src/queues/pokemon-phase-priority-queue.ts @@ -1,20 +1,10 @@ import type { DynamicPhase } from "#app/@types/phase-types"; import { PriorityQueue } from "#app/queues/priority-queue"; import { sortInSpeedOrder } from "#app/utils/speed-order"; -import type { BattlerIndex } from "#enums/battler-index"; -/** A generic speed-based priority queue of {@linkcode DynamicPhase}s */ +/** A generic speed-based priority queue of {@linkcode DynamicPhase}s. */ export class PokemonPhasePriorityQueue extends PriorityQueue { - protected setOrder: BattlerIndex[] | undefined; protected override reorder(): void { - const setOrder = this.setOrder; - if (setOrder) { - this.queue.sort( - (a, b) => - setOrder.indexOf(a.getPokemon().getBattlerIndex()) - setOrder.indexOf(b.getPokemon().getBattlerIndex()), - ); - } else { - this.queue = sortInSpeedOrder(this.queue); - } + sortInSpeedOrder(this.queue); } } diff --git a/src/queues/pokemon-priority-queue.ts b/src/queues/pokemon-priority-queue.ts index 597bfb32c0d..b4f1c852555 100644 --- a/src/queues/pokemon-priority-queue.ts +++ b/src/queues/pokemon-priority-queue.ts @@ -5,6 +5,6 @@ import { sortInSpeedOrder } from "#app/utils/speed-order"; /** A priority queue of {@linkcode Pokemon}s */ export class PokemonPriorityQueue extends PriorityQueue { protected override reorder(): void { - this.queue = sortInSpeedOrder(this.queue); + sortInSpeedOrder(this.queue); } } diff --git a/src/queues/post-summon-phase-priority-queue.ts b/src/queues/post-summon-phase-priority-queue.ts index 37da90a1427..da86ca48643 100644 --- a/src/queues/post-summon-phase-priority-queue.ts +++ b/src/queues/post-summon-phase-priority-queue.ts @@ -11,7 +11,7 @@ import { sortInSpeedOrder } from "#app/utils/speed-order"; */ export class PostSummonPhasePriorityQueue extends PokemonPhasePriorityQueue { protected override reorder(): void { - this.queue = sortInSpeedOrder(this.queue, false); + sortInSpeedOrder(this.queue, false); this.queue.sort((phaseA, phaseB) => phaseB.getPriority() - phaseA.getPriority()); } diff --git a/src/queues/priority-queue.ts b/src/queues/priority-queue.ts index b53cfec3f4d..e1e8f25ae68 100644 --- a/src/queues/priority-queue.ts +++ b/src/queues/priority-queue.ts @@ -1,19 +1,24 @@ /** - * Stores a list of elements. + * Abstract class representing a {@link https://en.wikipedia.org/wiki/Priority_queue#Min-priority_queue | Min-priority queue}. * - * Dynamically updates ordering to always pop the highest "priority", based on implementation of {@linkcode reorder}. + * Dynamically updates ordering to always return the highest "priority" item, + * based on the implementation of {@linkcode reorder}. */ export abstract class PriorityQueue { + /** The items in the queue. */ protected queue: T[] = []; /** - * Sorts the elements in the queue + * Sort the elements in the queue. + * @remarks + * When sorting, earlier elements will be accessed before later ones. */ protected abstract reorder(): void; /** - * Calls {@linkcode reorder} and shifts the queue - * @returns The front element of the queue after sorting, or `undefined` if the queue is empty + * Reorder the queue before removing and returning the highest priority element. + * @returns The front-most element of the queue after sorting, + * or `undefined` if the queue is empty. * @sealed */ public pop(): T | undefined { @@ -34,7 +39,7 @@ export abstract class PriorityQueue { } /** - * Removes all elements from the queue + * Remove all elements from the queue. * @sealed */ public clear(): void { @@ -50,8 +55,8 @@ export abstract class PriorityQueue { } /** - * Removes the first element matching the condition - * @param condition - An optional condition function (defaults to a function that always returns `true`) + * Remove the first element matching the condition + * @param condition - If provided, will restrict the removal to only phases matching the condition * @returns Whether a removal occurred */ public remove(condition: (t: T) => boolean = () => true): boolean { @@ -67,12 +72,12 @@ export abstract class PriorityQueue { } /** @returns An element matching the condition function */ - public find(condition?: (t: T) => boolean): T | undefined { - return this.queue.find(e => !condition || condition(e)); + public find(condition: (t: T) => boolean): T | undefined { + return this.queue.find(condition); } /** @returns Whether an element matching the condition function exists */ - public has(condition?: (t: T) => boolean): boolean { - return this.queue.some(e => !condition || condition(e)); + public has(condition: (t: T) => boolean): boolean { + return this.queue.some(condition); } } diff --git a/src/utils/common.ts b/src/utils/common.ts index 569333209bf..2a27c198fc4 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -135,14 +135,17 @@ export function randSeedItem(items: T[]): T { /** * Shuffle a list using the seeded rng. Utilises the Fisher-Yates algorithm. - * @param items An array of items. + * @param items - The array of items to shuffle + * @param mutate - Whether to mutate the array in place (`true`) or create a new one + * (`false`); default `false` * @returns A new shuffled array of items. */ -export function randSeedShuffle(items: T[]): T[] { +export function randSeedShuffle(items: T[], mutate = false): T[] { if (items.length <= 1) { return items; } - const newArray = items.slice(0); + + const newArray = mutate ? items.slice(0) : items; for (let i = items.length - 1; i > 0; i--) { const j = Phaser.Math.RND.integerInRange(0, i); [newArray[i], newArray[j]] = [newArray[j], newArray[i]]; diff --git a/src/utils/speed-order-generator.ts b/src/utils/speed-order-generator.ts index 24f95de665f..44b83b394b6 100644 --- a/src/utils/speed-order-generator.ts +++ b/src/utils/speed-order-generator.ts @@ -5,7 +5,8 @@ import type { Pokemon } from "#field/pokemon"; /** * A generator function which uses a priority queue to yield each pokemon from a given side of the field in speed order. - * @param side - The {@linkcode ArenaTagSide | side} of the field to use + * @param side - The {@linkcode ArenaTagSide | side} of the field to use; + * default `ArenaTagSide.BOTH` * @returns A {@linkcode Generator} of {@linkcode Pokemon} * * @remarks diff --git a/src/utils/speed-order.ts b/src/utils/speed-order.ts index 1d894369bb3..86ecbb8a515 100644 --- a/src/utils/speed-order.ts +++ b/src/utils/speed-order.ts @@ -10,35 +10,36 @@ interface hasPokemon { } /** - * Sorts an array of {@linkcode Pokemon} by speed, taking Trick Room into account. - * @param pokemonList - The list of Pokemon or objects containing Pokemon - * @param shuffleFirst - Whether to shuffle the list before sorting (to handle speed ties). Default `true`. - * @returns The sorted array of {@linkcode Pokemon} + * Sort an array of {@linkcode Pokemon} in speed order, taking Trick Room into account. + * @param pokemonList - An array of `Pokemon` or objects containing `Pokemon` to sort; + * will be mutated and sorted in place. + * @param shuffleFirst - Whether to shuffle the list before sorting (to handle speed ties); default `true`. + * If `false`, will sort speed ties in ascending order of `BattlerIndex`es. */ -export function sortInSpeedOrder(pokemonList: T[], shuffleFirst = true): T[] { - pokemonList = shuffleFirst ? shufflePokemonList(pokemonList) : pokemonList; +export function sortInSpeedOrder(pokemonList: T[], shuffleFirst = true): void { + if (shuffleFirst) { + shufflePokemonList(pokemonList); + } sortBySpeed(pokemonList); - return pokemonList; } /** - * @param pokemonList - The array of Pokemon or objects containing Pokemon - * @returns The shuffled array + * Helper function to randomly shuffle an array of Pokemon. + * @param pokemonList - The array of Pokemon or objects containing Pokemon to shuffle */ -function shufflePokemonList(pokemonList: T[]): T[] { +function shufflePokemonList(pokemonList: T[]): void { // This is seeded with the current turn to prevent an inconsistency where it // was varying based on how long since you last reloaded globalScene.executeWithSeedOffset( () => { - pokemonList = randSeedShuffle(pokemonList); + randSeedShuffle(pokemonList, true); }, globalScene.currentBattle.turn * 1000 + pokemonList.length, globalScene.waveSeed, ); - return pokemonList; } -/** Sorts an array of {@linkcode Pokemon} by speed (without shuffling) */ +/** Sort an array of {@linkcode Pokemon} in speed order (without shuffling) */ function sortBySpeed(pokemonList: T[]): void { pokemonList.sort((a, b) => { const aSpeed = (a instanceof Pokemon ? a : a.getPokemon()).getEffectiveStat(Stat.SPD); diff --git a/test/abilities/neutralizing-gas.test.ts b/test/abilities/neutralizing-gas.test.ts index fd9138e4174..140b7eae75b 100644 --- a/test/abilities/neutralizing-gas.test.ts +++ b/test/abilities/neutralizing-gas.test.ts @@ -120,7 +120,6 @@ describe("Abilities - Neutralizing Gas", () => { game.move.select(MoveId.SPLASH, 1); await game.move.selectEnemyMove(MoveId.ENTRAINMENT, BattlerIndex.PLAYER_2); await game.move.selectEnemyMove(MoveId.SPLASH); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); await game.phaseInterceptor.to("BerryPhase"); expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeUndefined(); // No neut gas users are left }); diff --git a/test/abilities/protosynthesis.test.ts b/test/abilities/protosynthesis.test.ts index ea2e1e20c17..1d4a9845f8f 100644 --- a/test/abilities/protosynthesis.test.ts +++ b/test/abilities/protosynthesis.test.ts @@ -97,7 +97,6 @@ describe("Abilities - Protosynthesis", () => { true, ); game.move.select(MoveId.TACKLE); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.toNextTurn(); const boosted_dmg = initialHp - enemy.hp; expect(boosted_dmg).toBeGreaterThan(unboosted_dmg); diff --git a/test/abilities/stall.test.ts b/test/abilities/stall.test.ts index b6a88964e09..df522867722 100644 --- a/test/abilities/stall.test.ts +++ b/test/abilities/stall.test.ts @@ -1,4 +1,5 @@ import { AbilityId } from "#enums/ability-id"; +import { BattlerIndex } from "#enums/battler-index"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { GameManager } from "#test/test-utils/game-manager"; @@ -24,7 +25,7 @@ describe("Abilities - Stall", () => { game.override .battleStyle("single") .criticalHits(false) - .enemySpecies(SpeciesId.REGIELEKI) + .enemySpecies(SpeciesId.SHUCKLE) .enemyAbility(AbilityId.STALL) .enemyMoveset(MoveId.QUICK_ATTACK) .moveset([MoveId.QUICK_ATTACK, MoveId.TACKLE]); @@ -42,7 +43,7 @@ describe("Abilities - Stall", () => { const player = game.field.getPlayerPokemon(); game.move.select(MoveId.QUICK_ATTACK); - + game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("MoveEndPhase", false); // The player Pokemon (without Stall) goes first despite having lower speed than the opponent. // The opponent Pokemon (with Stall) goes last despite having higher speed than the player Pokemon. @@ -55,6 +56,7 @@ describe("Abilities - Stall", () => { const player = game.field.getPlayerPokemon(); game.move.select(MoveId.TACKLE); + game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("MoveEndPhase", false); // The opponent Pokemon (with Stall) goes first because its move is still within a higher priority bracket than its opponent. @@ -69,6 +71,7 @@ describe("Abilities - Stall", () => { const player = game.field.getPlayerPokemon(); game.move.select(MoveId.TACKLE); + game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("MoveEndPhase", false); diff --git a/test/abilities/supreme-overlord.test.ts b/test/abilities/supreme-overlord.test.ts index d5470b70476..5ce04f0d0f4 100644 --- a/test/abilities/supreme-overlord.test.ts +++ b/test/abilities/supreme-overlord.test.ts @@ -53,12 +53,10 @@ describe("Abilities - Supreme Overlord", () => { await game.toNextTurn(); game.move.select(MoveId.EXPLOSION); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); game.doSelectPartyPokemon(2); await game.toNextTurn(); game.move.select(MoveId.TACKLE); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.phaseInterceptor.to(MoveEffectPhase); expect(move.calculateBattlePower).toHaveReturnedWith(basePower * 1.2); @@ -80,7 +78,6 @@ describe("Abilities - Supreme Overlord", () => { */ game.doRevivePokemon(1); game.move.select(MoveId.EXPLOSION); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); game.doSelectPartyPokemon(1); await game.toNextTurn(); @@ -88,12 +85,10 @@ describe("Abilities - Supreme Overlord", () => { * Bulbasur faints twice */ game.move.select(MoveId.EXPLOSION); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); game.doSelectPartyPokemon(2); await game.toNextTurn(); game.move.select(MoveId.TACKLE); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.phaseInterceptor.to(MoveEffectPhase); expect(move.calculateBattlePower).toHaveReturnedWith(basePower * 1.3); @@ -116,7 +111,6 @@ describe("Abilities - Supreme Overlord", () => { * Enemy Pokemon faints and new wave is entered. */ game.move.select(MoveId.TACKLE); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextWave(); game.move.select(MoveId.TACKLE); @@ -137,7 +131,6 @@ describe("Abilities - Supreme Overlord", () => { await game.toNextTurn(); game.move.select(MoveId.TACKLE); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextWave(); game.move.select(MoveId.TACKLE); @@ -158,7 +151,6 @@ describe("Abilities - Supreme Overlord", () => { await game.toNextTurn(); game.move.select(MoveId.TACKLE); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextWave(); game.move.select(MoveId.TACKLE); diff --git a/test/items/multi-lens.test.ts b/test/items/multi-lens.test.ts index 3686aff0fcf..ac04bcb3b3d 100644 --- a/test/items/multi-lens.test.ts +++ b/test/items/multi-lens.test.ts @@ -70,7 +70,6 @@ describe("Items - Multi Lens", () => { const playerPokemon = game.field.getPlayerPokemon(); game.move.select(MoveId.TACKLE); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.phaseInterceptor.to("MoveEndPhase"); expect(playerPokemon.turnData.hitCount).toBe(3); diff --git a/test/moves/baton-pass.test.ts b/test/moves/baton-pass.test.ts index caabcfa7158..11133a42a95 100644 --- a/test/moves/baton-pass.test.ts +++ b/test/moves/baton-pass.test.ts @@ -111,7 +111,6 @@ describe("Moves - Baton Pass", () => { expect(enemy.getTag(BattlerTagType.FIRE_SPIN)).toBeDefined(); game.move.select(MoveId.BATON_PASS); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); game.doSelectPartyPokemon(1); await game.toNextTurn(); diff --git a/test/moves/delayed-attack.test.ts b/test/moves/delayed-attack.test.ts index e31c7f28e48..8d2379d5835 100644 --- a/test/moves/delayed-attack.test.ts +++ b/test/moves/delayed-attack.test.ts @@ -165,18 +165,20 @@ describe("Moves - Delayed Attacks", () => { it("should trigger multiple pending attacks in order of creation, even if that order changes later on", async () => { game.override.battleStyle("double"); - await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.FEEBAS]); + await game.classicMode.startBattle([SpeciesId.ALOMOMOLA, SpeciesId.BLISSEY]); - const [alomomola, blissey] = game.scene.getField(); + const [alomomola, blissey, karp1, karp2] = game.scene.getField(); + vi.spyOn(karp1, "getNameToRender").mockReturnValue("Karp 1"); + vi.spyOn(karp2, "getNameToRender").mockReturnValue("Karp 2"); - const oldOrder = game.field.getSpeedOrder(); + const oldOrder = game.field.getSpeedOrder(true); game.move.use(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER, BattlerIndex.ENEMY); game.move.use(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2); await game.move.forceEnemyMove(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER); await game.move.forceEnemyMove(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER_2); // Ensure that the moves are used deterministically in speed order (for speed ties) - await game.setTurnOrder(oldOrder.map(p => p.getBattlerIndex())); + await game.setTurnOrder(oldOrder); await game.toNextTurn(); expectFutureSightActive(4); @@ -195,7 +197,11 @@ describe("Moves - Delayed Attacks", () => { const MEPs = game.scene.phaseManager["phaseQueue"].findAll("MoveEffectPhase"); expect(MEPs).toHaveLength(4); - expect(MEPs.map(mep => mep.getPokemon())).toEqual(oldOrder); + expect( + MEPs.map(mep => mep.getPokemon().getBattlerIndex()), + `Expected: ${oldOrder.map(o => game.scene.getField()[o].getNameToRender())} +Actual: ${MEPs.map(mep => mep.getPokemon().getNameToRender())}`, + ).toEqual(oldOrder); }); it("should vanish silently if it would otherwise hit the user", async () => { diff --git a/test/moves/destiny-bond.test.ts b/test/moves/destiny-bond.test.ts index a5020b83944..ba51a0a53c2 100644 --- a/test/moves/destiny-bond.test.ts +++ b/test/moves/destiny-bond.test.ts @@ -17,8 +17,6 @@ describe("Moves - Destiny Bond", () => { let game: GameManager; const defaultParty = [SpeciesId.BULBASAUR, SpeciesId.SQUIRTLE]; - const enemyFirst = [BattlerIndex.ENEMY, BattlerIndex.PLAYER]; - const playerFirst = [BattlerIndex.PLAYER, BattlerIndex.ENEMY]; beforeAll(() => { phaserGame = new Phaser.Game({ @@ -52,7 +50,7 @@ describe("Moves - Destiny Bond", () => { const playerPokemon = game.field.getPlayerPokemon(); game.move.select(moveToUse); - await game.setTurnOrder(enemyFirst); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("BerryPhase"); expect(enemyPokemon.isFainted()).toBe(true); @@ -70,7 +68,7 @@ describe("Moves - Destiny Bond", () => { // Turn 1: Enemy uses Destiny Bond and doesn't faint game.move.select(MoveId.SPLASH); - await game.setTurnOrder(playerFirst); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextTurn(); expect(enemyPokemon.isFainted()).toBe(false); @@ -78,8 +76,8 @@ describe("Moves - Destiny Bond", () => { // Turn 2: Player KO's the enemy before the enemy's turn game.move.select(moveToUse); - await game.setTurnOrder(playerFirst); - await game.phaseInterceptor.to("BerryPhase"); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.toEndOfTurn(); expect(enemyPokemon.isFainted()).toBe(true); expect(playerPokemon.isFainted()).toBe(true); @@ -96,7 +94,7 @@ describe("Moves - Destiny Bond", () => { // Turn 1: Enemy uses Destiny Bond and doesn't faint game.move.select(MoveId.SPLASH); - await game.setTurnOrder(enemyFirst); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextTurn(); expect(enemyPokemon.isFainted()).toBe(false); @@ -104,7 +102,6 @@ describe("Moves - Destiny Bond", () => { // Turn 2: Enemy should fail Destiny Bond then get KO'd game.move.select(moveToUse); - await game.setTurnOrder(enemyFirst); await game.phaseInterceptor.to("BerryPhase"); expect(enemyPokemon.isFainted()).toBe(true); @@ -122,7 +119,7 @@ describe("Moves - Destiny Bond", () => { const playerPokemon = game.field.getPlayerPokemon(); game.move.select(moveToUse); - await game.setTurnOrder(enemyFirst); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("BerryPhase"); expect(enemyPokemon.isFainted()).toBe(true); @@ -140,7 +137,7 @@ describe("Moves - Destiny Bond", () => { // Turn 1: Enemy uses Destiny Bond and doesn't faint game.move.select(MoveId.SPORE); - await game.setTurnOrder(enemyFirst); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextTurn(); expect(enemyPokemon.isFainted()).toBe(false); @@ -149,7 +146,6 @@ describe("Moves - Destiny Bond", () => { // Turn 2: Enemy should skip a turn due to sleep, then get KO'd game.move.select(moveToUse); - await game.setTurnOrder(enemyFirst); await game.phaseInterceptor.to("BerryPhase"); expect(enemyPokemon.isFainted()).toBe(true); @@ -184,7 +180,7 @@ describe("Moves - Destiny Bond", () => { const playerPokemon = game.field.getPlayerPokemon(); game.move.select(moveToUse); - await game.setTurnOrder(enemyFirst); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("BerryPhase"); expect(enemyPokemon.isFainted()).toBe(true); @@ -238,7 +234,7 @@ describe("Moves - Destiny Bond", () => { const playerPokemon = game.field.getPlayerPokemon(); game.move.select(moveToUse); - await game.setTurnOrder(enemyFirst); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("BerryPhase"); expect(enemyPokemon.isFainted()).toBe(true); diff --git a/test/moves/encore.test.ts b/test/moves/encore.test.ts index 0840346c3b1..8e8853abf4c 100644 --- a/test/moves/encore.test.ts +++ b/test/moves/encore.test.ts @@ -78,8 +78,9 @@ describe("Moves - Encore", () => { game.move.select(MoveId.ENCORE); - const turnOrder = delay ? [BattlerIndex.PLAYER, BattlerIndex.ENEMY] : [BattlerIndex.ENEMY, BattlerIndex.PLAYER]; - await game.setTurnOrder(turnOrder); + await game.setTurnOrder( + delay ? [BattlerIndex.PLAYER, BattlerIndex.ENEMY] : [BattlerIndex.ENEMY, BattlerIndex.PLAYER], + ); await game.phaseInterceptor.to("BerryPhase", false); expect(playerPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.FAIL); @@ -88,25 +89,22 @@ describe("Moves - Encore", () => { }); it("Pokemon under both Encore and Torment should alternate between Struggle and restricted move", async () => { - const turnOrder = [BattlerIndex.ENEMY, BattlerIndex.PLAYER]; game.override.moveset([MoveId.ENCORE, MoveId.TORMENT, MoveId.SPLASH]); await game.classicMode.startBattle([SpeciesId.FEEBAS]); const enemyPokemon = game.field.getEnemyPokemon(); game.move.select(MoveId.ENCORE); - await game.setTurnOrder(turnOrder); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("BerryPhase"); expect(enemyPokemon.getTag(BattlerTagType.ENCORE)).toBeDefined(); await game.toNextTurn(); game.move.select(MoveId.TORMENT); - await game.setTurnOrder(turnOrder); await game.phaseInterceptor.to("BerryPhase"); expect(enemyPokemon.getTag(BattlerTagType.TORMENT)).toBeDefined(); await game.toNextTurn(); game.move.select(MoveId.SPLASH); - await game.setTurnOrder(turnOrder); await game.phaseInterceptor.to("BerryPhase"); const lastMove = enemyPokemon.getLastXMoves()[0]; expect(lastMove?.move).toBe(MoveId.STRUGGLE); diff --git a/test/moves/grudge.test.ts b/test/moves/grudge.test.ts index cc75024bfbf..4fd66066a22 100644 --- a/test/moves/grudge.test.ts +++ b/test/moves/grudge.test.ts @@ -64,7 +64,6 @@ describe("Moves - Grudge", () => { game.move.use(MoveId.GUILLOTINE); await game.move.forceEnemyMove(MoveId.SPLASH); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.toEndOfTurn(); expect(ratatta).toHaveFainted(); diff --git a/test/moves/instruct.test.ts b/test/moves/instruct.test.ts index eb3eccff400..d0053acc447 100644 --- a/test/moves/instruct.test.ts +++ b/test/moves/instruct.test.ts @@ -343,7 +343,6 @@ describe("Moves - Instruct", () => { expect(player.getLastXMoves()[0].result).toBe(MoveResult.FAIL); game.move.select(MoveId.INSTRUCT); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("TurnEndPhase", false); expect(player.getLastXMoves()[0].result).toBe(MoveResult.FAIL); diff --git a/test/moves/last-respects.test.ts b/test/moves/last-respects.test.ts index 9dadb316144..ea6f3f2f854 100644 --- a/test/moves/last-respects.test.ts +++ b/test/moves/last-respects.test.ts @@ -59,7 +59,6 @@ describe("Moves - Last Respects", () => { * Charmander faints once */ game.move.select(MoveId.EXPLOSION); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); game.doSelectPartyPokemon(2); await game.toNextTurn(); @@ -86,7 +85,6 @@ describe("Moves - Last Respects", () => { */ game.doRevivePokemon(1); game.move.select(MoveId.EXPLOSION); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); game.doSelectPartyPokemon(1); await game.toNextTurn(); @@ -99,7 +97,6 @@ describe("Moves - Last Respects", () => { await game.toNextTurn(); game.move.select(MoveId.LAST_RESPECTS); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.phaseInterceptor.to(MoveEffectPhase); expect(move.calculateBattlePower).toHaveReturnedWith(basePower + 3 * 50); @@ -127,7 +124,6 @@ describe("Moves - Last Respects", () => { * Enemy Pokemon faints and new wave is entered. */ game.move.select(MoveId.LAST_RESPECTS); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextWave(); expect(game.scene.arena.playerFaints).toBe(1); @@ -160,7 +156,6 @@ describe("Moves - Last Respects", () => { * Enemy Pokemon faints and new wave is entered. */ game.move.select(MoveId.LAST_RESPECTS); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextWave(); expect(game.scene.currentBattle.enemyFaints).toBe(0); @@ -184,7 +179,6 @@ describe("Moves - Last Respects", () => { await game.toNextTurn(); game.move.select(MoveId.LAST_RESPECTS); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextWave(); game.move.select(MoveId.LAST_RESPECTS); @@ -205,7 +199,6 @@ describe("Moves - Last Respects", () => { await game.toNextTurn(); game.move.select(MoveId.LAST_RESPECTS); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextWave(); game.move.select(MoveId.LAST_RESPECTS); diff --git a/test/moves/metronome.test.ts b/test/moves/metronome.test.ts index 2215c18f451..df51394ad00 100644 --- a/test/moves/metronome.test.ts +++ b/test/moves/metronome.test.ts @@ -95,17 +95,16 @@ describe("Moves - Metronome", () => { game.move.select(MoveId.METRONOME); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("TurnEndPhase"); + await game.toNextTurn(); - expect(player.getTag(BattlerTagType.CHARGING)).toBeTruthy(); + expect(player).toHaveBattlerTag(BattlerTagType.CHARGING); const turn1PpUsed = metronomeMove.ppUsed; expect.soft(turn1PpUsed).toBeGreaterThan(1); expect(solarBeamMove.ppUsed).toBe(0); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.toNextTurn(); - expect(player.getTag(BattlerTagType.CHARGING)).toBeFalsy(); + expect(player).not.toHaveBattlerTag(BattlerTagType.CHARGING); const turn2PpUsed = metronomeMove.ppUsed - turn1PpUsed; expect(turn2PpUsed).toBeGreaterThan(1); expect(solarBeamMove.ppUsed).toBe(0); diff --git a/test/moves/pledge-moves.test.ts b/test/moves/pledge-moves.test.ts index 34058829d07..3cba2cf6e18 100644 --- a/test/moves/pledge-moves.test.ts +++ b/test/moves/pledge-moves.test.ts @@ -166,7 +166,6 @@ describe("Moves - Pledge Moves", () => { game.move.select(MoveId.FIERY_DANCE, 0, BattlerIndex.ENEMY_2); game.move.select(MoveId.SPLASH, 1); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); await game.phaseInterceptor.to("MoveEndPhase"); // Rainbow effect should increase Fiery Dance's chance of raising Sp. Atk to 100% diff --git a/test/moves/rage-fist.test.ts b/test/moves/rage-fist.test.ts index c58d1296ac5..f883bef1242 100644 --- a/test/moves/rage-fist.test.ts +++ b/test/moves/rage-fist.test.ts @@ -85,7 +85,6 @@ describe("Moves - Rage Fist", () => { // remove substitute and get confused game.move.select(MoveId.TIDY_UP); await game.move.selectEnemyMove(MoveId.CONFUSE_RAY); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.toNextTurn(); game.move.select(MoveId.RAGE_FIST); @@ -108,7 +107,6 @@ describe("Moves - Rage Fist", () => { expect(game.field.getPlayerPokemon().battleData.hitCount).toBe(2); game.move.select(MoveId.RAGE_FIST); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("TurnEndPhase"); expect(game.field.getPlayerPokemon().battleData.hitCount).toBe(4); @@ -147,7 +145,6 @@ describe("Moves - Rage Fist", () => { await game.toNextTurn(); game.move.select(MoveId.RAGE_FIST); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("BerryPhase", false); expect(move.calculateBattlePower).toHaveLastReturnedWith(150); diff --git a/test/moves/roost.test.ts b/test/moves/roost.test.ts index bb567a41cd0..c47b3e4b72f 100644 --- a/test/moves/roost.test.ts +++ b/test/moves/roost.test.ts @@ -159,7 +159,6 @@ describe("Moves - Roost", () => { await game.phaseInterceptor.to(TurnEndPhase); game.move.select(MoveId.ROOST); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.phaseInterceptor.to(MoveEffectPhase); // Should only be typeless type after roost and is grounded @@ -195,7 +194,6 @@ describe("Moves - Roost", () => { await game.phaseInterceptor.to(TurnEndPhase); game.move.select(MoveId.ROOST); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.phaseInterceptor.to(MoveEffectPhase); // Should only be typeless type after roost and is grounded diff --git a/test/moves/sketch.test.ts b/test/moves/sketch.test.ts index 0c2527bc09c..62f2aa33bc6 100644 --- a/test/moves/sketch.test.ts +++ b/test/moves/sketch.test.ts @@ -71,7 +71,6 @@ describe("Moves - Sketch", () => { await game.toNextTurn(); game.move.select(MoveId.SKETCH); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.move.forceStatusActivation(true); await game.phaseInterceptor.to("TurnEndPhase"); expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); diff --git a/test/moves/spite.test.ts b/test/moves/spite.test.ts index 56c1be76198..af0f802819e 100644 --- a/test/moves/spite.test.ts +++ b/test/moves/spite.test.ts @@ -49,7 +49,6 @@ describe("Moves - Spite", () => { game.move.use(MoveId.SPITE); await game.move.selectEnemyMove(MoveId.SPLASH); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.toEndOfTurn(); expect(karp).toHaveUsedPP(MoveId.TACKLE, 4 + 1); diff --git a/test/moves/wish.test.ts b/test/moves/wish.test.ts index b64a15ac654..ef46c85eb73 100644 --- a/test/moves/wish.test.ts +++ b/test/moves/wish.test.ts @@ -109,14 +109,14 @@ describe("Move - Wish", () => { vi.spyOn(karp1, "getNameToRender").mockReturnValue("Karp 1"); vi.spyOn(karp2, "getNameToRender").mockReturnValue("Karp 2"); - const oldOrder = game.field.getSpeedOrder(); + const oldOrder = game.field.getSpeedOrder(true); game.move.use(MoveId.WISH, BattlerIndex.PLAYER); game.move.use(MoveId.WISH, BattlerIndex.PLAYER_2); await game.move.forceEnemyMove(MoveId.WISH); await game.move.forceEnemyMove(MoveId.WISH); // Ensure that the wishes are used deterministically in speed order (for speed ties) - await game.setTurnOrder(oldOrder.map(p => p.getBattlerIndex())); + await game.setTurnOrder(oldOrder); await game.toNextTurn(); expect(game).toHavePositionalTag(PositionalTagType.WISH, 4); @@ -137,7 +137,9 @@ describe("Move - Wish", () => { const healPhases = game.scene.phaseManager["phaseQueue"].findAll("PokemonHealPhase"); expect(healPhases).toHaveLength(4); - expect.soft(healPhases.map(php => php.getPokemon())).toEqual(oldOrder); + expect + .soft(healPhases.map(php => php.getPokemon().getBattlerIndex(), "Wishes were not queued in correct order!")) + .toEqual(oldOrder); await game.toEndOfTurn(); diff --git a/test/phases/frenzy-move-reset.test.ts b/test/phases/frenzy-move-reset.test.ts index feedd1b5865..7872a56d716 100644 --- a/test/phases/frenzy-move-reset.test.ts +++ b/test/phases/frenzy-move-reset.test.ts @@ -62,7 +62,6 @@ describe("Frenzy Move Reset", () => { expect(playerPokemon.summonData.moveQueue.length).toBe(2); expect(playerPokemon.summonData.tags.some(tag => tag.tagType === BattlerTagType.FRENZY)).toBe(true); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.move.forceStatusActivation(true); await game.toNextTurn(); diff --git a/test/test-utils/game-manager.ts b/test/test-utils/game-manager.ts index abe0b8cfcf6..543fb37e00f 100644 --- a/test/test-utils/game-manager.ts +++ b/test/test-utils/game-manager.ts @@ -14,7 +14,7 @@ import { PlayerGender } from "#enums/player-gender"; import type { PokeballType } from "#enums/pokeball"; import type { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; -import type { EnemyPokemon, PlayerPokemon } from "#field/pokemon"; +import { type EnemyPokemon, type PlayerPokemon, Pokemon } from "#field/pokemon"; import { Trainer } from "#field/trainer"; import { ModifierTypeOption } from "#modifiers/modifier-type"; import { CheckSwitchPhase } from "#phases/check-switch-phase"; @@ -52,6 +52,7 @@ import type { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import type { PartyUiHandler } from "#ui/party-ui-handler"; import type { StarterSelectUiHandler } from "#ui/starter-select-ui-handler"; import type { TargetSelectUiHandler } from "#ui/target-select-ui-handler"; +import * as speedOrderUtils from "#utils/speed-order"; import fs from "node:fs"; import { AES, enc } from "crypto-js"; import { expect, vi } from "vitest"; @@ -536,19 +537,40 @@ export class GameManager { } /** - * Modifies the queue manager to return move phases in a particular order - * Used to manually modify Pokemon turn order. - * Note: This *DOES NOT* account for priority. - * @param order - The turn order to set as an array of {@linkcode BattlerIndex}es. + * Override the turn order of the battle's current combatants. + * @param order - The turn order to set, as an array of {@linkcode BattlerIndex}es * @example * ```ts - * await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2]); + * game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2]); * ``` + * @throws Fails test immediately if `order` does not contain all non-fainted combatants' `BattlerIndex`es. + * @remarks + * This does not account for priority, nor does it change the battlers' speed stats + * (for the purposes of Electro Ball, etc). + * @todo What should happen if the number of active battlers changes mid-test? + * @todo Remove `await`s from existing test files in a follow-up PR */ - async setTurnOrder(order: BattlerIndex[]): Promise { - await this.phaseInterceptor.to("TurnStartPhase", false); + public setTurnOrder(order: Exclude[]): void { + // TODO: Remove type assertions once `BattlerIndex.ATTACKER` ceases to exist + expect(order).toEqualUnsorted( + this.scene.getField(true).map(p => p.getBattlerIndex() as Exclude), + ); - this.scene.phaseManager.dynamicQueueManager.setMoveOrder(order); + // NB: This will need to be changed if `sortInSpeedOrder`'s order is ever changed + vi.spyOn(speedOrderUtils, "sortInSpeedOrder").mockImplementation(list => { + list.sort((a, b) => { + const aBattlerIndex = (a instanceof Pokemon ? a : a.getPokemon()).getBattlerIndex() as Exclude< + BattlerIndex, + BattlerIndex.ATTACKER + >; + const bBattlerIndex = (b instanceof Pokemon ? b : b.getPokemon()).getBattlerIndex() as Exclude< + BattlerIndex, + BattlerIndex.ATTACKER + >; + + return order.indexOf(aBattlerIndex) - order.indexOf(bBattlerIndex); + }); + }); } /** diff --git a/test/test-utils/helpers/field-helper.ts b/test/test-utils/helpers/field-helper.ts index 29eb70ae20c..6e52a9e3dde 100644 --- a/test/test-utils/helpers/field-helper.ts +++ b/test/test-utils/helpers/field-helper.ts @@ -61,14 +61,14 @@ export class FieldHelper extends GameManagerHelper { * Helper function to return all on-field {@linkcode Pokemon} in speed order (fastest first). * @param indices - Whether to only return {@linkcode BattlerIndex}es instead of full Pokemon objects * (such as for comparison with other speed order-related mechanisms); default `false` - * @returns An array containing the {@linkcode BattlerIndex}es of all on-field {@linkcode Pokemon} on the field in order of descending Speed. \ + * @returns An array containing the {@linkcode BattlerIndex}es of all on-field `Pokemon` on the field in order of **descending** Speed. \ * Speed ties are returned in increasing order of index. * * @remarks * This does not account for Trick Room as it does not modify the _speed_ of Pokemon on the field, * only their turn order. */ - public getSpeedOrder(indices: true): BattlerIndex[]; + public getSpeedOrder(indices: true): Exclude[]; public getSpeedOrder(indices = false): BattlerIndex[] | Pokemon[] { const ret = this.game.scene .getField(true)