mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-05 07:52:17 +02:00
Updated tests to use new functions introduced. Less intuitive, but faster.
This commit is contained in:
parent
cba864ebb4
commit
ce4ec533ac
5833
src/phases.ts
5833
src/phases.ts
File diff suppressed because it is too large
Load Diff
@ -1,42 +1,9 @@
|
|||||||
import { BattlerIndex } from "#app/battle.js";
|
import { BattlerIndex } from "#app/battle.js";
|
||||||
import { TrickRoomTag } from "#app/data/arena-tag.js";
|
|
||||||
import { Stat } from "#app/enums/stat.js";
|
|
||||||
import Pokemon from "#app/field/pokemon.js";
|
|
||||||
import { BattlePhase } from "./battle-phase";
|
import { BattlePhase } from "./battle-phase";
|
||||||
import * as Utils from "#app/utils.js";
|
|
||||||
|
|
||||||
type PokemonFunc = (pokemon: Pokemon) => void;
|
type PokemonFunc = (pokemon: Pokemon) => void;
|
||||||
|
|
||||||
export abstract class FieldPhase extends BattlePhase {
|
export abstract class FieldPhase extends BattlePhase {
|
||||||
getOrder(): BattlerIndex[] {
|
|
||||||
const playerField = this.scene.getPlayerField().filter(p => p.isActive()) as Pokemon[];
|
|
||||||
const enemyField = this.scene.getEnemyField().filter(p => p.isActive()) as Pokemon[];
|
|
||||||
|
|
||||||
// We shuffle the list before sorting so speed ties produce random results
|
|
||||||
let orderedTargets: Pokemon[] = playerField.concat(enemyField);
|
|
||||||
// We seed it with the current turn to prevent an inconsistency where it
|
|
||||||
// was varying based on how long since you last reloaded
|
|
||||||
this.scene.executeWithSeedOffset(() => {
|
|
||||||
orderedTargets = Utils.randSeedShuffle(orderedTargets);
|
|
||||||
}, this.scene.currentBattle.turn, this.scene.waveSeed);
|
|
||||||
|
|
||||||
orderedTargets.sort((a: Pokemon, b: Pokemon) => {
|
|
||||||
const aSpeed = a?.getBattleStat(Stat.SPD) || 0;
|
|
||||||
const bSpeed = b?.getBattleStat(Stat.SPD) || 0;
|
|
||||||
|
|
||||||
return bSpeed - aSpeed;
|
|
||||||
});
|
|
||||||
|
|
||||||
const speedReversed = new Utils.BooleanHolder(false);
|
|
||||||
this.scene.arena.applyTags(TrickRoomTag, speedReversed);
|
|
||||||
|
|
||||||
if (speedReversed.value) {
|
|
||||||
orderedTargets = orderedTargets.reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
return orderedTargets.map(t => t.getFieldIndex() + (!t.isPlayer() ? BattlerIndex.ENEMY : 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
executeForAll(func: PokemonFunc): void {
|
executeForAll(func: PokemonFunc): void {
|
||||||
const field = this.scene.getField(true).filter(p => p.summonData);
|
const field = this.scene.getField(true).filter(p => p.summonData);
|
||||||
field.forEach(pokemon => func(pokemon));
|
field.forEach(pokemon => func(pokemon));
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import BattleScene from "#app/battle-scene.js";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { applyAbAttrs, BypassSpeedChanceAbAttr, PreventBypassSpeedChanceAbAttr, ChangeMovePriorityAbAttr } from "#app/data/ability.js";
|
import { applyAbAttrs, BypassSpeedChanceAbAttr, PreventBypassSpeedChanceAbAttr, ChangeMovePriorityAbAttr } from "#app/data/ability";
|
||||||
import { allMoves, applyMoveAttrs, IncrementMovePriorityAttr, MoveHeaderAttr } from "#app/data/move.js";
|
import { allMoves, applyMoveAttrs, IncrementMovePriorityAttr, MoveHeaderAttr } from "#app/data/move";
|
||||||
import { Abilities } from "#app/enums/abilities.js";
|
import { Abilities } from "#app/enums/abilities";
|
||||||
import { Stat } from "#app/enums/stat.js";
|
import { Stat } from "#app/enums/stat";
|
||||||
import { PokemonMove } from "#app/field/pokemon.js";
|
import Pokemon, { PokemonMove } from "#app/field/pokemon";
|
||||||
import { BypassSpeedChanceModifier } from "#app/modifier/modifier.js";
|
import { BypassSpeedChanceModifier } from "#app/modifier/modifier";
|
||||||
import { Command } from "#app/ui/command-ui-handler.js";
|
import { Command } from "#app/ui/command-ui-handler";
|
||||||
import * as Utils from "#app/utils.js";
|
import * as Utils from "#app/utils";
|
||||||
import { AttemptCapturePhase } from "./attempt-capture-phase";
|
import { AttemptCapturePhase } from "./attempt-capture-phase";
|
||||||
import { AttemptRunPhase } from "./attempt-run-phase";
|
import { AttemptRunPhase } from "./attempt-run-phase";
|
||||||
import { BerryPhase } from "./berry-phase";
|
import { BerryPhase } from "./berry-phase";
|
||||||
@ -17,18 +17,49 @@ import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase";
|
|||||||
import { SwitchSummonPhase } from "./switch-summon-phase";
|
import { SwitchSummonPhase } from "./switch-summon-phase";
|
||||||
import { TurnEndPhase } from "./turn-end-phase";
|
import { TurnEndPhase } from "./turn-end-phase";
|
||||||
import { WeatherEffectPhase } from "./weather-effect-phase";
|
import { WeatherEffectPhase } from "./weather-effect-phase";
|
||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
import { TrickRoomTag } from "#app/data/arena-tag";
|
||||||
|
|
||||||
export class TurnStartPhase extends FieldPhase {
|
export class TurnStartPhase extends FieldPhase {
|
||||||
constructor(scene: BattleScene) {
|
constructor(scene: BattleScene) {
|
||||||
super(scene);
|
super(scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
getSpeedOrder(): BattlerIndex[] {
|
||||||
super.start();
|
const playerField = this.scene.getPlayerField().filter(p => p.isActive()) as Pokemon[];
|
||||||
|
const enemyField = this.scene.getEnemyField().filter(p => p.isActive()) as Pokemon[];
|
||||||
|
|
||||||
const field = this.scene.getField();
|
// We shuffle the list before sorting so speed ties produce random results
|
||||||
const order = this.getOrder();
|
let orderedTargets: Pokemon[] = playerField.concat(enemyField);
|
||||||
|
// We seed it with the current turn to prevent an inconsistency where it
|
||||||
|
// was varying based on how long since you last reloaded
|
||||||
|
this.scene.executeWithSeedOffset(() => {
|
||||||
|
orderedTargets = Utils.randSeedShuffle(orderedTargets);
|
||||||
|
}, this.scene.currentBattle.turn, this.scene.waveSeed);
|
||||||
|
|
||||||
|
orderedTargets.sort((a: Pokemon, b: Pokemon) => {
|
||||||
|
const aSpeed = a?.getBattleStat(Stat.SPD) || 0;
|
||||||
|
const bSpeed = b?.getBattleStat(Stat.SPD) || 0;
|
||||||
|
|
||||||
|
return bSpeed - aSpeed;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Next, a check for Trick Room is applied. If Trick Room is present, the order is reversed.
|
||||||
|
const speedReversed = new Utils.BooleanHolder(false);
|
||||||
|
this.scene.arena.applyTags(TrickRoomTag, speedReversed);
|
||||||
|
|
||||||
|
if (speedReversed.value) {
|
||||||
|
orderedTargets = orderedTargets.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
return orderedTargets.map(t => t.getFieldIndex() + (!t.isPlayer() ? BattlerIndex.ENEMY : BattlerIndex.PLAYER));
|
||||||
|
}
|
||||||
|
|
||||||
|
getCommandOrder(): BattlerIndex[] {
|
||||||
|
let moveOrder = this.getSpeedOrder();
|
||||||
|
// The creation of the battlerBypassSpeed object contains checks for the ability Quick Draw and the held item Quick Claw
|
||||||
|
// The ability Mycelium Might disables Quick Claw's activation when using a status move
|
||||||
|
// This occurs before the main loop because of battles with more than two Pokemon
|
||||||
const battlerBypassSpeed = {};
|
const battlerBypassSpeed = {};
|
||||||
|
|
||||||
this.scene.getField(true).filter(p => p.summonData).map(p => {
|
this.scene.getField(true).filter(p => p.summonData).map(p => {
|
||||||
@ -42,8 +73,9 @@ export class TurnStartPhase extends FieldPhase {
|
|||||||
battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed;
|
battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed;
|
||||||
});
|
});
|
||||||
|
|
||||||
const moveOrder = order.slice(0);
|
// The function begins sorting orderedTargets based on command priority, move priority, and possible speed bypasses.
|
||||||
|
// Non-FIGHT commands (SWITCH, BALL, RUN) have a higher command priority and will always occur before any FIGHT commands.
|
||||||
|
moveOrder = moveOrder.slice(0);
|
||||||
moveOrder.sort((a, b) => {
|
moveOrder.sort((a, b) => {
|
||||||
const aCommand = this.scene.currentBattle.turnCommands[a];
|
const aCommand = this.scene.currentBattle.turnCommands[a];
|
||||||
const bCommand = this.scene.currentBattle.turnCommands[b];
|
const bCommand = this.scene.currentBattle.turnCommands[b];
|
||||||
@ -58,6 +90,7 @@ export class TurnStartPhase extends FieldPhase {
|
|||||||
const aMove = allMoves[aCommand.move!.move];//TODO: is the bang correct here?
|
const aMove = allMoves[aCommand.move!.move];//TODO: is the bang correct here?
|
||||||
const bMove = allMoves[bCommand!.move!.move];//TODO: is the bang correct here?
|
const bMove = allMoves[bCommand!.move!.move];//TODO: is the bang correct here?
|
||||||
|
|
||||||
|
// The game now considers priority and applies the relevant move and ability attributes
|
||||||
const aPriority = new Utils.IntegerHolder(aMove.priority);
|
const aPriority = new Utils.IntegerHolder(aMove.priority);
|
||||||
const bPriority = new Utils.IntegerHolder(bMove.priority);
|
const bPriority = new Utils.IntegerHolder(bMove.priority);
|
||||||
|
|
||||||
@ -67,25 +100,37 @@ export class TurnStartPhase extends FieldPhase {
|
|||||||
applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority); //TODO: is the bang correct here?
|
applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority); //TODO: is the bang correct here?
|
||||||
applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority); //TODO: is the bang correct here?
|
applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority); //TODO: is the bang correct here?
|
||||||
|
|
||||||
|
// The game now checks for differences in priority levels.
|
||||||
|
// If the moves share the same original priority bracket, it can check for differences in battlerBypassSpeed and return the result.
|
||||||
|
// This conditional is used to ensure that Quick Claw can still activate with abilities like Stall and Mycelium Might (attack moves only)
|
||||||
|
// Otherwise, the game returns the user of the move with the highest priority.
|
||||||
|
const isSameBracket = Math.ceil(aPriority.value) - Math.ceil(bPriority.value) === 0;
|
||||||
if (aPriority.value !== bPriority.value) {
|
if (aPriority.value !== bPriority.value) {
|
||||||
const bracketDifference = Math.ceil(aPriority.value) - Math.ceil(bPriority.value);
|
if (isSameBracket && battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value) {
|
||||||
const hasSpeedDifference = battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value;
|
|
||||||
if (bracketDifference === 0 && hasSpeedDifference) {
|
|
||||||
return battlerBypassSpeed[a].value ? -1 : 1;
|
return battlerBypassSpeed[a].value ? -1 : 1;
|
||||||
}
|
}
|
||||||
return aPriority.value < bPriority.value ? 1 : -1;
|
return aPriority.value < bPriority.value ? 1 : -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there is no difference between the move's calculated priorities, the game checks for differences in battlerBypassSpeed and returns the result.
|
||||||
if (battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value) {
|
if (battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value) {
|
||||||
return battlerBypassSpeed[a].value ? -1 : 1;
|
return battlerBypassSpeed[a].value ? -1 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const aIndex = order.indexOf(a);
|
const aIndex = moveOrder.indexOf(a);
|
||||||
const bIndex = order.indexOf(b);
|
const bIndex = moveOrder.indexOf(b);
|
||||||
|
|
||||||
return aIndex < bIndex ? -1 : aIndex > bIndex ? 1 : 0;
|
return aIndex < bIndex ? -1 : aIndex > bIndex ? 1 : 0;
|
||||||
});
|
});
|
||||||
|
return moveOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
super.start();
|
||||||
|
|
||||||
|
const field = this.scene.getField();
|
||||||
|
const moveOrder = this.getCommandOrder();
|
||||||
|
|
||||||
let orderIndex = 0;
|
let orderIndex = 0;
|
||||||
|
|
||||||
@ -150,10 +195,9 @@ export class TurnStartPhase extends FieldPhase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.scene.pushPhase(new WeatherEffectPhase(this.scene));
|
this.scene.pushPhase(new WeatherEffectPhase(this.scene));
|
||||||
|
|
||||||
for (const o of order) {
|
for (const o of moveOrder) {
|
||||||
if (field[o].status && field[o].status.isPostTurn()) {
|
if (field[o].status && field[o].status.isPostTurn()) {
|
||||||
this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, o));
|
this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, o));
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { MovePhase } from "#app/phases/move-phase.js";
|
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
||||||
import { TurnEndPhase } from "#app/phases/turn-end-phase.js";
|
import { TurnStartPhase } from "#app/phases/turn-start-phase";
|
||||||
|
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
|
||||||
import GameManager from "#test/utils/gameManager";
|
import GameManager from "#test/utils/gameManager";
|
||||||
import { getMovePosition } from "#test/utils/gameManagerUtils";
|
import { getMovePosition } from "#test/utils/gameManagerUtils";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
@ -9,7 +10,6 @@ import { Species } from "#enums/species";
|
|||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
|
||||||
describe("Abilities - Mycelium Might", () => {
|
describe("Abilities - Mycelium Might", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
let game: GameManager;
|
let game: GameManager;
|
||||||
@ -36,7 +36,7 @@ describe("Abilities - Mycelium Might", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bulbapedia References:
|
* References:
|
||||||
* https://bulbapedia.bulbagarden.net/wiki/Mycelium_Might_(Ability)
|
* https://bulbapedia.bulbagarden.net/wiki/Mycelium_Might_(Ability)
|
||||||
* https://bulbapedia.bulbagarden.net/wiki/Priority
|
* https://bulbapedia.bulbagarden.net/wiki/Priority
|
||||||
* https://www.smogon.com/forums/threads/scarlet-violet-battle-mechanics-research.3709545/page-24
|
* https://www.smogon.com/forums/threads/scarlet-violet-battle-mechanics-research.3709545/page-24
|
||||||
@ -44,63 +44,55 @@ describe("Abilities - Mycelium Might", () => {
|
|||||||
|
|
||||||
it("If a Pokemon with Mycelium Might uses a status move, it will always move last but the status move will ignore protective abilities", async() => {
|
it("If a Pokemon with Mycelium Might uses a status move, it will always move last but the status move will ignore protective abilities", async() => {
|
||||||
await game.startBattle([ Species.REGIELEKI ]);
|
await game.startBattle([ Species.REGIELEKI ]);
|
||||||
|
|
||||||
const leadIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||||
const enemyIndex = enemyPokemon?.getBattlerIndex();
|
|
||||||
|
|
||||||
game.doAttack(getMovePosition(game.scene, 0, Moves.BABY_DOLL_EYES));
|
game.doAttack(getMovePosition(game.scene, 0, Moves.BABY_DOLL_EYES));
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MovePhase, false);
|
await game.phaseInterceptor.run(EnemyCommandPhase);
|
||||||
|
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
|
||||||
|
const speedOrder = phase.getSpeedOrder();
|
||||||
|
const commandOrder = phase.getCommandOrder();
|
||||||
// The opponent Pokemon (without Mycelium Might) goes first despite having lower speed than the player Pokemon.
|
// The opponent Pokemon (without Mycelium Might) goes first despite having lower speed than the player Pokemon.
|
||||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(enemyIndex);
|
|
||||||
|
|
||||||
await game.phaseInterceptor.run(MovePhase);
|
|
||||||
await game.phaseInterceptor.to(MovePhase, false);
|
|
||||||
|
|
||||||
// The player Pokemon (with Mycelium Might) goes last despite having higher speed than the opponent.
|
// The player Pokemon (with Mycelium Might) goes last despite having higher speed than the opponent.
|
||||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(leadIndex);
|
// This means that the commandOrder is equivalent to the speed Order reversed
|
||||||
|
expect(speedOrder.reverse().every((val, index) => val === commandOrder[index])).toBe(true);
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
// Despite the opponent's ability (Clear Body), its attack stat is still reduced.
|
||||||
expect(enemyPokemon?.summonData.battleStats[BattleStat.ATK]).toBe(-1);
|
expect(enemyPokemon?.summonData.battleStats[BattleStat.ATK]).toBe(-1);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
it("Pokemon with Mycelium Might will go first if a status move that is in a higher priority bracket than the opponent's move is used", async() => {
|
it("Pokemon with Mycelium Might will go first if a status move that is in a higher priority bracket than the opponent's move is used", async() => {
|
||||||
game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
|
game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
|
||||||
await game.startBattle([ Species.REGIELEKI ]);
|
await game.startBattle([ Species.REGIELEKI ]);
|
||||||
|
|
||||||
const leadIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||||
const enemyIndex = enemyPokemon?.getBattlerIndex();
|
|
||||||
|
|
||||||
game.doAttack(getMovePosition(game.scene, 0, Moves.BABY_DOLL_EYES));
|
game.doAttack(getMovePosition(game.scene, 0, Moves.BABY_DOLL_EYES));
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MovePhase, false);
|
await game.phaseInterceptor.run(EnemyCommandPhase);
|
||||||
|
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
|
||||||
|
const speedOrder = phase.getSpeedOrder();
|
||||||
|
const commandOrder = phase.getCommandOrder();
|
||||||
// The player Pokemon (with M.M.) goes first because its move is still within a higher priority bracket than its opponent.
|
// The player Pokemon (with M.M.) goes first because its move is still within a higher priority bracket than its opponent.
|
||||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(leadIndex);
|
|
||||||
|
|
||||||
await game.phaseInterceptor.run(MovePhase);
|
|
||||||
await game.phaseInterceptor.to(MovePhase, false);
|
|
||||||
// The enemy Pokemon goes second because its move is in a lower priority bracket.
|
// The enemy Pokemon goes second because its move is in a lower priority bracket.
|
||||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(enemyIndex);
|
// This means that the commandOrder should be identical to the speedOrder
|
||||||
|
expect(speedOrder.every((val, index) => val === commandOrder[index])).toBe(true);
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
// Despite the opponent's ability (Clear Body), its attack stat is still reduced.
|
||||||
expect(enemyPokemon?.summonData.battleStats[BattleStat.ATK]).toBe(-1);
|
expect(enemyPokemon?.summonData.battleStats[BattleStat.ATK]).toBe(-1);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
it("Order is established normally if the Pokemon uses a non-status move", async() => {
|
it("Order is established normally if the Pokemon uses a non-status move", async() => {
|
||||||
await game.startBattle([ Species.REGIELEKI ]);
|
await game.startBattle([ Species.REGIELEKI ]);
|
||||||
|
|
||||||
const leadIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
|
|
||||||
const enemyIndex = game.scene.getEnemyPokemon()!.getBattlerIndex();
|
|
||||||
|
|
||||||
game.doAttack(getMovePosition(game.scene, 0, Moves.QUICK_ATTACK));
|
game.doAttack(getMovePosition(game.scene, 0, Moves.QUICK_ATTACK));
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MovePhase, false);
|
await game.phaseInterceptor.run(EnemyCommandPhase);
|
||||||
|
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
|
||||||
|
const speedOrder = phase.getSpeedOrder();
|
||||||
|
const commandOrder = phase.getCommandOrder();
|
||||||
// The player Pokemon (with M.M.) goes first because it has a higher speed and did not use a status move.
|
// The player Pokemon (with M.M.) goes first because it has a higher speed and did not use a status move.
|
||||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(leadIndex);
|
|
||||||
|
|
||||||
await game.phaseInterceptor.run(MovePhase);
|
|
||||||
await game.phaseInterceptor.to(MovePhase, false);
|
|
||||||
// The enemy Pokemon (without M.M.) goes second because its speed is lower.
|
// The enemy Pokemon (without M.M.) goes second because its speed is lower.
|
||||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(enemyIndex);
|
// This means that the commandOrder should be identical to the speedOrder
|
||||||
|
expect(speedOrder.every((val, index) => val === commandOrder[index])).toBe(true);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
});
|
});
|
||||||
|
@ -5,8 +5,8 @@ import { Moves } from "#enums/moves";
|
|||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
import { MovePhase } from "#app/phases/move-phase.js";
|
import { TurnStartPhase } from "#app/phases/turn-start-phase";
|
||||||
|
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
|
||||||
|
|
||||||
describe("Abilities - Stall", () => {
|
describe("Abilities - Stall", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -33,7 +33,7 @@ describe("Abilities - Stall", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bulbapedia References:
|
* References:
|
||||||
* https://bulbapedia.bulbagarden.net/wiki/Stall_(Ability)
|
* https://bulbapedia.bulbagarden.net/wiki/Stall_(Ability)
|
||||||
* https://bulbapedia.bulbagarden.net/wiki/Priority
|
* https://bulbapedia.bulbagarden.net/wiki/Priority
|
||||||
**/
|
**/
|
||||||
@ -41,55 +41,47 @@ describe("Abilities - Stall", () => {
|
|||||||
it("Pokemon with Stall should move last in its priority bracket regardless of speed", async() => {
|
it("Pokemon with Stall should move last in its priority bracket regardless of speed", async() => {
|
||||||
await game.startBattle([ Species.SHUCKLE ]);
|
await game.startBattle([ Species.SHUCKLE ]);
|
||||||
|
|
||||||
const leadIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
|
|
||||||
const enemyIndex = game.scene.getEnemyPokemon()!.getBattlerIndex();
|
|
||||||
|
|
||||||
game.doAttack(getMovePosition(game.scene, 0, Moves.QUICK_ATTACK));
|
game.doAttack(getMovePosition(game.scene, 0, Moves.QUICK_ATTACK));
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MovePhase, false);
|
await game.phaseInterceptor.run(EnemyCommandPhase);
|
||||||
|
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
|
||||||
|
const speedOrder = phase.getSpeedOrder();
|
||||||
|
const commandOrder = phase.getCommandOrder();
|
||||||
// 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.
|
||||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(leadIndex);
|
|
||||||
|
|
||||||
await game.phaseInterceptor.run(MovePhase);
|
|
||||||
await game.phaseInterceptor.to(MovePhase, false);
|
|
||||||
// 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.
|
||||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(enemyIndex);
|
// This means that the commandOrder is equivalent to the speed Order reversed
|
||||||
|
expect(speedOrder.reverse().every((val, index) => val === commandOrder[index])).toBe(true);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
it("Pokemon with Stall will go first if a move that is in a higher priority bracket than the opponent's move is used", async() => {
|
it("Pokemon with Stall will go first if a move that is in a higher priority bracket than the opponent's move is used", async() => {
|
||||||
await game.startBattle([ Species.SHUCKLE ]);
|
await game.startBattle([ Species.SHUCKLE ]);
|
||||||
|
|
||||||
const leadIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
|
|
||||||
const enemyIndex = game.scene.getEnemyPokemon()!.getBattlerIndex();
|
|
||||||
|
|
||||||
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
|
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MovePhase, false);
|
await game.phaseInterceptor.run(EnemyCommandPhase);
|
||||||
|
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
|
||||||
|
const speedOrder = phase.getSpeedOrder();
|
||||||
|
const commandOrder = phase.getCommandOrder();
|
||||||
// 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.
|
||||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(enemyIndex);
|
|
||||||
|
|
||||||
await game.phaseInterceptor.run(MovePhase);
|
|
||||||
await game.phaseInterceptor.to(MovePhase, false);
|
|
||||||
// The player Pokemon goes second because its move is in a lower priority bracket.
|
// The player Pokemon goes second because its move is in a lower priority bracket.
|
||||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(leadIndex);
|
// This means that the commandOrder should be identical to the speedOrder
|
||||||
|
expect(speedOrder.every((val, index) => val === commandOrder[index])).toBe(true);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
it("If both Pokemon have stall and use the same move, speed is used to determine who goes first.", async() => {
|
it("If both Pokemon have stall and use the same move, speed is used to determine who goes first.", async() => {
|
||||||
game.override.ability(Abilities.STALL);
|
game.override.ability(Abilities.STALL);
|
||||||
await game.startBattle([ Species.SHUCKLE ]);
|
await game.startBattle([ Species.SHUCKLE ]);
|
||||||
|
|
||||||
const leadIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
|
|
||||||
const enemyIndex = game.scene.getEnemyPokemon()!.getBattlerIndex();
|
|
||||||
|
|
||||||
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
|
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MovePhase, false);
|
await game.phaseInterceptor.run(EnemyCommandPhase);
|
||||||
// The opponent Pokemon (with Stall) goes first because it has a higher speed.
|
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
|
||||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(enemyIndex);
|
const speedOrder = phase.getSpeedOrder();
|
||||||
|
const commandOrder = phase.getCommandOrder();
|
||||||
|
|
||||||
await game.phaseInterceptor.run(MovePhase);
|
// The opponent Pokemon (with Stall) goes first because it has a higher speed.
|
||||||
await game.phaseInterceptor.to(MovePhase, false);
|
|
||||||
// The player Pokemon (with Stall) goes second because its speed is lower.
|
// The player Pokemon (with Stall) goes second because its speed is lower.
|
||||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(leadIndex);
|
// This means that the commandOrder should be identical to the speedOrder
|
||||||
|
expect(speedOrder.every((val, index) => val === commandOrder[index])).toBe(true);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
});
|
});
|
||||||
|
@ -11,9 +11,9 @@ import { Species } from "#enums/species";
|
|||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
import { CommandPhase } from "#app/phases/command-phase.js";
|
import { CommandPhase } from "#app/phases/command-phase.js";
|
||||||
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js";
|
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
|
||||||
import { SelectTargetPhase } from "#app/phases/select-target-phase.js";
|
import { SelectTargetPhase } from "#app/phases/select-target-phase";
|
||||||
import { TurnStartPhase } from "#app/phases/turn-start-phase.js";
|
import { TurnStartPhase } from "#app/phases/turn-start-phase";
|
||||||
|
|
||||||
|
|
||||||
describe("Battle order", () => {
|
describe("Battle order", () => {
|
||||||
|
@ -20,28 +20,28 @@ import { GameDataType } from "#enums/game-data-type";
|
|||||||
import { PlayerGender } from "#enums/player-gender";
|
import { PlayerGender } from "#enums/player-gender";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import { Button } from "#enums/buttons";
|
import { Button } from "#enums/buttons";
|
||||||
import { BattlerIndex } from "#app/battle.js";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import TargetSelectUiHandler from "#app/ui/target-select-ui-handler.js";
|
import TargetSelectUiHandler from "#app/ui/target-select-ui-handler";
|
||||||
import { OverridesHelper } from "./helpers/overridesHelper";
|
import { OverridesHelper } from "./helpers/overridesHelper";
|
||||||
import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type.js";
|
import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type";
|
||||||
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler.js";
|
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
||||||
import { MoveHelper } from "./helpers/moveHelper";
|
import { MoveHelper } from "./helpers/moveHelper";
|
||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
import { ClassicModeHelper } from "./helpers/classicModeHelper";
|
import { ClassicModeHelper } from "./helpers/classicModeHelper";
|
||||||
import { DailyModeHelper } from "./helpers/dailyModeHelper";
|
import { DailyModeHelper } from "./helpers/dailyModeHelper";
|
||||||
import { SettingsHelper } from "./helpers/settingsHelper";
|
import { SettingsHelper } from "./helpers/settingsHelper";
|
||||||
import { CommandPhase } from "#app/phases/command-phase.js";
|
import { CommandPhase } from "#app/phases/command-phase";
|
||||||
import { EncounterPhase } from "#app/phases/encounter-phase.js";
|
import { EncounterPhase } from "#app/phases/encounter-phase";
|
||||||
import { FaintPhase } from "#app/phases/faint-phase.js";
|
import { FaintPhase } from "#app/phases/faint-phase";
|
||||||
import { LoginPhase } from "#app/phases/login-phase.js";
|
import { LoginPhase } from "#app/phases/login-phase";
|
||||||
import { MovePhase } from "#app/phases/move-phase.js";
|
import { MovePhase } from "#app/phases/move-phase";
|
||||||
import { NewBattlePhase } from "#app/phases/new-battle-phase.js";
|
import { NewBattlePhase } from "#app/phases/new-battle-phase";
|
||||||
import { SelectStarterPhase } from "#app/phases/select-starter-phase.js";
|
import { SelectStarterPhase } from "#app/phases/select-starter-phase";
|
||||||
import { SelectTargetPhase } from "#app/phases/select-target-phase.js";
|
import { SelectTargetPhase } from "#app/phases/select-target-phase";
|
||||||
import { TitlePhase } from "#app/phases/title-phase.js";
|
import { TitlePhase } from "#app/phases/title-phase";
|
||||||
import { TurnEndPhase } from "#app/phases/turn-end-phase.js";
|
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
||||||
import { TurnInitPhase } from "#app/phases/turn-init-phase.js";
|
import { TurnInitPhase } from "#app/phases/turn-init-phase";
|
||||||
import { TurnStartPhase } from "#app/phases/turn-start-phase.js";
|
import { TurnStartPhase } from "#app/phases/turn-start-phase";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to manage the game state and transitions between phases.
|
* Class to manage the game state and transitions between phases.
|
||||||
|
Loading…
Reference in New Issue
Block a user