mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-09-23 06:53:27 +02:00
Merge ba83f18e2a
into 8d5ba221d8
This commit is contained in:
commit
6b96e9160a
12
biome.jsonc
12
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"
|
||||
}
|
||||
}
|
||||
|
@ -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<T extends PhaseString>(type: T, condition?: PhaseConditionFunc<T>): boolean {
|
||||
public exists<T extends PhaseString>(type: T, condition: PhaseConditionFunc<T> = () => 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`.
|
||||
|
@ -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();
|
||||
|
@ -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<P extends PhaseString>(phaseType: P, phaseFilter?: PhaseConditionFunc<P>): 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<P extends PhaseString>(phaseType: P, phaseFilter?: PhaseConditionFunc<P>): 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<P extends PhaseString>(phaseType: P, phaseFilter?: PhaseConditionFunc<P>): 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))));
|
||||
}
|
||||
}
|
||||
|
@ -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<MovePhase>
|
||||
}
|
||||
|
||||
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<MovePhase>
|
||||
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<MovePhase>
|
||||
}
|
||||
}
|
||||
|
||||
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<MovePhase>
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -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<T extends DynamicPhase> extends PriorityQueue<T> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,6 @@ import { sortInSpeedOrder } from "#app/utils/speed-order";
|
||||
/** A priority queue of {@linkcode Pokemon}s */
|
||||
export class PokemonPriorityQueue extends PriorityQueue<Pokemon> {
|
||||
protected override reorder(): void {
|
||||
this.queue = sortInSpeedOrder(this.queue);
|
||||
sortInSpeedOrder(this.queue);
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import { sortInSpeedOrder } from "#app/utils/speed-order";
|
||||
*/
|
||||
export class PostSummonPhasePriorityQueue extends PokemonPhasePriorityQueue<PostSummonPhase> {
|
||||
protected override reorder(): void {
|
||||
this.queue = sortInSpeedOrder(this.queue, false);
|
||||
sortInSpeedOrder(this.queue, false);
|
||||
this.queue.sort((phaseA, phaseB) => phaseB.getPriority() - phaseA.getPriority());
|
||||
}
|
||||
|
||||
|
@ -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<T> {
|
||||
/** 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<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all elements from the queue
|
||||
* Remove all elements from the queue.
|
||||
* @sealed
|
||||
*/
|
||||
public clear(): void {
|
||||
@ -50,8 +55,8 @@ export abstract class PriorityQueue<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<T> {
|
||||
}
|
||||
|
||||
/** @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);
|
||||
}
|
||||
}
|
||||
|
@ -135,14 +135,17 @@ export function randSeedItem<T>(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<T>(items: T[]): T[] {
|
||||
export function randSeedShuffle<T>(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]];
|
||||
|
@ -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
|
||||
|
@ -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<T extends Pokemon | hasPokemon>(pokemonList: T[], shuffleFirst = true): T[] {
|
||||
pokemonList = shuffleFirst ? shufflePokemonList(pokemonList) : pokemonList;
|
||||
export function sortInSpeedOrder<T extends Pokemon | hasPokemon>(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<T extends Pokemon | hasPokemon>(pokemonList: T[]): T[] {
|
||||
function shufflePokemonList<T extends Pokemon | hasPokemon>(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<T extends Pokemon | hasPokemon>(pokemonList: T[]): void {
|
||||
pokemonList.sort((a, b) => {
|
||||
const aSpeed = (a instanceof Pokemon ? a : a.getPokemon()).getEffectiveStat(Stat.SPD);
|
||||
|
@ -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
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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 () => {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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%
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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<void> {
|
||||
await this.phaseInterceptor.to("TurnStartPhase", false);
|
||||
public setTurnOrder(order: Exclude<BattlerIndex, BattlerIndex.ATTACKER>[]): void {
|
||||
// TODO: Remove type assertions once `BattlerIndex.ATTACKER` ceases to exist
|
||||
expect(order).toEqualUnsorted(
|
||||
this.scene.getField(true).map(p => p.getBattlerIndex() as Exclude<BattlerIndex, BattlerIndex.ATTACKER>),
|
||||
);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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<BattlerIndex, BattlerIndex.ATTACKER>[];
|
||||
public getSpeedOrder(indices = false): BattlerIndex[] | Pokemon[] {
|
||||
const ret = this.game.scene
|
||||
.getField(true)
|
||||
|
Loading…
Reference in New Issue
Block a user