mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-09-23 15:03:24 +02:00
Merge ba83f18e2a
into 8d5ba221d8
This commit is contained in:
commit
6b96e9160a
12
biome.jsonc
12
biome.jsonc
@ -236,7 +236,7 @@
|
|||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"includes": ["**/test/**/*.test.ts"],
|
"includes": ["**/test/**/*.ts"],
|
||||||
"linter": {
|
"linter": {
|
||||||
"rules": {
|
"rules": {
|
||||||
"performance": {
|
"performance": {
|
||||||
@ -245,8 +245,16 @@
|
|||||||
},
|
},
|
||||||
"style": {
|
"style": {
|
||||||
"noNonNullAssertion": "off" // tedious in some tests
|
"noNonNullAssertion": "off" // tedious in some tests
|
||||||
},
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"includes": ["**/test/**/*.test.ts"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
"nursery": {
|
"nursery": {
|
||||||
|
// TODO: Enable for normal test folder files as well
|
||||||
"noFloatingPromises": "error"
|
"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 { PokemonPhasePriorityQueue } from "#app/queues/pokemon-phase-priority-queue";
|
||||||
import { PostSummonPhasePriorityQueue } from "#app/queues/post-summon-phase-priority-queue";
|
import { PostSummonPhasePriorityQueue } from "#app/queues/post-summon-phase-priority-queue";
|
||||||
import type { PriorityQueue } from "#app/queues/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";
|
import type { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier";
|
||||||
|
|
||||||
// TODO: might be easier to define which phases should be dynamic instead
|
// 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
|
* Returns the highest-priority (generally by speed) {@linkcode Phase} of the specified type.
|
||||||
* @param type - The {@linkcode PhaseString | type} to pop
|
* @param type - The {@linkcode PhaseString | type} of phase to access
|
||||||
* @returns The popped {@linkcode Phase}, or `undefined` if none of the specified type exist
|
* @returns The popped {@linkcode Phase}, or `undefined` if none of the specified type exist
|
||||||
*/
|
*/
|
||||||
public popNextPhase(type: PhaseString): Phase | undefined {
|
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
|
* @param condition - An optional {@linkcode PhaseConditionFunc} to add conditions to the search
|
||||||
* @returns Whether a matching phase exists
|
* @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);
|
return !!this.dynamicPhaseMap.get(type)?.has(condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,21 +140,13 @@ export class DynamicQueueManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds and cancels a {@linkcode MovePhase} meeting the condition
|
* Find and cancel a {@linkcode MovePhase} meeting the condition
|
||||||
* @param phaseCondition - The {@linkcode PhaseConditionFunc | condition} function
|
* @param phaseCondition - The {@linkcode PhaseConditionFunc | condition} function to filter phases by
|
||||||
*/
|
*/
|
||||||
public cancelMovePhase(condition: PhaseConditionFunc<"MovePhase">): void {
|
public cancelMovePhase(condition: PhaseConditionFunc<"MovePhase">): void {
|
||||||
this.getMovePhaseQueue().cancelMove(condition);
|
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
|
* @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.
|
* Internal helper to determine if a phase is dynamic.
|
||||||
* @param phase - The {@linkcode Phase} to check
|
* @param phase - The {@linkcode Phase} to check
|
||||||
* @returns Whether `phase` is dynamic
|
* @returns Whether `phase` is dynamic.
|
||||||
* @privateRemarks
|
* @privateRemarks
|
||||||
* Currently, this checks that `phase` has a `getPokemon` method
|
* Currently, this checks that `phase` has a `getPokemon` method
|
||||||
* and is not blacklisted in `nonDynamicPokemonPhases`.
|
* 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
|
* @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 {
|
public isOpponent(target: Pokemon): boolean {
|
||||||
return this.isPlayer() !== target.isPlayer();
|
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
|
* @returns - The next {@linkcode Phase}, or `undefined` if the Tree is empty
|
||||||
*/
|
*/
|
||||||
public getNextPhase(): Phase | undefined {
|
public getNextPhase(): Phase | undefined {
|
||||||
|
// Clear out all empty levels from the tree
|
||||||
this.currentLevel = this.levels.length - 1;
|
this.currentLevel = this.levels.length - 1;
|
||||||
while (this.currentLevel > 0 && this.levels[this.currentLevel].length === 0) {
|
while (this.currentLevel > 0 && this.levels[this.currentLevel].length === 0) {
|
||||||
this.deferredActive = false;
|
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 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 add conditions to the search
|
||||||
* @returns The matching {@linkcode Phase}, or `undefined` if none exists
|
* @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 {
|
public find<P extends PhaseString>(phaseType: P, phaseFilter?: PhaseConditionFunc<P>): PhaseMap[P] | undefined {
|
||||||
for (let i = this.levels.length - 1; i >= 0; i--) {
|
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 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 add conditions to the search
|
||||||
* @returns The matching {@linkcode Phase}, or `undefined` if none exists
|
* @returns An array containing all `Phase`s matching the criteria.
|
||||||
*/
|
*/
|
||||||
public findAll<P extends PhaseString>(phaseType: P, phaseFilter?: PhaseConditionFunc<P>): PhaseMap[P][] {
|
public findAll<P extends PhaseString>(phaseType: P, phaseFilter?: PhaseConditionFunc<P>): PhaseMap[P][] {
|
||||||
const phases: PhaseMap[P][] = [];
|
const phases: PhaseMap[P][] = [];
|
||||||
for (let i = this.levels.length - 1; i >= 0; i--) {
|
for (let i = this.levels.length - 1; i >= 0; i--) {
|
||||||
const level = this.levels[i];
|
const level = this.levels[i];
|
||||||
const levelPhases = level.filter((p): p is PhaseMap[P] => p.is(phaseType) && (!phaseFilter || phaseFilter(p)));
|
phases.push(...level.filter((p): p is PhaseMap[P] => p.is(phaseType) && (!phaseFilter || phaseFilter(p))));
|
||||||
phases.push(...levelPhases);
|
|
||||||
}
|
}
|
||||||
return phases;
|
return phases;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the Tree
|
* Clear all Phases from the Tree.
|
||||||
* @param leaveFirstLevel - If `true`, leaves the top level of the tree intact
|
* @param leaveFirstLevel - Whether to leave the top level of the tree intact; default `false`
|
||||||
*
|
* @privateRemarks
|
||||||
* @privateremarks
|
|
||||||
* The parameter on this method exists because {@linkcode PhaseManager.clearPhaseQueue} previously (probably by mistake) ignored `phaseQueuePrepend`.
|
* 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.
|
* This is (probably by mistake) relied upon by certain ME functions.
|
||||||
@ -181,25 +180,17 @@ export class PhaseTree {
|
|||||||
*/
|
*/
|
||||||
public removeAll(phaseType: PhaseString): void {
|
public removeAll(phaseType: PhaseString): void {
|
||||||
for (let i = 0; i < this.levels.length; i++) {
|
for (let i = 0; i < this.levels.length; i++) {
|
||||||
const level = this.levels[i].filter(phase => !phase.is(phaseType));
|
this.levels[i] = this.levels[i].filter(phase => !phase.is(phaseType));
|
||||||
this.levels[i] = level;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 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
|
* @returns Whether a matching phase exists
|
||||||
*/
|
*/
|
||||||
public exists<P extends PhaseString>(phaseType: P, phaseFilter?: PhaseConditionFunc<P>): boolean {
|
public exists<P extends PhaseString>(phaseType: P, phaseFilter?: PhaseConditionFunc<P>): boolean {
|
||||||
for (const level of this.levels) {
|
return this.levels.some(level => level.some(phase => phase.is(phaseType) && (!phaseFilter || phaseFilter(phase))));
|
||||||
for (const phase of level) {
|
|
||||||
if (phase.is(phaseType) && (!phaseFilter || phaseFilter(phase))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ import type { Pokemon } from "#app/field/pokemon";
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import type { MovePhase } from "#app/phases/move-phase";
|
import type { MovePhase } from "#app/phases/move-phase";
|
||||||
import { PokemonPhasePriorityQueue } from "#app/queues/pokemon-phase-priority-queue";
|
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 { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier";
|
||||||
import type { PhaseConditionFunc } from "#types/phase-types";
|
import type { PhaseConditionFunc } from "#types/phase-types";
|
||||||
|
|
||||||
@ -17,18 +16,18 @@ export class MovePhasePriorityQueue extends PokemonPhasePriorityQueue<MovePhase>
|
|||||||
}
|
}
|
||||||
|
|
||||||
public cancelMove(condition: PhaseConditionFunc<"MovePhase">): void {
|
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 {
|
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) {
|
if (phase != null) {
|
||||||
phase.timingModifier = modifier;
|
phase.timingModifier = modifier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public setMoveForPhase(condition: PhaseConditionFunc<"MovePhase">, move: PokemonMove) {
|
public setMoveForPhase(condition: PhaseConditionFunc<"MovePhase">, move: PokemonMove) {
|
||||||
const phase = this.queue.find(p => condition(p));
|
const phase = this.queue.find(condition);
|
||||||
if (phase != null) {
|
if (phase != null) {
|
||||||
phase.move = move;
|
phase.move = move;
|
||||||
}
|
}
|
||||||
@ -47,7 +46,7 @@ export class MovePhasePriorityQueue extends PokemonPhasePriorityQueue<MovePhase>
|
|||||||
mp =>
|
mp =>
|
||||||
mp.targets.length === 1
|
mp.targets.length === 1
|
||||||
&& mp.targets[0] === removedPokemon.getBattlerIndex()
|
&& mp.targets[0] === removedPokemon.getBattlerIndex()
|
||||||
&& mp.pokemon.isPlayer() !== allyPokemon.isPlayer(),
|
&& mp.pokemon.isOpponent(allyPokemon),
|
||||||
)
|
)
|
||||||
.forEach(targetingMovePhase => {
|
.forEach(targetingMovePhase => {
|
||||||
if (targetingMovePhase && targetingMovePhase.targets[0] !== allyPokemon.getBattlerIndex()) {
|
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 {
|
public override pop(): MovePhase | undefined {
|
||||||
this.reorder();
|
this.reorder();
|
||||||
const phase = this.queue.shift();
|
const phase = this.queue.shift();
|
||||||
@ -79,25 +74,20 @@ export class MovePhasePriorityQueue extends PokemonPhasePriorityQueue<MovePhase>
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override clear(): void {
|
public override clear(): void {
|
||||||
this.setOrder = undefined;
|
|
||||||
this.lastTurnOrder = [];
|
this.lastTurnOrder = [];
|
||||||
super.clear();
|
super.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private sortPostSpeed(): void {
|
private sortPostSpeed(): void {
|
||||||
this.queue.sort((a: MovePhase, b: MovePhase) => {
|
this.queue.sort(
|
||||||
const priority = [a, b].map(movePhase => {
|
(a: MovePhase, b: MovePhase) =>
|
||||||
const move = movePhase.move.getMove();
|
// formatting
|
||||||
return move.getPriority(movePhase.pokemon, true);
|
b.timingModifier - a.timingModifier || getPriorityForMP(b) - getPriorityForMP(a),
|
||||||
});
|
);
|
||||||
|
|
||||||
const timingModifiers = [a, b].map(movePhase => movePhase.timingModifier);
|
|
||||||
|
|
||||||
if (timingModifiers[0] !== timingModifiers[1]) {
|
|
||||||
return timingModifiers[1] - timingModifiers[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return priority[1] - priority[0];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 type { DynamicPhase } from "#app/@types/phase-types";
|
||||||
import { PriorityQueue } from "#app/queues/priority-queue";
|
import { PriorityQueue } from "#app/queues/priority-queue";
|
||||||
import { sortInSpeedOrder } from "#app/utils/speed-order";
|
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> {
|
export class PokemonPhasePriorityQueue<T extends DynamicPhase> extends PriorityQueue<T> {
|
||||||
protected setOrder: BattlerIndex[] | undefined;
|
|
||||||
protected override reorder(): void {
|
protected override reorder(): void {
|
||||||
const setOrder = this.setOrder;
|
sortInSpeedOrder(this.queue);
|
||||||
if (setOrder) {
|
|
||||||
this.queue.sort(
|
|
||||||
(a, b) =>
|
|
||||||
setOrder.indexOf(a.getPokemon().getBattlerIndex()) - setOrder.indexOf(b.getPokemon().getBattlerIndex()),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.queue = sortInSpeedOrder(this.queue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,6 @@ import { sortInSpeedOrder } from "#app/utils/speed-order";
|
|||||||
/** A priority queue of {@linkcode Pokemon}s */
|
/** A priority queue of {@linkcode Pokemon}s */
|
||||||
export class PokemonPriorityQueue extends PriorityQueue<Pokemon> {
|
export class PokemonPriorityQueue extends PriorityQueue<Pokemon> {
|
||||||
protected override reorder(): void {
|
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> {
|
export class PostSummonPhasePriorityQueue extends PokemonPhasePriorityQueue<PostSummonPhase> {
|
||||||
protected override reorder(): void {
|
protected override reorder(): void {
|
||||||
this.queue = sortInSpeedOrder(this.queue, false);
|
sortInSpeedOrder(this.queue, false);
|
||||||
this.queue.sort((phaseA, phaseB) => phaseB.getPriority() - phaseA.getPriority());
|
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> {
|
export abstract class PriorityQueue<T> {
|
||||||
|
/** The items in the queue. */
|
||||||
protected queue: T[] = [];
|
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;
|
protected abstract reorder(): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls {@linkcode reorder} and shifts the queue
|
* Reorder the queue before removing and returning the highest priority element.
|
||||||
* @returns The front element of the queue after sorting, or `undefined` if the queue is empty
|
* @returns The front-most element of the queue after sorting,
|
||||||
|
* or `undefined` if the queue is empty.
|
||||||
* @sealed
|
* @sealed
|
||||||
*/
|
*/
|
||||||
public pop(): T | undefined {
|
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
|
* @sealed
|
||||||
*/
|
*/
|
||||||
public clear(): void {
|
public clear(): void {
|
||||||
@ -50,8 +55,8 @@ export abstract class PriorityQueue<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the first element matching the condition
|
* Remove the first element matching the condition
|
||||||
* @param condition - An optional condition function (defaults to a function that always returns `true`)
|
* @param condition - If provided, will restrict the removal to only phases matching the condition
|
||||||
* @returns Whether a removal occurred
|
* @returns Whether a removal occurred
|
||||||
*/
|
*/
|
||||||
public remove(condition: (t: T) => boolean = () => true): boolean {
|
public remove(condition: (t: T) => boolean = () => true): boolean {
|
||||||
@ -67,12 +72,12 @@ export abstract class PriorityQueue<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @returns An element matching the condition function */
|
/** @returns An element matching the condition function */
|
||||||
public find(condition?: (t: T) => boolean): T | undefined {
|
public find(condition: (t: T) => boolean): T | undefined {
|
||||||
return this.queue.find(e => !condition || condition(e));
|
return this.queue.find(condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns Whether an element matching the condition function exists */
|
/** @returns Whether an element matching the condition function exists */
|
||||||
public has(condition?: (t: T) => boolean): boolean {
|
public has(condition: (t: T) => boolean): boolean {
|
||||||
return this.queue.some(e => !condition || condition(e));
|
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.
|
* 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.
|
* @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) {
|
if (items.length <= 1) {
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
const newArray = items.slice(0);
|
|
||||||
|
const newArray = mutate ? items.slice(0) : items;
|
||||||
for (let i = items.length - 1; i > 0; i--) {
|
for (let i = items.length - 1; i > 0; i--) {
|
||||||
const j = Phaser.Math.RND.integerInRange(0, i);
|
const j = Phaser.Math.RND.integerInRange(0, i);
|
||||||
[newArray[i], newArray[j]] = [newArray[j], newArray[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.
|
* 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}
|
* @returns A {@linkcode Generator} of {@linkcode Pokemon}
|
||||||
*
|
*
|
||||||
* @remarks
|
* @remarks
|
||||||
|
@ -10,35 +10,36 @@ interface hasPokemon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sorts an array of {@linkcode Pokemon} by speed, taking Trick Room into account.
|
* Sort an array of {@linkcode Pokemon} in speed order, taking Trick Room into account.
|
||||||
* @param pokemonList - The list of Pokemon or objects containing Pokemon
|
* @param pokemonList - An array of `Pokemon` or objects containing `Pokemon` to sort;
|
||||||
* @param shuffleFirst - Whether to shuffle the list before sorting (to handle speed ties). Default `true`.
|
* will be mutated and sorted in place.
|
||||||
* @returns The sorted array of {@linkcode Pokemon}
|
* @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[] {
|
export function sortInSpeedOrder<T extends Pokemon | hasPokemon>(pokemonList: T[], shuffleFirst = true): void {
|
||||||
pokemonList = shuffleFirst ? shufflePokemonList(pokemonList) : pokemonList;
|
if (shuffleFirst) {
|
||||||
|
shufflePokemonList(pokemonList);
|
||||||
|
}
|
||||||
sortBySpeed(pokemonList);
|
sortBySpeed(pokemonList);
|
||||||
return pokemonList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param pokemonList - The array of Pokemon or objects containing Pokemon
|
* Helper function to randomly shuffle an array of Pokemon.
|
||||||
* @returns The shuffled array
|
* @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
|
// This is seeded with the current turn to prevent an inconsistency where it
|
||||||
// was varying based on how long since you last reloaded
|
// was varying based on how long since you last reloaded
|
||||||
globalScene.executeWithSeedOffset(
|
globalScene.executeWithSeedOffset(
|
||||||
() => {
|
() => {
|
||||||
pokemonList = randSeedShuffle(pokemonList);
|
randSeedShuffle(pokemonList, true);
|
||||||
},
|
},
|
||||||
globalScene.currentBattle.turn * 1000 + pokemonList.length,
|
globalScene.currentBattle.turn * 1000 + pokemonList.length,
|
||||||
globalScene.waveSeed,
|
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 {
|
function sortBySpeed<T extends Pokemon | hasPokemon>(pokemonList: T[]): void {
|
||||||
pokemonList.sort((a, b) => {
|
pokemonList.sort((a, b) => {
|
||||||
const aSpeed = (a instanceof Pokemon ? a : a.getPokemon()).getEffectiveStat(Stat.SPD);
|
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);
|
game.move.select(MoveId.SPLASH, 1);
|
||||||
await game.move.selectEnemyMove(MoveId.ENTRAINMENT, BattlerIndex.PLAYER_2);
|
await game.move.selectEnemyMove(MoveId.ENTRAINMENT, BattlerIndex.PLAYER_2);
|
||||||
await game.move.selectEnemyMove(MoveId.SPLASH);
|
await game.move.selectEnemyMove(MoveId.SPLASH);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
|
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeUndefined(); // No neut gas users are left
|
expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeUndefined(); // No neut gas users are left
|
||||||
});
|
});
|
||||||
|
@ -97,7 +97,6 @@ describe("Abilities - Protosynthesis", () => {
|
|||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
game.move.select(MoveId.TACKLE);
|
game.move.select(MoveId.TACKLE);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
const boosted_dmg = initialHp - enemy.hp;
|
const boosted_dmg = initialHp - enemy.hp;
|
||||||
expect(boosted_dmg).toBeGreaterThan(unboosted_dmg);
|
expect(boosted_dmg).toBeGreaterThan(unboosted_dmg);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { AbilityId } from "#enums/ability-id";
|
import { AbilityId } from "#enums/ability-id";
|
||||||
|
import { BattlerIndex } from "#enums/battler-index";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
import { GameManager } from "#test/test-utils/game-manager";
|
import { GameManager } from "#test/test-utils/game-manager";
|
||||||
@ -24,7 +25,7 @@ describe("Abilities - Stall", () => {
|
|||||||
game.override
|
game.override
|
||||||
.battleStyle("single")
|
.battleStyle("single")
|
||||||
.criticalHits(false)
|
.criticalHits(false)
|
||||||
.enemySpecies(SpeciesId.REGIELEKI)
|
.enemySpecies(SpeciesId.SHUCKLE)
|
||||||
.enemyAbility(AbilityId.STALL)
|
.enemyAbility(AbilityId.STALL)
|
||||||
.enemyMoveset(MoveId.QUICK_ATTACK)
|
.enemyMoveset(MoveId.QUICK_ATTACK)
|
||||||
.moveset([MoveId.QUICK_ATTACK, MoveId.TACKLE]);
|
.moveset([MoveId.QUICK_ATTACK, MoveId.TACKLE]);
|
||||||
@ -42,7 +43,7 @@ describe("Abilities - Stall", () => {
|
|||||||
const player = game.field.getPlayerPokemon();
|
const player = game.field.getPlayerPokemon();
|
||||||
|
|
||||||
game.move.select(MoveId.QUICK_ATTACK);
|
game.move.select(MoveId.QUICK_ATTACK);
|
||||||
|
game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
await game.phaseInterceptor.to("MoveEndPhase", false);
|
await game.phaseInterceptor.to("MoveEndPhase", false);
|
||||||
// The player Pokemon (without Stall) goes first despite having lower speed than the opponent.
|
// 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.
|
// 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();
|
const player = game.field.getPlayerPokemon();
|
||||||
|
|
||||||
game.move.select(MoveId.TACKLE);
|
game.move.select(MoveId.TACKLE);
|
||||||
|
game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
|
|
||||||
await game.phaseInterceptor.to("MoveEndPhase", false);
|
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.
|
// 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();
|
const player = game.field.getPlayerPokemon();
|
||||||
|
|
||||||
game.move.select(MoveId.TACKLE);
|
game.move.select(MoveId.TACKLE);
|
||||||
|
game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
|
|
||||||
await game.phaseInterceptor.to("MoveEndPhase", false);
|
await game.phaseInterceptor.to("MoveEndPhase", false);
|
||||||
|
|
||||||
|
@ -53,12 +53,10 @@ describe("Abilities - Supreme Overlord", () => {
|
|||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
game.move.select(MoveId.EXPLOSION);
|
game.move.select(MoveId.EXPLOSION);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
||||||
game.doSelectPartyPokemon(2);
|
game.doSelectPartyPokemon(2);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
game.move.select(MoveId.TACKLE);
|
game.move.select(MoveId.TACKLE);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
await game.phaseInterceptor.to(MoveEffectPhase);
|
||||||
|
|
||||||
expect(move.calculateBattlePower).toHaveReturnedWith(basePower * 1.2);
|
expect(move.calculateBattlePower).toHaveReturnedWith(basePower * 1.2);
|
||||||
@ -80,7 +78,6 @@ describe("Abilities - Supreme Overlord", () => {
|
|||||||
*/
|
*/
|
||||||
game.doRevivePokemon(1);
|
game.doRevivePokemon(1);
|
||||||
game.move.select(MoveId.EXPLOSION);
|
game.move.select(MoveId.EXPLOSION);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
||||||
game.doSelectPartyPokemon(1);
|
game.doSelectPartyPokemon(1);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
@ -88,12 +85,10 @@ describe("Abilities - Supreme Overlord", () => {
|
|||||||
* Bulbasur faints twice
|
* Bulbasur faints twice
|
||||||
*/
|
*/
|
||||||
game.move.select(MoveId.EXPLOSION);
|
game.move.select(MoveId.EXPLOSION);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
||||||
game.doSelectPartyPokemon(2);
|
game.doSelectPartyPokemon(2);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
game.move.select(MoveId.TACKLE);
|
game.move.select(MoveId.TACKLE);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
await game.phaseInterceptor.to(MoveEffectPhase);
|
||||||
|
|
||||||
expect(move.calculateBattlePower).toHaveReturnedWith(basePower * 1.3);
|
expect(move.calculateBattlePower).toHaveReturnedWith(basePower * 1.3);
|
||||||
@ -116,7 +111,6 @@ describe("Abilities - Supreme Overlord", () => {
|
|||||||
* Enemy Pokemon faints and new wave is entered.
|
* Enemy Pokemon faints and new wave is entered.
|
||||||
*/
|
*/
|
||||||
game.move.select(MoveId.TACKLE);
|
game.move.select(MoveId.TACKLE);
|
||||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
|
||||||
await game.toNextWave();
|
await game.toNextWave();
|
||||||
|
|
||||||
game.move.select(MoveId.TACKLE);
|
game.move.select(MoveId.TACKLE);
|
||||||
@ -137,7 +131,6 @@ describe("Abilities - Supreme Overlord", () => {
|
|||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
game.move.select(MoveId.TACKLE);
|
game.move.select(MoveId.TACKLE);
|
||||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
|
||||||
await game.toNextWave();
|
await game.toNextWave();
|
||||||
|
|
||||||
game.move.select(MoveId.TACKLE);
|
game.move.select(MoveId.TACKLE);
|
||||||
@ -158,7 +151,6 @@ describe("Abilities - Supreme Overlord", () => {
|
|||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
game.move.select(MoveId.TACKLE);
|
game.move.select(MoveId.TACKLE);
|
||||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
|
||||||
await game.toNextWave();
|
await game.toNextWave();
|
||||||
|
|
||||||
game.move.select(MoveId.TACKLE);
|
game.move.select(MoveId.TACKLE);
|
||||||
|
@ -70,7 +70,6 @@ describe("Items - Multi Lens", () => {
|
|||||||
const playerPokemon = game.field.getPlayerPokemon();
|
const playerPokemon = game.field.getPlayerPokemon();
|
||||||
|
|
||||||
game.move.select(MoveId.TACKLE);
|
game.move.select(MoveId.TACKLE);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
||||||
|
|
||||||
await game.phaseInterceptor.to("MoveEndPhase");
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
expect(playerPokemon.turnData.hitCount).toBe(3);
|
expect(playerPokemon.turnData.hitCount).toBe(3);
|
||||||
|
@ -111,7 +111,6 @@ describe("Moves - Baton Pass", () => {
|
|||||||
expect(enemy.getTag(BattlerTagType.FIRE_SPIN)).toBeDefined();
|
expect(enemy.getTag(BattlerTagType.FIRE_SPIN)).toBeDefined();
|
||||||
|
|
||||||
game.move.select(MoveId.BATON_PASS);
|
game.move.select(MoveId.BATON_PASS);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
||||||
|
|
||||||
game.doSelectPartyPokemon(1);
|
game.doSelectPartyPokemon(1);
|
||||||
await game.toNextTurn();
|
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 () => {
|
it("should trigger multiple pending attacks in order of creation, even if that order changes later on", async () => {
|
||||||
game.override.battleStyle("double");
|
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, BattlerIndex.ENEMY);
|
||||||
game.move.use(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2);
|
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);
|
||||||
await game.move.forceEnemyMove(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER_2);
|
await game.move.forceEnemyMove(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER_2);
|
||||||
// Ensure that the moves are used deterministically in speed order (for speed ties)
|
// 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();
|
await game.toNextTurn();
|
||||||
|
|
||||||
expectFutureSightActive(4);
|
expectFutureSightActive(4);
|
||||||
@ -195,7 +197,11 @@ describe("Moves - Delayed Attacks", () => {
|
|||||||
|
|
||||||
const MEPs = game.scene.phaseManager["phaseQueue"].findAll("MoveEffectPhase");
|
const MEPs = game.scene.phaseManager["phaseQueue"].findAll("MoveEffectPhase");
|
||||||
expect(MEPs).toHaveLength(4);
|
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 () => {
|
it("should vanish silently if it would otherwise hit the user", async () => {
|
||||||
|
@ -17,8 +17,6 @@ describe("Moves - Destiny Bond", () => {
|
|||||||
let game: GameManager;
|
let game: GameManager;
|
||||||
|
|
||||||
const defaultParty = [SpeciesId.BULBASAUR, SpeciesId.SQUIRTLE];
|
const defaultParty = [SpeciesId.BULBASAUR, SpeciesId.SQUIRTLE];
|
||||||
const enemyFirst = [BattlerIndex.ENEMY, BattlerIndex.PLAYER];
|
|
||||||
const playerFirst = [BattlerIndex.PLAYER, BattlerIndex.ENEMY];
|
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
phaserGame = new Phaser.Game({
|
phaserGame = new Phaser.Game({
|
||||||
@ -52,7 +50,7 @@ describe("Moves - Destiny Bond", () => {
|
|||||||
const playerPokemon = game.field.getPlayerPokemon();
|
const playerPokemon = game.field.getPlayerPokemon();
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
await game.setTurnOrder(enemyFirst);
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
expect(enemyPokemon.isFainted()).toBe(true);
|
expect(enemyPokemon.isFainted()).toBe(true);
|
||||||
@ -70,7 +68,7 @@ describe("Moves - Destiny Bond", () => {
|
|||||||
|
|
||||||
// Turn 1: Enemy uses Destiny Bond and doesn't faint
|
// Turn 1: Enemy uses Destiny Bond and doesn't faint
|
||||||
game.move.select(MoveId.SPLASH);
|
game.move.select(MoveId.SPLASH);
|
||||||
await game.setTurnOrder(playerFirst);
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
expect(enemyPokemon.isFainted()).toBe(false);
|
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
|
// Turn 2: Player KO's the enemy before the enemy's turn
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
await game.setTurnOrder(playerFirst);
|
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
expect(enemyPokemon.isFainted()).toBe(true);
|
expect(enemyPokemon.isFainted()).toBe(true);
|
||||||
expect(playerPokemon.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
|
// Turn 1: Enemy uses Destiny Bond and doesn't faint
|
||||||
game.move.select(MoveId.SPLASH);
|
game.move.select(MoveId.SPLASH);
|
||||||
await game.setTurnOrder(enemyFirst);
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
expect(enemyPokemon.isFainted()).toBe(false);
|
expect(enemyPokemon.isFainted()).toBe(false);
|
||||||
@ -104,7 +102,6 @@ describe("Moves - Destiny Bond", () => {
|
|||||||
|
|
||||||
// Turn 2: Enemy should fail Destiny Bond then get KO'd
|
// Turn 2: Enemy should fail Destiny Bond then get KO'd
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
await game.setTurnOrder(enemyFirst);
|
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
expect(enemyPokemon.isFainted()).toBe(true);
|
expect(enemyPokemon.isFainted()).toBe(true);
|
||||||
@ -122,7 +119,7 @@ describe("Moves - Destiny Bond", () => {
|
|||||||
const playerPokemon = game.field.getPlayerPokemon();
|
const playerPokemon = game.field.getPlayerPokemon();
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
await game.setTurnOrder(enemyFirst);
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
expect(enemyPokemon.isFainted()).toBe(true);
|
expect(enemyPokemon.isFainted()).toBe(true);
|
||||||
@ -140,7 +137,7 @@ describe("Moves - Destiny Bond", () => {
|
|||||||
|
|
||||||
// Turn 1: Enemy uses Destiny Bond and doesn't faint
|
// Turn 1: Enemy uses Destiny Bond and doesn't faint
|
||||||
game.move.select(MoveId.SPORE);
|
game.move.select(MoveId.SPORE);
|
||||||
await game.setTurnOrder(enemyFirst);
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
expect(enemyPokemon.isFainted()).toBe(false);
|
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
|
// Turn 2: Enemy should skip a turn due to sleep, then get KO'd
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
await game.setTurnOrder(enemyFirst);
|
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
expect(enemyPokemon.isFainted()).toBe(true);
|
expect(enemyPokemon.isFainted()).toBe(true);
|
||||||
@ -184,7 +180,7 @@ describe("Moves - Destiny Bond", () => {
|
|||||||
const playerPokemon = game.field.getPlayerPokemon();
|
const playerPokemon = game.field.getPlayerPokemon();
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
await game.setTurnOrder(enemyFirst);
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
expect(enemyPokemon.isFainted()).toBe(true);
|
expect(enemyPokemon.isFainted()).toBe(true);
|
||||||
@ -238,7 +234,7 @@ describe("Moves - Destiny Bond", () => {
|
|||||||
const playerPokemon = game.field.getPlayerPokemon();
|
const playerPokemon = game.field.getPlayerPokemon();
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
await game.setTurnOrder(enemyFirst);
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
expect(enemyPokemon.isFainted()).toBe(true);
|
expect(enemyPokemon.isFainted()).toBe(true);
|
||||||
|
@ -78,8 +78,9 @@ describe("Moves - Encore", () => {
|
|||||||
|
|
||||||
game.move.select(MoveId.ENCORE);
|
game.move.select(MoveId.ENCORE);
|
||||||
|
|
||||||
const turnOrder = delay ? [BattlerIndex.PLAYER, BattlerIndex.ENEMY] : [BattlerIndex.ENEMY, BattlerIndex.PLAYER];
|
await game.setTurnOrder(
|
||||||
await game.setTurnOrder(turnOrder);
|
delay ? [BattlerIndex.PLAYER, BattlerIndex.ENEMY] : [BattlerIndex.ENEMY, BattlerIndex.PLAYER],
|
||||||
|
);
|
||||||
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
expect(playerPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.FAIL);
|
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 () => {
|
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]);
|
game.override.moveset([MoveId.ENCORE, MoveId.TORMENT, MoveId.SPLASH]);
|
||||||
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||||
|
|
||||||
const enemyPokemon = game.field.getEnemyPokemon();
|
const enemyPokemon = game.field.getEnemyPokemon();
|
||||||
game.move.select(MoveId.ENCORE);
|
game.move.select(MoveId.ENCORE);
|
||||||
await game.setTurnOrder(turnOrder);
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
expect(enemyPokemon.getTag(BattlerTagType.ENCORE)).toBeDefined();
|
expect(enemyPokemon.getTag(BattlerTagType.ENCORE)).toBeDefined();
|
||||||
|
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
game.move.select(MoveId.TORMENT);
|
game.move.select(MoveId.TORMENT);
|
||||||
await game.setTurnOrder(turnOrder);
|
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
expect(enemyPokemon.getTag(BattlerTagType.TORMENT)).toBeDefined();
|
expect(enemyPokemon.getTag(BattlerTagType.TORMENT)).toBeDefined();
|
||||||
|
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
game.move.select(MoveId.SPLASH);
|
game.move.select(MoveId.SPLASH);
|
||||||
await game.setTurnOrder(turnOrder);
|
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
const lastMove = enemyPokemon.getLastXMoves()[0];
|
const lastMove = enemyPokemon.getLastXMoves()[0];
|
||||||
expect(lastMove?.move).toBe(MoveId.STRUGGLE);
|
expect(lastMove?.move).toBe(MoveId.STRUGGLE);
|
||||||
|
@ -64,7 +64,6 @@ describe("Moves - Grudge", () => {
|
|||||||
|
|
||||||
game.move.use(MoveId.GUILLOTINE);
|
game.move.use(MoveId.GUILLOTINE);
|
||||||
await game.move.forceEnemyMove(MoveId.SPLASH);
|
await game.move.forceEnemyMove(MoveId.SPLASH);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
||||||
await game.toEndOfTurn();
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
expect(ratatta).toHaveFainted();
|
expect(ratatta).toHaveFainted();
|
||||||
|
@ -343,7 +343,6 @@ describe("Moves - Instruct", () => {
|
|||||||
expect(player.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
expect(player.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||||
|
|
||||||
game.move.select(MoveId.INSTRUCT);
|
game.move.select(MoveId.INSTRUCT);
|
||||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
|
||||||
await game.phaseInterceptor.to("TurnEndPhase", false);
|
await game.phaseInterceptor.to("TurnEndPhase", false);
|
||||||
|
|
||||||
expect(player.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
expect(player.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||||
|
@ -59,7 +59,6 @@ describe("Moves - Last Respects", () => {
|
|||||||
* Charmander faints once
|
* Charmander faints once
|
||||||
*/
|
*/
|
||||||
game.move.select(MoveId.EXPLOSION);
|
game.move.select(MoveId.EXPLOSION);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
||||||
game.doSelectPartyPokemon(2);
|
game.doSelectPartyPokemon(2);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
@ -86,7 +85,6 @@ describe("Moves - Last Respects", () => {
|
|||||||
*/
|
*/
|
||||||
game.doRevivePokemon(1);
|
game.doRevivePokemon(1);
|
||||||
game.move.select(MoveId.EXPLOSION);
|
game.move.select(MoveId.EXPLOSION);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
||||||
game.doSelectPartyPokemon(1);
|
game.doSelectPartyPokemon(1);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
@ -99,7 +97,6 @@ describe("Moves - Last Respects", () => {
|
|||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
game.move.select(MoveId.LAST_RESPECTS);
|
game.move.select(MoveId.LAST_RESPECTS);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
await game.phaseInterceptor.to(MoveEffectPhase);
|
||||||
|
|
||||||
expect(move.calculateBattlePower).toHaveReturnedWith(basePower + 3 * 50);
|
expect(move.calculateBattlePower).toHaveReturnedWith(basePower + 3 * 50);
|
||||||
@ -127,7 +124,6 @@ describe("Moves - Last Respects", () => {
|
|||||||
* Enemy Pokemon faints and new wave is entered.
|
* Enemy Pokemon faints and new wave is entered.
|
||||||
*/
|
*/
|
||||||
game.move.select(MoveId.LAST_RESPECTS);
|
game.move.select(MoveId.LAST_RESPECTS);
|
||||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
|
||||||
await game.toNextWave();
|
await game.toNextWave();
|
||||||
expect(game.scene.arena.playerFaints).toBe(1);
|
expect(game.scene.arena.playerFaints).toBe(1);
|
||||||
|
|
||||||
@ -160,7 +156,6 @@ describe("Moves - Last Respects", () => {
|
|||||||
* Enemy Pokemon faints and new wave is entered.
|
* Enemy Pokemon faints and new wave is entered.
|
||||||
*/
|
*/
|
||||||
game.move.select(MoveId.LAST_RESPECTS);
|
game.move.select(MoveId.LAST_RESPECTS);
|
||||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
|
||||||
await game.toNextWave();
|
await game.toNextWave();
|
||||||
expect(game.scene.currentBattle.enemyFaints).toBe(0);
|
expect(game.scene.currentBattle.enemyFaints).toBe(0);
|
||||||
|
|
||||||
@ -184,7 +179,6 @@ describe("Moves - Last Respects", () => {
|
|||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
game.move.select(MoveId.LAST_RESPECTS);
|
game.move.select(MoveId.LAST_RESPECTS);
|
||||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
|
||||||
await game.toNextWave();
|
await game.toNextWave();
|
||||||
|
|
||||||
game.move.select(MoveId.LAST_RESPECTS);
|
game.move.select(MoveId.LAST_RESPECTS);
|
||||||
@ -205,7 +199,6 @@ describe("Moves - Last Respects", () => {
|
|||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
game.move.select(MoveId.LAST_RESPECTS);
|
game.move.select(MoveId.LAST_RESPECTS);
|
||||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
|
||||||
await game.toNextWave();
|
await game.toNextWave();
|
||||||
|
|
||||||
game.move.select(MoveId.LAST_RESPECTS);
|
game.move.select(MoveId.LAST_RESPECTS);
|
||||||
|
@ -95,17 +95,16 @@ describe("Moves - Metronome", () => {
|
|||||||
|
|
||||||
game.move.select(MoveId.METRONOME);
|
game.move.select(MoveId.METRONOME);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
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;
|
const turn1PpUsed = metronomeMove.ppUsed;
|
||||||
expect.soft(turn1PpUsed).toBeGreaterThan(1);
|
expect.soft(turn1PpUsed).toBeGreaterThan(1);
|
||||||
expect(solarBeamMove.ppUsed).toBe(0);
|
expect(solarBeamMove.ppUsed).toBe(0);
|
||||||
|
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
expect(player.getTag(BattlerTagType.CHARGING)).toBeFalsy();
|
expect(player).not.toHaveBattlerTag(BattlerTagType.CHARGING);
|
||||||
const turn2PpUsed = metronomeMove.ppUsed - turn1PpUsed;
|
const turn2PpUsed = metronomeMove.ppUsed - turn1PpUsed;
|
||||||
expect(turn2PpUsed).toBeGreaterThan(1);
|
expect(turn2PpUsed).toBeGreaterThan(1);
|
||||||
expect(solarBeamMove.ppUsed).toBe(0);
|
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.FIERY_DANCE, 0, BattlerIndex.ENEMY_2);
|
||||||
game.move.select(MoveId.SPLASH, 1);
|
game.move.select(MoveId.SPLASH, 1);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
|
|
||||||
await game.phaseInterceptor.to("MoveEndPhase");
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
|
||||||
// Rainbow effect should increase Fiery Dance's chance of raising Sp. Atk to 100%
|
// 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
|
// remove substitute and get confused
|
||||||
game.move.select(MoveId.TIDY_UP);
|
game.move.select(MoveId.TIDY_UP);
|
||||||
await game.move.selectEnemyMove(MoveId.CONFUSE_RAY);
|
await game.move.selectEnemyMove(MoveId.CONFUSE_RAY);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
game.move.select(MoveId.RAGE_FIST);
|
game.move.select(MoveId.RAGE_FIST);
|
||||||
@ -108,7 +107,6 @@ describe("Moves - Rage Fist", () => {
|
|||||||
expect(game.field.getPlayerPokemon().battleData.hitCount).toBe(2);
|
expect(game.field.getPlayerPokemon().battleData.hitCount).toBe(2);
|
||||||
|
|
||||||
game.move.select(MoveId.RAGE_FIST);
|
game.move.select(MoveId.RAGE_FIST);
|
||||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
|
||||||
await game.phaseInterceptor.to("TurnEndPhase");
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
expect(game.field.getPlayerPokemon().battleData.hitCount).toBe(4);
|
expect(game.field.getPlayerPokemon().battleData.hitCount).toBe(4);
|
||||||
@ -147,7 +145,6 @@ describe("Moves - Rage Fist", () => {
|
|||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
game.move.select(MoveId.RAGE_FIST);
|
game.move.select(MoveId.RAGE_FIST);
|
||||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
expect(move.calculateBattlePower).toHaveLastReturnedWith(150);
|
expect(move.calculateBattlePower).toHaveLastReturnedWith(150);
|
||||||
|
@ -159,7 +159,6 @@ describe("Moves - Roost", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
game.move.select(MoveId.ROOST);
|
game.move.select(MoveId.ROOST);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
await game.phaseInterceptor.to(MoveEffectPhase);
|
||||||
|
|
||||||
// Should only be typeless type after roost and is grounded
|
// Should only be typeless type after roost and is grounded
|
||||||
@ -195,7 +194,6 @@ describe("Moves - Roost", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
game.move.select(MoveId.ROOST);
|
game.move.select(MoveId.ROOST);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
await game.phaseInterceptor.to(MoveEffectPhase);
|
||||||
|
|
||||||
// Should only be typeless type after roost and is grounded
|
// Should only be typeless type after roost and is grounded
|
||||||
|
@ -71,7 +71,6 @@ describe("Moves - Sketch", () => {
|
|||||||
|
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
game.move.select(MoveId.SKETCH);
|
game.move.select(MoveId.SKETCH);
|
||||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
|
||||||
await game.move.forceStatusActivation(true);
|
await game.move.forceStatusActivation(true);
|
||||||
await game.phaseInterceptor.to("TurnEndPhase");
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
||||||
|
@ -49,7 +49,6 @@ describe("Moves - Spite", () => {
|
|||||||
|
|
||||||
game.move.use(MoveId.SPITE);
|
game.move.use(MoveId.SPITE);
|
||||||
await game.move.selectEnemyMove(MoveId.SPLASH);
|
await game.move.selectEnemyMove(MoveId.SPLASH);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
||||||
await game.toEndOfTurn();
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
expect(karp).toHaveUsedPP(MoveId.TACKLE, 4 + 1);
|
expect(karp).toHaveUsedPP(MoveId.TACKLE, 4 + 1);
|
||||||
|
@ -109,14 +109,14 @@ describe("Move - Wish", () => {
|
|||||||
vi.spyOn(karp1, "getNameToRender").mockReturnValue("Karp 1");
|
vi.spyOn(karp1, "getNameToRender").mockReturnValue("Karp 1");
|
||||||
vi.spyOn(karp2, "getNameToRender").mockReturnValue("Karp 2");
|
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);
|
||||||
game.move.use(MoveId.WISH, BattlerIndex.PLAYER_2);
|
game.move.use(MoveId.WISH, BattlerIndex.PLAYER_2);
|
||||||
await game.move.forceEnemyMove(MoveId.WISH);
|
await game.move.forceEnemyMove(MoveId.WISH);
|
||||||
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)
|
// 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();
|
await game.toNextTurn();
|
||||||
|
|
||||||
expect(game).toHavePositionalTag(PositionalTagType.WISH, 4);
|
expect(game).toHavePositionalTag(PositionalTagType.WISH, 4);
|
||||||
@ -137,7 +137,9 @@ describe("Move - Wish", () => {
|
|||||||
|
|
||||||
const healPhases = game.scene.phaseManager["phaseQueue"].findAll("PokemonHealPhase");
|
const healPhases = game.scene.phaseManager["phaseQueue"].findAll("PokemonHealPhase");
|
||||||
expect(healPhases).toHaveLength(4);
|
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();
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
|
@ -62,7 +62,6 @@ describe("Frenzy Move Reset", () => {
|
|||||||
expect(playerPokemon.summonData.moveQueue.length).toBe(2);
|
expect(playerPokemon.summonData.moveQueue.length).toBe(2);
|
||||||
expect(playerPokemon.summonData.tags.some(tag => tag.tagType === BattlerTagType.FRENZY)).toBe(true);
|
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.move.forceStatusActivation(true);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import { PlayerGender } from "#enums/player-gender";
|
|||||||
import type { PokeballType } from "#enums/pokeball";
|
import type { PokeballType } from "#enums/pokeball";
|
||||||
import type { SpeciesId } from "#enums/species-id";
|
import type { SpeciesId } from "#enums/species-id";
|
||||||
import { UiMode } from "#enums/ui-mode";
|
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 { Trainer } from "#field/trainer";
|
||||||
import { ModifierTypeOption } from "#modifiers/modifier-type";
|
import { ModifierTypeOption } from "#modifiers/modifier-type";
|
||||||
import { CheckSwitchPhase } from "#phases/check-switch-phase";
|
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 { PartyUiHandler } from "#ui/party-ui-handler";
|
||||||
import type { StarterSelectUiHandler } from "#ui/starter-select-ui-handler";
|
import type { StarterSelectUiHandler } from "#ui/starter-select-ui-handler";
|
||||||
import type { TargetSelectUiHandler } from "#ui/target-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 fs from "node:fs";
|
||||||
import { AES, enc } from "crypto-js";
|
import { AES, enc } from "crypto-js";
|
||||||
import { expect, vi } from "vitest";
|
import { expect, vi } from "vitest";
|
||||||
@ -536,19 +537,40 @@ export class GameManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modifies the queue manager to return move phases in a particular order
|
* Override the turn order of the battle's current combatants.
|
||||||
* Used to manually modify Pokemon turn order.
|
* @param order - The turn order to set, as an array of {@linkcode BattlerIndex}es
|
||||||
* Note: This *DOES NOT* account for priority.
|
|
||||||
* @param order - The turn order to set as an array of {@linkcode BattlerIndex}es.
|
|
||||||
* @example
|
* @example
|
||||||
* ```ts
|
* ```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> {
|
public setTurnOrder(order: Exclude<BattlerIndex, BattlerIndex.ATTACKER>[]): void {
|
||||||
await this.phaseInterceptor.to("TurnStartPhase", false);
|
// 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).
|
* 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
|
* @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`
|
* (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.
|
* Speed ties are returned in increasing order of index.
|
||||||
*
|
*
|
||||||
* @remarks
|
* @remarks
|
||||||
* This does not account for Trick Room as it does not modify the _speed_ of Pokemon on the field,
|
* This does not account for Trick Room as it does not modify the _speed_ of Pokemon on the field,
|
||||||
* only their turn order.
|
* only their turn order.
|
||||||
*/
|
*/
|
||||||
public getSpeedOrder(indices: true): BattlerIndex[];
|
public getSpeedOrder(indices: true): Exclude<BattlerIndex, BattlerIndex.ATTACKER>[];
|
||||||
public getSpeedOrder(indices = false): BattlerIndex[] | Pokemon[] {
|
public getSpeedOrder(indices = false): BattlerIndex[] | Pokemon[] {
|
||||||
const ret = this.game.scene
|
const ret = this.game.scene
|
||||||
.getField(true)
|
.getField(true)
|
||||||
|
Loading…
Reference in New Issue
Block a user