Merge remote-tracking branch 'upstream/beta' into modifier-fixes

This commit is contained in:
Bertie690 2025-06-08 13:07:40 -04:00
commit cd2c710937
237 changed files with 6037 additions and 3874 deletions

View File

@ -31,7 +31,6 @@
"src/overrides.ts", "src/overrides.ts",
// TODO: these files are too big and complex, ignore them until their respective refactors // TODO: these files are too big and complex, ignore them until their respective refactors
"src/data/moves/move.ts", "src/data/moves/move.ts",
"src/data/abilities/ability.ts",
// this file is just too big: // this file is just too big:
"src/data/balance/tms.ts" "src/data/balance/tms.ts"
@ -42,9 +41,6 @@
// TODO: Remove if we ever get down to 0 circular imports // TODO: Remove if we ever get down to 0 circular imports
"organizeImports": { "enabled": false }, "organizeImports": { "enabled": false },
"linter": { "linter": {
"ignore": [
"src/phases/move-effect-phase.ts" // TODO: unignore after move-effect-phase refactor
],
"enabled": true, "enabled": true,
"rules": { "rules": {
"recommended": true, "recommended": true,

View File

@ -145,6 +145,5 @@
</div> </div>
<script type="module" src="./src/main.ts"></script> <script type="module" src="./src/main.ts"></script>
<script src="./src/touch-controls.ts" type="module"></script> <script src="./src/touch-controls.ts" type="module"></script>
<script src="./src/debug.js" type="module"></script>
</body> </body>
</html> </html>

View File

@ -9,6 +9,11 @@ pre-commit:
- rebase - rebase
post-merge: post-merge:
commands:
update-submodules:
run: git submodule update --init --recursive
post-checkout:
commands: commands:
update-submodules: update-submodules:
run: git submodule update --init --recursive run: git submodule update --init --recursive

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -1,6 +1,7 @@
/** export interface DexData {
* Dex entry for a single Pokemon Species [key: number]: DexEntry;
*/ }
export interface DexEntry { export interface DexEntry {
seenAttr: bigint; seenAttr: bigint;
caughtAttr: bigint; caughtAttr: bigint;
@ -10,7 +11,3 @@ export interface DexEntry {
hatchedCount: number; hatchedCount: number;
ivs: number[]; ivs: number[];
} }
export interface DexData {
[key: number]: DexEntry;
}

View File

@ -2,9 +2,6 @@ export interface Localizable {
localize(): void; localize(): void;
} }
export interface TranslationEntries {
[key: string]: string | { [key: string]: string };
}
export interface SimpleTranslationEntries { export interface SimpleTranslationEntries {
[key: string]: string; [key: string]: string;
} }

25
src/@types/phase-types.ts Normal file
View File

@ -0,0 +1,25 @@
import type { PhaseConstructorMap } from "#app/phase-manager";
// Intentionally export the types of everything in phase-manager, as this file is meant to be
// the centralized place for type definitions for the phase system.
export type * from "#app/phase-manager";
// This file includes helpful types for the phase system.
// It intentionally imports the phase constructor map from the phase manager (and re-exports it)
/**
* Map of phase names to constructors for said phase
*/
export type PhaseMap = {
[K in keyof PhaseConstructorMap]: InstanceType<PhaseConstructorMap[K]>;
};
/**
* Union type of all phase constructors.
*/
export type PhaseClass = PhaseConstructorMap[keyof PhaseConstructorMap];
/**
* Union type of all phase names as strings.
*/
export type PhaseString = keyof PhaseMap;

View File

@ -2,8 +2,8 @@ import type { EnemyPokemon } from "#app/field/pokemon";
import type { PersistentModifier } from "#app/modifier/modifier"; import type { PersistentModifier } from "#app/modifier/modifier";
import type { PartyMemberStrength } from "#enums/party-member-strength"; import type { PartyMemberStrength } from "#enums/party-member-strength";
import type { SpeciesId } from "#enums/species-id"; import type { SpeciesId } from "#enums/species-id";
import type { TrainerConfig } from "./trainer-config"; import type { TrainerConfig } from "../data/trainers/trainer-config";
import type { TrainerPartyTemplate } from "./TrainerPartyTemplate"; import type { TrainerPartyTemplate } from "../data/trainers/TrainerPartyTemplate";
export type PartyTemplateFunc = () => TrainerPartyTemplate; export type PartyTemplateFunc = () => TrainerPartyTemplate;
export type PartyMemberFunc = (level: number, strength: PartyMemberStrength) => EnemyPokemon; export type PartyMemberFunc = (level: number, strength: PartyMemberStrength) => EnemyPokemon;

View File

@ -108,7 +108,6 @@ import {
SpeciesFormChangeManualTrigger, SpeciesFormChangeManualTrigger,
SpeciesFormChangeTimeOfDayTrigger, SpeciesFormChangeTimeOfDayTrigger,
} from "#app/data/pokemon-forms"; } from "#app/data/pokemon-forms";
import { FormChangePhase } from "#app/phases/form-change-phase";
import { getTypeRgb } from "#app/data/type"; import { getTypeRgb } from "#app/data/type";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import PokemonSpriteSparkleHandler from "#app/field/pokemon-sprite-sparkle-handler"; import PokemonSpriteSparkleHandler from "#app/field/pokemon-sprite-sparkle-handler";
@ -120,7 +119,7 @@ import { SceneBase } from "#app/scene-base";
import CandyBar from "#app/ui/candy-bar"; import CandyBar from "#app/ui/candy-bar";
import type { Variant } from "#app/sprites/variant"; import type { Variant } from "#app/sprites/variant";
import { variantData, clearVariantData } from "#app/sprites/variant"; import { variantData, clearVariantData } from "#app/sprites/variant";
import type { Localizable } from "#app/interfaces/locales"; import type { Localizable } from "#app/@types/locales";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { InputsController } from "#app/inputs-controller"; import { InputsController } from "#app/inputs-controller";
import { UiInputs } from "#app/ui-inputs"; import { UiInputs } from "#app/ui-inputs";
@ -142,21 +141,7 @@ import i18next from "i18next";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { battleSpecDialogue } from "#app/data/dialogue"; import { battleSpecDialogue } from "#app/data/dialogue";
import { LoadingScene } from "#app/loading-scene"; import { LoadingScene } from "#app/loading-scene";
import { LevelCapPhase } from "#app/phases/level-cap-phase"; import type { MovePhase } from "#app/phases/move-phase";
import { LoginPhase } from "#app/phases/login-phase";
import { MessagePhase } from "#app/phases/message-phase";
import { MovePhase } from "#app/phases/move-phase";
import { NewBiomeEncounterPhase } from "#app/phases/new-biome-encounter-phase";
import { NextEncounterPhase } from "#app/phases/next-encounter-phase";
import { PokemonAnimPhase } from "#app/phases/pokemon-anim-phase";
import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase";
import { ReturnPhase } from "#app/phases/return-phase";
import { ShowTrainerPhase } from "#app/phases/show-trainer-phase";
import { SummonPhase } from "#app/phases/summon-phase";
import { SwitchPhase } from "#app/phases/switch-phase";
import { TitlePhase } from "#app/phases/title-phase";
import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase";
import { TurnInitPhase } from "#app/phases/turn-init-phase";
import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target";
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { import {
@ -170,22 +155,19 @@ import {
import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; import type HeldModifierConfig from "#app/@types/held-modifier-config";
import { ExpPhase } from "#app/phases/exp-phase";
import { ShowPartyExpBarPhase } from "#app/phases/show-party-exp-bar-phase";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { ExpGainsSpeed } from "#enums/exp-gains-speed"; import { ExpGainsSpeed } from "#enums/exp-gains-speed";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters"; import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { initGlobalScene } from "#app/global-scene"; import { initGlobalScene } from "#app/global-scene";
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
import { HideAbilityPhase } from "#app/phases/hide-ability-phase";
import { expSpriteKeys } from "./sprites/sprite-keys"; import { expSpriteKeys } from "./sprites/sprite-keys";
import { hasExpSprite } from "./sprites/sprite-utils"; import { hasExpSprite } from "./sprites/sprite-utils";
import { timedEventManager } from "./global-event-manager"; import { timedEventManager } from "./global-event-manager";
import { starterColors } from "./global-vars/starter-colors"; import { starterColors } from "./global-vars/starter-colors";
import { startingWave } from "./starting-wave"; import { startingWave } from "./starting-wave";
import { PhaseManager } from "./phase-manager";
const DEBUG_RNG = false; const DEBUG_RNG = false;
@ -298,18 +280,8 @@ export default class BattleScene extends SceneBase {
public gameData: GameData; public gameData: GameData;
public sessionSlotId: number; public sessionSlotId: number;
/** PhaseQueue: dequeue/remove the first element to get the next phase */ /** Manager for the phases active in the battle scene */
public phaseQueue: Phase[]; public readonly phaseManager: PhaseManager;
public conditionalQueue: Array<[() => boolean, Phase]>;
/** PhaseQueuePrepend: is a temp storage of what will be added to PhaseQueue */
private phaseQueuePrepend: Phase[];
/** overrides default of inserting phases to end of phaseQueuePrepend array, useful or inserting Phases "out of order" */
private phaseQueuePrependSpliceIndex: number;
private nextCommandPhaseQueue: Phase[];
private currentPhase: Phase | null;
private standbyPhase: Phase | null;
public field: Phaser.GameObjects.Container; public field: Phaser.GameObjects.Container;
public fieldUI: Phaser.GameObjects.Container; public fieldUI: Phaser.GameObjects.Container;
public charSprite: CharSprite; public charSprite: CharSprite;
@ -397,11 +369,7 @@ export default class BattleScene extends SceneBase {
constructor() { constructor() {
super("battle"); super("battle");
this.phaseQueue = []; this.phaseManager = new PhaseManager();
this.phaseQueuePrepend = [];
this.conditionalQueue = [];
this.phaseQueuePrependSpliceIndex = -1;
this.nextCommandPhaseQueue = [];
this.eventManager = new TimedEventManager(); this.eventManager = new TimedEventManager();
this.updateGameInfo(); this.updateGameInfo();
initGlobalScene(this); initGlobalScene(this);
@ -717,10 +685,10 @@ export default class BattleScene extends SceneBase {
).then(() => loadMoveAnimAssets(defaultMoves, true)), ).then(() => loadMoveAnimAssets(defaultMoves, true)),
this.initStarterColors(), this.initStarterColors(),
]).then(() => { ]).then(() => {
this.pushPhase(new LoginPhase()); this.phaseManager.pushNew("LoginPhase");
this.pushPhase(new TitlePhase()); this.phaseManager.pushNew("TitlePhase");
this.shiftPhase(); this.phaseManager.shiftPhase();
}); });
} }
@ -812,6 +780,7 @@ export default class BattleScene extends SceneBase {
} }
} }
// TODO: Add a `getPartyOnSide` function for getting the party of a pokemon
public getPlayerParty(): PlayerPokemon[] { public getPlayerParty(): PlayerPokemon[] {
return this.party; return this.party;
} }
@ -899,9 +868,9 @@ export default class BattleScene extends SceneBase {
if (allyPokemon?.isActive(true)) { if (allyPokemon?.isActive(true)) {
let targetingMovePhase: MovePhase; let targetingMovePhase: MovePhase;
do { do {
targetingMovePhase = this.findPhase( targetingMovePhase = this.phaseManager.findPhase(
mp => mp =>
mp instanceof MovePhase && mp.is("MovePhase") &&
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.isPlayer() !== allyPokemon.isPlayer(),
@ -1287,7 +1256,7 @@ export default class BattleScene extends SceneBase {
duration: 250, duration: 250,
ease: "Sine.easeInOut", ease: "Sine.easeInOut",
onComplete: () => { onComplete: () => {
this.clearPhaseQueue(); this.phaseManager.clearPhaseQueue();
this.ui.freeUIData(); this.ui.freeUIData();
this.uiContainer.remove(this.ui, true); this.uiContainer.remove(this.ui, true);
@ -1460,7 +1429,7 @@ export default class BattleScene extends SceneBase {
} }
if (lastBattle?.double && !newDouble) { if (lastBattle?.double && !newDouble) {
this.tryRemovePhase(p => p instanceof SwitchPhase); this.phaseManager.tryRemovePhase((p: Phase) => p.is("SwitchPhase"));
for (const p of this.getPlayerField()) { for (const p of this.getPlayerField()) {
p.lapseTag(BattlerTagType.COMMANDED); p.lapseTag(BattlerTagType.COMMANDED);
} }
@ -1502,7 +1471,7 @@ export default class BattleScene extends SceneBase {
playerField.forEach((pokemon, p) => { playerField.forEach((pokemon, p) => {
if (pokemon.isOnField()) { if (pokemon.isOnField()) {
this.pushPhase(new ReturnPhase(p)); this.phaseManager.pushNew("ReturnPhase", p);
} }
}); });
@ -1519,7 +1488,7 @@ export default class BattleScene extends SceneBase {
} }
if (!this.trainer.visible) { if (!this.trainer.visible) {
this.pushPhase(new ShowTrainerPhase()); this.phaseManager.pushNew("ShowTrainerPhase");
} }
} }
@ -1528,13 +1497,13 @@ export default class BattleScene extends SceneBase {
} }
if (!this.gameMode.hasRandomBiomes && !isNewBiome) { if (!this.gameMode.hasRandomBiomes && !isNewBiome) {
this.pushPhase(new NextEncounterPhase()); this.phaseManager.pushNew("NextEncounterPhase");
} else { } else {
this.pushPhase(new NewBiomeEncounterPhase()); this.phaseManager.pushNew("NewBiomeEncounterPhase");
const newMaxExpLevel = this.getMaxExpLevel(); const newMaxExpLevel = this.getMaxExpLevel();
if (newMaxExpLevel > maxExpLevel) { if (newMaxExpLevel > maxExpLevel) {
this.pushPhase(new LevelCapPhase()); this.phaseManager.pushNew("LevelCapPhase");
} }
} }
} }
@ -1599,7 +1568,7 @@ export default class BattleScene extends SceneBase {
} }
const isEggPhase: boolean = ["EggLapsePhase", "EggHatchPhase"].includes( const isEggPhase: boolean = ["EggLapsePhase", "EggHatchPhase"].includes(
this.getCurrentPhase()?.constructor.name ?? "", this.phaseManager.getCurrentPhase()?.phaseName ?? "",
); );
if ( if (
@ -2627,286 +2596,6 @@ export default class BattleScene extends SceneBase {
} }
} }
/* Phase Functions */
getCurrentPhase(): Phase | null {
return this.currentPhase;
}
getStandbyPhase(): Phase | null {
return this.standbyPhase;
}
/**
* Adds a phase to the conditional queue and ensures it is executed only when the specified condition is met.
*
* This method allows deferring the execution of a phase until certain conditions are met, which is useful for handling
* situations like abilities and entry hazards that depend on specific game states.
*
* @param {Phase} phase - The phase to be added to the conditional queue.
* @param {() => boolean} condition - A function that returns a boolean indicating whether the phase should be executed.
*
*/
pushConditionalPhase(phase: Phase, condition: () => boolean): void {
this.conditionalQueue.push([condition, phase]);
}
/**
* Adds a phase to nextCommandPhaseQueue, as long as boolean passed in is false
* @param phase {@linkcode Phase} the phase to add
* @param defer boolean on which queue to add to, defaults to false, and adds to phaseQueue
*/
pushPhase(phase: Phase, defer = false): void {
(!defer ? this.phaseQueue : this.nextCommandPhaseQueue).push(phase);
}
/**
* Adds Phase(s) to the end of phaseQueuePrepend, or at phaseQueuePrependSpliceIndex
* @param phases {@linkcode Phase} the phase(s) to add
*/
unshiftPhase(...phases: Phase[]): void {
if (this.phaseQueuePrependSpliceIndex === -1) {
this.phaseQueuePrepend.push(...phases);
} else {
this.phaseQueuePrepend.splice(this.phaseQueuePrependSpliceIndex, 0, ...phases);
}
}
/**
* Clears the phaseQueue
*/
clearPhaseQueue(): void {
this.phaseQueue.splice(0, this.phaseQueue.length);
}
/**
* Clears all phase-related stuff, including all phase queues, the current and standby phases, and a splice index
*/
clearAllPhases(): void {
for (const queue of [this.phaseQueue, this.phaseQueuePrepend, this.conditionalQueue, this.nextCommandPhaseQueue]) {
queue.splice(0, queue.length);
}
this.currentPhase = null;
this.standbyPhase = null;
this.clearPhaseQueueSplice();
}
/**
* Used by function unshiftPhase(), sets index to start inserting at current length instead of the end of the array, useful if phaseQueuePrepend gets longer with Phases
*/
setPhaseQueueSplice(): void {
this.phaseQueuePrependSpliceIndex = this.phaseQueuePrepend.length;
}
/**
* Resets phaseQueuePrependSpliceIndex to -1, implies that calls to unshiftPhase will insert at end of phaseQueuePrepend
*/
clearPhaseQueueSplice(): void {
this.phaseQueuePrependSpliceIndex = -1;
}
/**
* Is called by each Phase implementations "end()" by default
* We dump everything from phaseQueuePrepend to the start of of phaseQueue
* then removes first Phase and starts it
*/
shiftPhase(): void {
if (this.standbyPhase) {
this.currentPhase = this.standbyPhase;
this.standbyPhase = null;
return;
}
if (this.phaseQueuePrependSpliceIndex > -1) {
this.clearPhaseQueueSplice();
}
if (this.phaseQueuePrepend.length) {
while (this.phaseQueuePrepend.length) {
const poppedPhase = this.phaseQueuePrepend.pop();
if (poppedPhase) {
this.phaseQueue.unshift(poppedPhase);
}
}
}
if (!this.phaseQueue.length) {
this.populatePhaseQueue();
// Clear the conditionalQueue if there are no phases left in the phaseQueue
this.conditionalQueue = [];
}
this.currentPhase = this.phaseQueue.shift() ?? null;
// Check if there are any conditional phases queued
if (this.conditionalQueue?.length) {
// Retrieve the first conditional phase from the queue
const conditionalPhase = this.conditionalQueue.shift();
// Evaluate the condition associated with the phase
if (conditionalPhase?.[0]()) {
// If the condition is met, add the phase to the phase queue
this.pushPhase(conditionalPhase[1]);
} else if (conditionalPhase) {
// If the condition is not met, re-add the phase back to the front of the conditional queue
this.conditionalQueue.unshift(conditionalPhase);
} else {
console.warn("condition phase is undefined/null!", conditionalPhase);
}
}
if (this.currentPhase) {
console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;");
this.currentPhase.start();
}
}
overridePhase(phase: Phase): boolean {
if (this.standbyPhase) {
return false;
}
this.standbyPhase = this.currentPhase;
this.currentPhase = phase;
console.log(`%cStart Phase ${phase.constructor.name}`, "color:green;");
phase.start();
return true;
}
/**
* Find a specific {@linkcode Phase} in the phase queue.
*
* @param phaseFilter filter function to use to find the wanted phase
* @returns the found phase or undefined if none found
*/
findPhase<P extends Phase = Phase>(phaseFilter: (phase: P) => boolean): P | undefined {
return this.phaseQueue.find(phaseFilter) as P;
}
tryReplacePhase(phaseFilter: (phase: Phase) => boolean, phase: Phase): boolean {
const phaseIndex = this.phaseQueue.findIndex(phaseFilter);
if (phaseIndex > -1) {
this.phaseQueue[phaseIndex] = phase;
return true;
}
return false;
}
tryRemovePhase(phaseFilter: (phase: Phase) => boolean): boolean {
const phaseIndex = this.phaseQueue.findIndex(phaseFilter);
if (phaseIndex > -1) {
this.phaseQueue.splice(phaseIndex, 1);
return true;
}
return false;
}
/**
* Will search for a specific phase in {@linkcode phaseQueuePrepend} via filter, and remove the first result if a match is found.
* @param phaseFilter filter function
*/
tryRemoveUnshiftedPhase(phaseFilter: (phase: Phase) => boolean): boolean {
const phaseIndex = this.phaseQueuePrepend.findIndex(phaseFilter);
if (phaseIndex > -1) {
this.phaseQueuePrepend.splice(phaseIndex, 1);
return true;
}
return false;
}
/**
* Tries to add the input phase to index before target phase in the phaseQueue, else simply calls unshiftPhase()
* @param phase {@linkcode Phase} the phase to be added
* @param targetPhase {@linkcode Phase} the type of phase to search for in phaseQueue
* @returns boolean if a targetPhase was found and added
*/
prependToPhase(phase: Phase | Phase[], targetPhase: Constructor<Phase>): boolean {
if (!Array.isArray(phase)) {
phase = [phase];
}
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof targetPhase);
if (targetIndex !== -1) {
this.phaseQueue.splice(targetIndex, 0, ...phase);
return true;
}
this.unshiftPhase(...phase);
return false;
}
/**
* Tries to add the input phase(s) to index after target phase in the {@linkcode phaseQueue}, else simply calls {@linkcode unshiftPhase()}
* @param phase {@linkcode Phase} the phase(s) to be added
* @param targetPhase {@linkcode Phase} the type of phase to search for in {@linkcode phaseQueue}
* @returns `true` if a `targetPhase` was found to append to
*/
appendToPhase(phase: Phase | Phase[], targetPhase: Constructor<Phase>): boolean {
if (!Array.isArray(phase)) {
phase = [phase];
}
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof targetPhase);
if (targetIndex !== -1 && this.phaseQueue.length > targetIndex) {
this.phaseQueue.splice(targetIndex + 1, 0, ...phase);
return true;
}
this.unshiftPhase(...phase);
return false;
}
/**
* Adds a MessagePhase, either to PhaseQueuePrepend or nextCommandPhaseQueue
* @param message string for MessagePhase
* @param callbackDelay optional param for MessagePhase constructor
* @param prompt optional param for MessagePhase constructor
* @param promptDelay optional param for MessagePhase constructor
* @param defer boolean for which queue to add it to, false -> add to PhaseQueuePrepend, true -> nextCommandPhaseQueue
*/
queueMessage(
message: string,
callbackDelay?: number | null,
prompt?: boolean | null,
promptDelay?: number | null,
defer?: boolean | null,
) {
const phase = new MessagePhase(message, callbackDelay, prompt, promptDelay);
if (!defer) {
// adds to the end of PhaseQueuePrepend
this.unshiftPhase(phase);
} else {
//remember that pushPhase adds it to nextCommandPhaseQueue
this.pushPhase(phase);
}
}
/**
* Queues an ability bar flyout phase
* @param pokemon The pokemon who has the ability
* @param passive Whether the ability is a passive
* @param show Whether to show or hide the bar
*/
public queueAbilityDisplay(pokemon: Pokemon, passive: boolean, show: boolean): void {
this.unshiftPhase(show ? new ShowAbilityPhase(pokemon.getBattlerIndex(), passive) : new HideAbilityPhase());
this.clearPhaseQueueSplice();
}
/**
* Hides the ability bar if it is currently visible
*/
public hideAbilityBar(): void {
if (this.abilityBar.isVisible()) {
this.unshiftPhase(new HideAbilityPhase());
}
}
/**
* Moves everything from nextCommandPhaseQueue to phaseQueue (keeping order)
*/
populatePhaseQueue(): void {
if (this.nextCommandPhaseQueue.length) {
this.phaseQueue.push(...this.nextCommandPhaseQueue);
this.nextCommandPhaseQueue.splice(0, this.nextCommandPhaseQueue.length);
}
this.phaseQueue.push(new TurnInitPhase());
}
addMoney(amount: number): void { addMoney(amount: number): void {
this.money = Math.min(this.money + amount, Number.MAX_SAFE_INTEGER); this.money = Math.min(this.money + amount, Number.MAX_SAFE_INTEGER);
this.updateMoneyText(); this.updateMoneyText();
@ -2954,7 +2643,7 @@ export default class BattleScene extends SceneBase {
} }
} else if (!virtual) { } else if (!virtual) {
const defaultModifierType = getDefaultModifierTypeForTier(modifier.type.tier); const defaultModifierType = getDefaultModifierTypeForTier(modifier.type.tier);
this.queueMessage( this.phaseManager.queueMessage(
i18next.t("battle:itemStackFull", { i18next.t("battle:itemStackFull", {
fullItemName: modifier.type.name, fullItemName: modifier.type.name,
itemName: defaultModifierType.name, itemName: defaultModifierType.name,
@ -3545,17 +3234,17 @@ export default class BattleScene extends SceneBase {
} }
if (matchingFormChange) { if (matchingFormChange) {
let phase: Phase; let phase: Phase;
if (pokemon instanceof PlayerPokemon && !matchingFormChange.quiet) { if (pokemon.isPlayer() && !matchingFormChange.quiet) {
phase = new FormChangePhase(pokemon, matchingFormChange, modal); phase = this.phaseManager.create("FormChangePhase", pokemon, matchingFormChange, modal);
} else { } else {
phase = new QuietFormChangePhase(pokemon, matchingFormChange); phase = this.phaseManager.create("QuietFormChangePhase", pokemon, matchingFormChange);
} }
if (pokemon instanceof PlayerPokemon && !matchingFormChange.quiet && modal) { if (pokemon.isPlayer() && !matchingFormChange.quiet && modal) {
this.overridePhase(phase); this.phaseManager.overridePhase(phase);
} else if (delayed) { } else if (delayed) {
this.pushPhase(phase); this.phaseManager.pushPhase(phase);
} else { } else {
this.unshiftPhase(phase); this.phaseManager.unshiftPhase(phase);
} }
return true; return true;
} }
@ -3570,11 +3259,12 @@ export default class BattleScene extends SceneBase {
fieldAssets?: Phaser.GameObjects.Sprite[], fieldAssets?: Phaser.GameObjects.Sprite[],
delayed = false, delayed = false,
): boolean { ): boolean {
const phase: Phase = new PokemonAnimPhase(battleAnimType, pokemon, fieldAssets); const phaseManager = this.phaseManager;
const phase: Phase = phaseManager.create("PokemonAnimPhase", battleAnimType, pokemon, fieldAssets);
if (delayed) { if (delayed) {
this.pushPhase(phase); phaseManager.pushPhase(phase);
} else { } else {
this.unshiftPhase(phase); phaseManager.unshiftPhase(phase);
} }
return true; return true;
} }
@ -3619,21 +3309,18 @@ export default class BattleScene extends SceneBase {
gameMode: this.currentBattle ? this.gameMode.getName() : "Title", gameMode: this.currentBattle ? this.gameMode.getName() : "Title",
biome: this.currentBattle ? getBiomeName(this.arena.biomeType) : "", biome: this.currentBattle ? getBiomeName(this.arena.biomeType) : "",
wave: this.currentBattle?.waveIndex ?? 0, wave: this.currentBattle?.waveIndex ?? 0,
party: this.party party:
? this.party.map(p => { this.party?.map(p => ({
return { name: p.name,
name: p.name, form: p.getFormKey(),
form: p.getFormKey(), types: p.getTypes().map(type => PokemonType[type]),
types: p.getTypes().map(type => PokemonType[type]), teraType: PokemonType[p.getTeraType()],
teraType: PokemonType[p.getTeraType()], isTerastallized: p.isTerastallized,
isTerastallized: p.isTerastallized, level: p.level,
level: p.level, currentHP: p.hp,
currentHP: p.hp, maxHP: p.getMaxHp(),
maxHP: p.getMaxHp(), status: p.status?.effect ? StatusEffect[p.status.effect] : "",
status: p.status?.effect ? StatusEffect[p.status.effect] : "", })) ?? [], // TODO: review if this can be nullish
};
})
: [],
modeChain: this.ui?.getModeChain() ?? [], modeChain: this.ui?.getModeChain() ?? [],
}; };
(window as any).gameInfo = gameInfo; (window as any).gameInfo = gameInfo;
@ -3651,7 +3338,7 @@ export default class BattleScene extends SceneBase {
activePokemon = activePokemon.concat(this.getEnemyParty()); activePokemon = activePokemon.concat(this.getEnemyParty());
for (const p of activePokemon) { for (const p of activePokemon) {
keys.push(p.getSpriteKey(true)); keys.push(p.getSpriteKey(true));
if (p instanceof PlayerPokemon) { if (p.isPlayer()) {
keys.push(p.getBattleSpriteKey(true, true)); keys.push(p.getBattleSpriteKey(true, true));
} }
keys.push(p.species.getCryKey(p.formIndex)); keys.push(p.species.getCryKey(p.formIndex));
@ -3667,7 +3354,7 @@ export default class BattleScene extends SceneBase {
* @param pokemon The (enemy) pokemon * @param pokemon The (enemy) pokemon
*/ */
initFinalBossPhaseTwo(pokemon: Pokemon): void { initFinalBossPhaseTwo(pokemon: Pokemon): void {
if (pokemon instanceof EnemyPokemon && pokemon.isBoss() && !pokemon.formIndex && pokemon.bossSegmentIndex < 1) { if (pokemon.isEnemy() && pokemon.isBoss() && !pokemon.formIndex && pokemon.bossSegmentIndex < 1) {
this.fadeOutBgm(fixedInt(2000), false); this.fadeOutBgm(fixedInt(2000), false);
this.ui.showDialogue( this.ui.showDialogue(
battleSpecDialogue[BattleSpec.FINAL_BOSS].firstStageWin, battleSpecDialogue[BattleSpec.FINAL_BOSS].firstStageWin,
@ -3685,19 +3372,19 @@ export default class BattleScene extends SceneBase {
this.currentBattle.double = true; this.currentBattle.double = true;
const availablePartyMembers = this.getPlayerParty().filter(p => p.isAllowedInBattle()); const availablePartyMembers = this.getPlayerParty().filter(p => p.isAllowedInBattle());
if (availablePartyMembers.length > 1) { if (availablePartyMembers.length > 1) {
this.pushPhase(new ToggleDoublePositionPhase(true)); this.phaseManager.pushNew("ToggleDoublePositionPhase", true);
if (!availablePartyMembers[1].isOnField()) { if (!availablePartyMembers[1].isOnField()) {
this.pushPhase(new SummonPhase(1)); this.phaseManager.pushNew("SummonPhase", 1);
} }
} }
this.shiftPhase(); this.phaseManager.shiftPhase();
}, },
); );
return; return;
} }
this.shiftPhase(); this.phaseManager.shiftPhase();
} }
/** /**
@ -3809,10 +3496,10 @@ export default class BattleScene extends SceneBase {
if (exp) { if (exp) {
const partyMemberIndex = party.indexOf(expPartyMembers[pm]); const partyMemberIndex = party.indexOf(expPartyMembers[pm]);
this.unshiftPhase( this.phaseManager.unshiftPhase(
expPartyMembers[pm].isOnField() expPartyMembers[pm].isOnField()
? new ExpPhase(partyMemberIndex, exp) ? this.phaseManager.create("ExpPhase", partyMemberIndex, exp)
: new ShowPartyExpBarPhase(partyMemberIndex, exp), : this.phaseManager.create("ShowPartyExpBarPhase", partyMemberIndex, exp),
); );
} }
} }
@ -4016,16 +3703,13 @@ export default class BattleScene extends SceneBase {
if (previousEncounter !== null && encounterType === previousEncounter) { if (previousEncounter !== null && encounterType === previousEncounter) {
return false; return false;
} }
if ( return !(
this.mysteryEncounterSaveData.encounteredEvents.length > 0 && this.mysteryEncounterSaveData.encounteredEvents.length > 0 &&
encounterCandidate.maxAllowedEncounters && encounterCandidate.maxAllowedEncounters &&
encounterCandidate.maxAllowedEncounters > 0 && encounterCandidate.maxAllowedEncounters > 0 &&
this.mysteryEncounterSaveData.encounteredEvents.filter(e => e.type === encounterType).length >= this.mysteryEncounterSaveData.encounteredEvents.filter(e => e.type === encounterType).length >=
encounterCandidate.maxAllowedEncounters encounterCandidate.maxAllowedEncounters
) { );
return false;
}
return true;
}) })
.map(m => allMysteryEncounters[m]); .map(m => allMysteryEncounters[m]);
// Decrement tier // Decrement tier

View File

@ -8,6 +8,7 @@ import {
shiftCharCodes, shiftCharCodes,
randSeedItem, randSeedItem,
randInt, randInt,
randSeedFloat,
} from "#app/utils/common"; } from "#app/utils/common";
import Trainer, { TrainerVariant } from "./field/trainer"; import Trainer, { TrainerVariant } from "./field/trainer";
import type { GameMode } from "./game-mode"; import type { GameMode } from "./game-mode";
@ -150,7 +151,7 @@ export default class Battle {
randSeedGaussForLevel(value: number): number { randSeedGaussForLevel(value: number): number {
let rand = 0; let rand = 0;
for (let i = value; i > 0; i--) { for (let i = value; i > 0; i--) {
rand += Phaser.Math.RND.realInRange(0, 1); rand += randSeedFloat();
} }
return rand / value; return rand / value;
} }
@ -199,7 +200,7 @@ export default class Battle {
const message = i18next.t("battle:moneyPickedUp", { const message = i18next.t("battle:moneyPickedUp", {
moneyAmount: formattedMoneyAmount, moneyAmount: formattedMoneyAmount,
}); });
globalScene.queueMessage(message, undefined, true); globalScene.phaseManager.queueMessage(message, undefined, true);
globalScene.currentBattle.moneyScattered = 0; globalScene.currentBattle.moneyScattered = 0;
} }

View File

@ -197,10 +197,7 @@ export function canIAssignThisKey(config, key) {
export function canIOverrideThisSetting(config, settingName) { export function canIOverrideThisSetting(config, settingName) {
const key = getKeyWithSettingName(config, settingName); const key = getKeyWithSettingName(config, settingName);
// || isTheLatestBind(config, settingName) no longer needed since action and cancel are protected // || isTheLatestBind(config, settingName) no longer needed since action and cancel are protected
if (config.blacklist?.includes(key)) { return !config.blacklist?.includes(key);
return false;
}
return true;
} }
export function canIDeleteThisKey(config, key) { export function canIDeleteThisKey(config, key) {

View File

@ -2,7 +2,7 @@ import { AbilityId } from "#enums/ability-id";
import type { AbAttrCondition } from "#app/@types/ability-types"; import type { AbAttrCondition } from "#app/@types/ability-types";
import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr"; import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr";
import i18next from "i18next"; import i18next from "i18next";
import type { Localizable } from "#app/interfaces/locales"; import type { Localizable } from "#app/@types/locales";
import type { Constructor } from "#app/utils/common"; import type { Constructor } from "#app/utils/common";
export class Ability implements Localizable { export class Ability implements Localizable {

File diff suppressed because it is too large Load Diff

View File

@ -26,10 +26,6 @@ import { AbilityId } from "#enums/ability-id";
import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CommonAnimPhase } from "#app/phases/common-anim-phase";
export enum ArenaTagSide { export enum ArenaTagSide {
BOTH, BOTH,
@ -54,7 +50,7 @@ export abstract class ArenaTag {
onRemove(_arena: Arena, quiet = false): void { onRemove(_arena: Arena, quiet = false): void {
if (!quiet) { if (!quiet) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:arenaOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:arenaOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
{ moveName: this.getMoveName() }, { moveName: this.getMoveName() },
@ -134,7 +130,7 @@ export class MistTag extends ArenaTag {
return; return;
} }
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:mistOnAdd", { i18next.t("arenaTag:mistOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(source), pokemonNameWithAffix: getPokemonNameWithAffix(source),
}), }),
@ -165,7 +161,7 @@ export class MistTag extends ArenaTag {
cancelled.value = true; cancelled.value = true;
if (!simulated) { if (!simulated) {
globalScene.queueMessage(i18next.t("arenaTag:mistApply")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:mistApply"));
} }
return true; return true;
@ -243,7 +239,7 @@ class ReflectTag extends WeakenMoveScreenTag {
onAdd(_arena: Arena, quiet = false): void { onAdd(_arena: Arena, quiet = false): void {
if (!quiet) { if (!quiet) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:reflectOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:reflectOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -263,7 +259,7 @@ class LightScreenTag extends WeakenMoveScreenTag {
onAdd(_arena: Arena, quiet = false): void { onAdd(_arena: Arena, quiet = false): void {
if (!quiet) { if (!quiet) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:lightScreenOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:lightScreenOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -286,7 +282,7 @@ class AuroraVeilTag extends WeakenMoveScreenTag {
onAdd(_arena: Arena, quiet = false): void { onAdd(_arena: Arena, quiet = false): void {
if (!quiet) { if (!quiet) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:auroraVeilOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:auroraVeilOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -322,7 +318,7 @@ export class ConditionalProtectTag extends ArenaTag {
} }
onAdd(_arena: Arena): void { onAdd(_arena: Arena): void {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:conditionalProtectOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:conditionalProtectOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
{ moveName: super.getMoveName() }, { moveName: super.getMoveName() },
@ -359,7 +355,7 @@ export class ConditionalProtectTag extends ArenaTag {
isProtected.value = true; isProtected.value = true;
if (!simulated) { if (!simulated) {
new CommonBattleAnim(CommonAnim.PROTECT, defender).play(); new CommonBattleAnim(CommonAnim.PROTECT, defender).play();
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:conditionalProtectApply", { i18next.t("arenaTag:conditionalProtectApply", {
moveName: super.getMoveName(), moveName: super.getMoveName(),
pokemonNameWithAffix: getPokemonNameWithAffix(defender), pokemonNameWithAffix: getPokemonNameWithAffix(defender),
@ -385,9 +381,9 @@ export class ConditionalProtectTag extends ArenaTag {
*/ */
const QuickGuardConditionFunc: ProtectConditionFunc = (_arena, moveId) => { const QuickGuardConditionFunc: ProtectConditionFunc = (_arena, moveId) => {
const move = allMoves[moveId]; const move = allMoves[moveId];
const effectPhase = globalScene.getCurrentPhase(); const effectPhase = globalScene.phaseManager.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase) { if (effectPhase?.is("MoveEffectPhase")) {
const attacker = effectPhase.getUserPokemon(); const attacker = effectPhase.getUserPokemon();
if (attacker) { if (attacker) {
return move.getPriority(attacker) > 0; return move.getPriority(attacker) > 0;
@ -466,7 +462,7 @@ class MatBlockTag extends ConditionalProtectTag {
} }
super.onAdd(_arena); super.onAdd(_arena);
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:matBlockOnAdd", { i18next.t("arenaTag:matBlockOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(source), pokemonNameWithAffix: getPokemonNameWithAffix(source),
}), }),
@ -521,7 +517,7 @@ export class NoCritTag extends ArenaTag {
/** Queues a message upon adding this effect to the field */ /** Queues a message upon adding this effect to the field */
onAdd(_arena: Arena): void { onAdd(_arena: Arena): void {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t(`arenaTag:noCritOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : "Enemy"}`, { i18next.t(`arenaTag:noCritOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : "Enemy"}`, {
moveName: this.getMoveName(), moveName: this.getMoveName(),
}), }),
@ -536,7 +532,7 @@ export class NoCritTag extends ArenaTag {
return; return;
} }
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:noCritOnRemove", { i18next.t("arenaTag:noCritOnRemove", {
pokemonNameWithAffix: getPokemonNameWithAffix(source ?? undefined), pokemonNameWithAffix: getPokemonNameWithAffix(source ?? undefined),
moveName: this.getMoveName(), moveName: this.getMoveName(),
@ -568,7 +564,7 @@ class WishTag extends ArenaTag {
super.onAdd(_arena); super.onAdd(_arena);
this.healHp = toDmgValue(source.getMaxHp() / 2); this.healHp = toDmgValue(source.getMaxHp() / 2);
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:wishTagOnAdd", { i18next.t("arenaTag:wishTagOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(source), pokemonNameWithAffix: getPokemonNameWithAffix(source),
}), }),
@ -578,8 +574,8 @@ class WishTag extends ArenaTag {
onRemove(_arena: Arena): void { onRemove(_arena: Arena): void {
const target = globalScene.getField()[this.battlerIndex]; const target = globalScene.getField()[this.battlerIndex];
if (target?.isActive(true)) { if (target?.isActive(true)) {
globalScene.queueMessage(this.triggerMessage); globalScene.phaseManager.queueMessage(this.triggerMessage);
globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), this.healHp, null, true, false)); globalScene.phaseManager.unshiftNew("PokemonHealPhase", target.getBattlerIndex(), this.healHp, null, true, false);
} }
} }
} }
@ -632,11 +628,11 @@ class MudSportTag extends WeakenMoveTypeTag {
} }
onAdd(_arena: Arena): void { onAdd(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:mudSportOnAdd")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:mudSportOnAdd"));
} }
onRemove(_arena: Arena): void { onRemove(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:mudSportOnRemove")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:mudSportOnRemove"));
} }
} }
@ -650,11 +646,11 @@ class WaterSportTag extends WeakenMoveTypeTag {
} }
onAdd(_arena: Arena): void { onAdd(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:waterSportOnAdd")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:waterSportOnAdd"));
} }
onRemove(_arena: Arena): void { onRemove(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:waterSportOnRemove")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:waterSportOnRemove"));
} }
} }
@ -670,7 +666,7 @@ export class IonDelugeTag extends ArenaTag {
/** Queues an on-add message */ /** Queues an on-add message */
onAdd(_arena: Arena): void { onAdd(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:plasmaFistsOnAdd")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:plasmaFistsOnAdd"));
} }
onRemove(_arena: Arena): void {} // Removes default on-remove message onRemove(_arena: Arena): void {} // Removes default on-remove message
@ -778,7 +774,7 @@ class SpikesTag extends ArenaTrapTag {
return; return;
} }
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:spikesOnAdd", { i18next.t("arenaTag:spikesOnAdd", {
moveName: this.getMoveName(), moveName: this.getMoveName(),
opponentDesc: source.getOpponentDescriptor(), opponentDesc: source.getOpponentDescriptor(),
@ -800,7 +796,7 @@ class SpikesTag extends ArenaTrapTag {
const damageHpRatio = 1 / (10 - 2 * this.layers); const damageHpRatio = 1 / (10 - 2 * this.layers);
const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio); const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio);
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:spikesActivateTrap", { i18next.t("arenaTag:spikesActivateTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), }),
@ -839,7 +835,7 @@ class ToxicSpikesTag extends ArenaTrapTag {
return; return;
} }
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:toxicSpikesOnAdd", { i18next.t("arenaTag:toxicSpikesOnAdd", {
moveName: this.getMoveName(), moveName: this.getMoveName(),
opponentDesc: source.getOpponentDescriptor(), opponentDesc: source.getOpponentDescriptor(),
@ -861,7 +857,7 @@ class ToxicSpikesTag extends ArenaTrapTag {
if (pokemon.isOfType(PokemonType.POISON)) { if (pokemon.isOfType(PokemonType.POISON)) {
this.neutralized = true; this.neutralized = true;
if (globalScene.arena.removeTag(this.tagType)) { if (globalScene.arena.removeTag(this.tagType)) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:toxicSpikesActivateTrapPoison", { i18next.t("arenaTag:toxicSpikesActivateTrapPoison", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
moveName: this.getMoveName(), moveName: this.getMoveName(),
@ -918,8 +914,13 @@ export class DelayedAttackTag extends ArenaTag {
const ret = super.lapse(arena); const ret = super.lapse(arena);
if (!ret) { if (!ret) {
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new MoveEffectPhase(this.sourceId!, [this.targetIndex], allMoves[this.sourceMove!], false, true), "MoveEffectPhase",
this.sourceId!,
[this.targetIndex],
allMoves[this.sourceMove!],
false,
true,
); // TODO: are those bangs correct? ); // TODO: are those bangs correct?
} }
@ -948,7 +949,7 @@ class StealthRockTag extends ArenaTrapTag {
const source = this.getSourcePokemon(); const source = this.getSourcePokemon();
if (!quiet && source) { if (!quiet && source) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:stealthRockOnAdd", { i18next.t("arenaTag:stealthRockOnAdd", {
opponentDesc: source.getOpponentDescriptor(), opponentDesc: source.getOpponentDescriptor(),
}), }),
@ -1002,7 +1003,7 @@ class StealthRockTag extends ArenaTrapTag {
} }
const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio); const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio);
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:stealthRockActivateTrap", { i18next.t("arenaTag:stealthRockActivateTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), }),
@ -1042,7 +1043,7 @@ class StickyWebTag extends ArenaTrapTag {
return; return;
} }
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:spikesOnAdd", { i18next.t("arenaTag:spikesOnAdd", {
moveName: this.getMoveName(), moveName: this.getMoveName(),
opponentDesc: source.getOpponentDescriptor(), opponentDesc: source.getOpponentDescriptor(),
@ -1060,25 +1061,24 @@ class StickyWebTag extends ArenaTrapTag {
} }
if (!cancelled.value) { if (!cancelled.value) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:stickyWebActivateTrap", { i18next.t("arenaTag:stickyWebActivateTrap", {
pokemonName: pokemon.getNameToRender(), pokemonName: pokemon.getNameToRender(),
}), }),
); );
const stages = new NumberHolder(-1); const stages = new NumberHolder(-1);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase( "StatStageChangePhase",
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
false, false,
[Stat.SPD], [Stat.SPD],
stages.value, stages.value,
true, true,
false, false,
true, true,
null, null,
false, false,
true, true,
),
); );
return true; return true;
} }
@ -1120,7 +1120,7 @@ export class TrickRoomTag extends ArenaTag {
return; return;
} }
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:spikesOnAdd", { i18next.t("arenaTag:spikesOnAdd", {
moveName: this.getMoveName(), moveName: this.getMoveName(),
opponentDesc: source.getOpponentDescriptor(), opponentDesc: source.getOpponentDescriptor(),
@ -1129,7 +1129,7 @@ export class TrickRoomTag extends ArenaTag {
} }
onRemove(_arena: Arena): void { onRemove(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:trickRoomOnRemove")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:trickRoomOnRemove"));
} }
} }
@ -1144,7 +1144,7 @@ export class GravityTag extends ArenaTag {
} }
onAdd(_arena: Arena): void { onAdd(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:gravityOnAdd")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:gravityOnAdd"));
globalScene.getField(true).forEach(pokemon => { globalScene.getField(true).forEach(pokemon => {
if (pokemon !== null) { if (pokemon !== null) {
pokemon.removeTag(BattlerTagType.FLOATING); pokemon.removeTag(BattlerTagType.FLOATING);
@ -1157,7 +1157,7 @@ export class GravityTag extends ArenaTag {
} }
onRemove(_arena: Arena): void { onRemove(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:gravityOnRemove")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:gravityOnRemove"));
} }
} }
@ -1180,7 +1180,7 @@ class TailwindTag extends ArenaTag {
super.onAdd(_arena, quiet); super.onAdd(_arena, quiet);
if (!quiet) { if (!quiet) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:tailwindOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:tailwindOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -1191,28 +1191,30 @@ class TailwindTag extends ArenaTag {
for (const pokemon of field) { for (const pokemon of field) {
// Apply the CHARGED tag to party members with the WIND_POWER ability // Apply the CHARGED tag to party members with the WIND_POWER ability
// TODO: This should not be handled here
if (pokemon.hasAbility(AbilityId.WIND_POWER) && !pokemon.getTag(BattlerTagType.CHARGED)) { if (pokemon.hasAbility(AbilityId.WIND_POWER) && !pokemon.getTag(BattlerTagType.CHARGED)) {
pokemon.addTag(BattlerTagType.CHARGED); pokemon.addTag(BattlerTagType.CHARGED);
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("abilityTriggers:windPowerCharged", { i18next.t("abilityTriggers:windPowerCharged", {
pokemonName: getPokemonNameWithAffix(pokemon), pokemonName: getPokemonNameWithAffix(pokemon),
moveName: this.getMoveName(), moveName: this.getMoveName(),
}), }),
); );
} }
// Raise attack by one stage if party member has WIND_RIDER ability // Raise attack by one stage if party member has WIND_RIDER ability
// TODO: Ability displays should be handled by the ability // TODO: Ability displays should be handled by the ability
if (pokemon.hasAbility(AbilityId.WIND_RIDER)) { if (pokemon.hasAbility(AbilityId.WIND_RIDER)) {
globalScene.queueAbilityDisplay(pokemon, false, true); globalScene.phaseManager.queueAbilityDisplay(pokemon, false, true);
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.ATK], 1, true)); globalScene.phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), true, [Stat.ATK], 1, true);
globalScene.queueAbilityDisplay(pokemon, false, false); globalScene.phaseManager.queueAbilityDisplay(pokemon, false, false);
} }
} }
} }
onRemove(_arena: Arena, quiet = false): void { onRemove(_arena: Arena, quiet = false): void {
if (!quiet) { if (!quiet) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:tailwindOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:tailwindOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -1231,11 +1233,11 @@ class HappyHourTag extends ArenaTag {
} }
onAdd(_arena: Arena): void { onAdd(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:happyHourOnAdd")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:happyHourOnAdd"));
} }
onRemove(_arena: Arena): void { onRemove(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:happyHourOnRemove")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:happyHourOnRemove"));
} }
} }
@ -1245,7 +1247,7 @@ class SafeguardTag extends ArenaTag {
} }
onAdd(_arena: Arena): void { onAdd(_arena: Arena): void {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:safeguardOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:safeguardOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -1253,7 +1255,7 @@ class SafeguardTag extends ArenaTag {
} }
onRemove(_arena: Arena): void { onRemove(_arena: Arena): void {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:safeguardOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:safeguardOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -1292,7 +1294,7 @@ class ImprisonTag extends ArenaTrapTag {
} }
}); });
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("battlerTags:imprisonOnAdd", { i18next.t("battlerTags:imprisonOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(source), pokemonNameWithAffix: getPokemonNameWithAffix(source),
}), }),
@ -1346,7 +1348,7 @@ class FireGrassPledgeTag extends ArenaTag {
override onAdd(_arena: Arena): void { override onAdd(_arena: Arena): void {
// "A sea of fire enveloped your/the opposing team!" // "A sea of fire enveloped your/the opposing team!"
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:fireGrassPledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:fireGrassPledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -1361,14 +1363,17 @@ class FireGrassPledgeTag extends ArenaTag {
.filter(pokemon => !pokemon.isOfType(PokemonType.FIRE) && !pokemon.switchOutStatus) .filter(pokemon => !pokemon.isOfType(PokemonType.FIRE) && !pokemon.switchOutStatus)
.forEach(pokemon => { .forEach(pokemon => {
// "{pokemonNameWithAffix} was hurt by the sea of fire!" // "{pokemonNameWithAffix} was hurt by the sea of fire!"
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:fireGrassPledgeLapse", { i18next.t("arenaTag:fireGrassPledgeLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), }),
); );
// TODO: Replace this with a proper animation // TODO: Replace this with a proper animation
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.MAGMA_STORM), "CommonAnimPhase",
pokemon.getBattlerIndex(),
pokemon.getBattlerIndex(),
CommonAnim.MAGMA_STORM,
); );
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT }); pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
}); });
@ -1391,7 +1396,7 @@ class WaterFirePledgeTag extends ArenaTag {
override onAdd(_arena: Arena): void { override onAdd(_arena: Arena): void {
// "A rainbow appeared in the sky on your/the opposing team's side!" // "A rainbow appeared in the sky on your/the opposing team's side!"
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:waterFirePledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:waterFirePledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -1425,7 +1430,7 @@ class GrassWaterPledgeTag extends ArenaTag {
override onAdd(_arena: Arena): void { override onAdd(_arena: Arena): void {
// "A swamp enveloped your/the opposing team!" // "A swamp enveloped your/the opposing team!"
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:grassWaterPledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:grassWaterPledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -1446,7 +1451,7 @@ export class FairyLockTag extends ArenaTag {
} }
onAdd(_arena: Arena): void { onAdd(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:fairyLockOnAdd")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:fairyLockOnAdd"));
} }
} }
@ -1503,7 +1508,7 @@ export class SuppressAbilitiesTag extends ArenaTag {
public override onRemove(_arena: Arena, quiet = false) { public override onRemove(_arena: Arena, quiet = false) {
this.beingRemoved = true; this.beingRemoved = true;
if (!quiet) { if (!quiet) {
globalScene.queueMessage(i18next.t("arenaTag:neutralizingGasOnRemove")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:neutralizingGasOnRemove"));
} }
for (const pokemon of globalScene.getField(true)) { for (const pokemon of globalScene.getField(true)) {
@ -1524,7 +1529,7 @@ export class SuppressAbilitiesTag extends ArenaTag {
private playActivationMessage(pokemon: Pokemon | null) { private playActivationMessage(pokemon: Pokemon | null) {
if (pokemon) { if (pokemon) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:neutralizingGasOnAdd", { i18next.t("arenaTag:neutralizingGasOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), }),

File diff suppressed because it is too large Load Diff

View File

@ -3,17 +3,11 @@ import type Pokemon from "../field/pokemon";
import { HitResult } from "../field/pokemon"; import { HitResult } from "../field/pokemon";
import { getStatusEffectHealText } from "./status-effect"; import { getStatusEffectHealText } from "./status-effect";
import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils/common"; import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils/common";
import { import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./abilities/ability";
DoubleBerryEffectAbAttr,
ReduceBerryUseThresholdAbAttr,
applyAbAttrs,
} from "./abilities/ability";
import i18next from "i18next"; import i18next from "i18next";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { Stat, type BattleStat } from "#app/enums/stat"; import { Stat, type BattleStat } from "#app/enums/stat";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
export function getBerryName(berryType: BerryType): string { export function getBerryName(berryType: BerryType): string {
@ -79,23 +73,22 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
{ {
const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4)); const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4));
applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, hpHealed); applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, hpHealed);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new PokemonHealPhase( "PokemonHealPhase",
consumer.getBattlerIndex(), consumer.getBattlerIndex(),
hpHealed.value, hpHealed.value,
i18next.t("battle:hpHealBerry", { i18next.t("battle:hpHealBerry", {
pokemonNameWithAffix: getPokemonNameWithAffix(consumer), pokemonNameWithAffix: getPokemonNameWithAffix(consumer),
berryName: getBerryName(berryType), berryName: getBerryName(berryType),
}), }),
true, true,
),
); );
} }
break; break;
case BerryType.LUM: case BerryType.LUM:
{ {
if (consumer.status) { if (consumer.status) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
getStatusEffectHealText(consumer.status.effect, getPokemonNameWithAffix(consumer)), getStatusEffectHealText(consumer.status.effect, getPokemonNameWithAffix(consumer)),
); );
} }
@ -113,8 +106,12 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
const stat: BattleStat = berryType - BerryType.ENIGMA; const stat: BattleStat = berryType - BerryType.ENIGMA;
const statStages = new NumberHolder(1); const statStages = new NumberHolder(1);
applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, statStages); applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, statStages);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase(consumer.getBattlerIndex(), true, [stat], statStages.value), "StatStageChangePhase",
consumer.getBattlerIndex(),
true,
[stat],
statStages.value,
); );
} }
break; break;
@ -130,8 +127,12 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
const randStat = randSeedInt(Stat.SPD, Stat.ATK); const randStat = randSeedInt(Stat.SPD, Stat.ATK);
const stages = new NumberHolder(2); const stages = new NumberHolder(2);
applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, stages); applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, stages);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase(consumer.getBattlerIndex(), true, [randStat], stages.value), "StatStageChangePhase",
consumer.getBattlerIndex(),
true,
[randStat],
stages.value,
); );
} }
break; break;
@ -144,7 +145,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
consumer.getMoveset().find(m => m.ppUsed < m.getMovePp()); consumer.getMoveset().find(m => m.ppUsed < m.getMovePp());
if (ppRestoreMove) { if (ppRestoreMove) {
ppRestoreMove.ppUsed = Math.max(ppRestoreMove.ppUsed - 10, 0); ppRestoreMove.ppUsed = Math.max(ppRestoreMove.ppUsed - 10, 0);
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("battle:ppHealBerry", { i18next.t("battle:ppHealBerry", {
pokemonNameWithAffix: getPokemonNameWithAffix(consumer), pokemonNameWithAffix: getPokemonNameWithAffix(consumer),
moveName: ppRestoreMove.getName(), moveName: ppRestoreMove.getName(),

View File

@ -1,6 +1,7 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type { DexEntry, StarterDataEntry } from "#app/system/game-data"; import type { StarterDataEntry } from "#app/system/game-data";
import type { DexEntry } from "#app/@types/dex-data";
/** /**
* Stores data associated with a specific egg and the hatched pokemon * Stores data associated with a specific egg and the hatched pokemon

View File

@ -29,7 +29,7 @@ import {
} from "../status-effect"; } from "../status-effect";
import { getTypeDamageMultiplier } from "../type"; import { getTypeDamageMultiplier } from "../type";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { BooleanHolder, NumberHolder, isNullOrUndefined, toDmgValue, randSeedItem, randSeedInt, getEnumValues, toReadableString, type Constructor } from "#app/utils/common"; import { BooleanHolder, NumberHolder, isNullOrUndefined, toDmgValue, randSeedItem, randSeedInt, getEnumValues, toReadableString, type Constructor, randSeedFloat } from "#app/utils/common";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import type { ArenaTrapTag } from "../arena-tag"; import type { ArenaTrapTag } from "../arena-tag";
import { ArenaTagSide, WeakenMoveTypeTag } from "../arena-tag"; import { ArenaTagSide, WeakenMoveTypeTag } from "../arena-tag";
@ -81,7 +81,7 @@ import { TerrainType } from "../terrain";
import { ModifierPoolType } from "#app/modifier/modifier-type"; import { ModifierPoolType } from "#app/modifier/modifier-type";
import { Command } from "../../ui/command-ui-handler"; import { Command } from "../../ui/command-ui-handler";
import i18next from "i18next"; import i18next from "i18next";
import type { Localizable } from "#app/interfaces/locales"; import type { Localizable } from "#app/@types/locales";
import { getBerryEffectFunc } from "../berry"; import { getBerryEffectFunc } from "../berry";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type";
@ -837,7 +837,7 @@ export default class Move implements Localizable {
aura.applyPreAttack(source, null, simulated, target, this, [ power ]); aura.applyPreAttack(source, null, simulated, target, this, [ power ]);
} }
const alliedField: Pokemon[] = source instanceof PlayerPokemon ? globalScene.getPlayerField() : globalScene.getEnemyField(); const alliedField: Pokemon[] = source.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
alliedField.forEach(p => applyPreAttackAbAttrs(UserFieldMoveTypePowerBoostAbAttr, p, target, this, simulated, power)); alliedField.forEach(p => applyPreAttackAbAttrs(UserFieldMoveTypePowerBoostAbAttr, p, target, this, simulated, power));
power.value *= typeChangeMovePowerMultiplier.value; power.value *= typeChangeMovePowerMultiplier.value;
@ -1048,7 +1048,7 @@ function ChargeMove<TBase extends SubMove>(Base: TBase) {
* @param target the {@linkcode Pokemon} targeted by this move (optional) * @param target the {@linkcode Pokemon} targeted by this move (optional)
*/ */
showChargeText(user: Pokemon, target?: Pokemon): void { showChargeText(user: Pokemon, target?: Pokemon): void {
globalScene.queueMessage(this._chargeText globalScene.phaseManager.queueMessage(this._chargeText
.replace("{USER}", getPokemonNameWithAffix(user)) .replace("{USER}", getPokemonNameWithAffix(user))
.replace("{TARGET}", getPokemonNameWithAffix(target)) .replace("{TARGET}", getPokemonNameWithAffix(target))
); );
@ -1310,7 +1310,7 @@ export class MessageHeaderAttr extends MoveHeaderAttr {
: this.message(user, move); : this.message(user, move);
if (message) { if (message) {
globalScene.queueMessage(message); globalScene.phaseManager.queueMessage(message);
return true; return true;
} }
return false; return false;
@ -1363,7 +1363,7 @@ export class PreMoveMessageAttr extends MoveAttr {
? this.message as string ? this.message as string
: this.message(user, target, move); : this.message(user, target, move);
if (message) { if (message) {
globalScene.queueMessage(message, 500); globalScene.phaseManager.queueMessage(message, 500);
return true; return true;
} }
return false; return false;
@ -1620,14 +1620,14 @@ export class SurviveDamageAttr extends ModifiedDamageAttr {
export class SplashAttr extends MoveEffectAttr { export class SplashAttr extends MoveEffectAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
globalScene.queueMessage(i18next.t("moveTriggers:splash")); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:splash"));
return true; return true;
} }
} }
export class CelebrateAttr extends MoveEffectAttr { export class CelebrateAttr extends MoveEffectAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
globalScene.queueMessage(i18next.t("moveTriggers:celebrate", { playerName: loggedInUser?.username })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:celebrate", { playerName: loggedInUser?.username }));
return true; return true;
} }
} }
@ -1677,7 +1677,7 @@ export class RecoilAttr extends MoveEffectAttr {
} }
user.damageAndUpdate(recoilDamage, { result: HitResult.INDIRECT, ignoreSegments: true }); user.damageAndUpdate(recoilDamage, { result: HitResult.INDIRECT, ignoreSegments: true });
globalScene.queueMessage(i18next.t("moveTriggers:hitWithRecoil", { pokemonName: getPokemonNameWithAffix(user) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:hitWithRecoil", { pokemonName: getPokemonNameWithAffix(user) }));
user.turnData.damageTaken += recoilDamage; user.turnData.damageTaken += recoilDamage;
return true; return true;
@ -1789,7 +1789,7 @@ export class HalfSacrificialAttr extends MoveEffectAttr {
applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT, ignoreSegments: true }); user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT, ignoreSegments: true });
globalScene.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", { pokemonName: getPokemonNameWithAffix(user) })); // Queue recoil message globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", { pokemonName: getPokemonNameWithAffix(user) })); // Queue recoil message
} }
return true; return true;
} }
@ -1890,8 +1890,8 @@ export class HealAttr extends MoveEffectAttr {
* This heals the target and shows the appropriate message. * This heals the target and shows the appropriate message.
*/ */
addHealPhase(target: Pokemon, healRatio: number) { addHealPhase(target: Pokemon, healRatio: number) {
globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), globalScene.phaseManager.unshiftNew("PokemonHealPhase", target.getBattlerIndex(),
toDmgValue(target.getMaxHp() * healRatio), i18next.t("moveTriggers:healHp", { pokemonName: getPokemonNameWithAffix(target) }), true, !this.showAnim)); toDmgValue(target.getMaxHp() * healRatio), i18next.t("moveTriggers:healHp", { pokemonName: getPokemonNameWithAffix(target) }), true, !this.showAnim);
} }
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
@ -1934,7 +1934,7 @@ export class PartyStatusCureAttr extends MoveEffectAttr {
partyPokemon.forEach(p => this.cureStatus(p, user.id)); partyPokemon.forEach(p => this.cureStatus(p, user.id));
if (this.message) { if (this.message) {
globalScene.queueMessage(this.message); globalScene.phaseManager.queueMessage(this.message);
} }
return true; return true;
@ -1954,8 +1954,8 @@ export class PartyStatusCureAttr extends MoveEffectAttr {
pokemon.updateInfo(); pokemon.updateInfo();
} else { } else {
// TODO: Ability displays should be handled by the ability // TODO: Ability displays should be handled by the ability
globalScene.queueAbilityDisplay(pokemon, pokemon.getPassiveAbility()?.id === this.abilityCondition, true); globalScene.phaseManager.queueAbilityDisplay(pokemon, pokemon.getPassiveAbility()?.id === this.abilityCondition, true);
globalScene.queueAbilityDisplay(pokemon, pokemon.getPassiveAbility()?.id === this.abilityCondition, false); globalScene.phaseManager.queueAbilityDisplay(pokemon, pokemon.getPassiveAbility()?.id === this.abilityCondition, false);
} }
} }
} }
@ -2021,8 +2021,10 @@ export class SacrificialFullRestoreAttr extends SacrificialAttr {
const party = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); const party = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty();
const maxPartyMemberHp = party.map(p => p.getMaxHp()).reduce((maxHp: number, hp: number) => Math.max(hp, maxHp), 0); const maxPartyMemberHp = party.map(p => p.getMaxHp()).reduce((maxHp: number, hp: number) => Math.max(hp, maxHp), 0);
globalScene.pushPhase( const pm = globalScene.phaseManager;
new PokemonHealPhase(
pm.pushPhase(
pm.create("PokemonHealPhase",
user.getBattlerIndex(), user.getBattlerIndex(),
maxPartyMemberHp, maxPartyMemberHp,
i18next.t(this.moveMessage, { pokemonName: getPokemonNameWithAffix(user) }), i18next.t(this.moveMessage, { pokemonName: getPokemonNameWithAffix(user) }),
@ -2233,7 +2235,7 @@ export class HitHealAttr extends MoveEffectAttr {
message = ""; message = "";
} }
} }
globalScene.unshiftPhase(new PokemonHealPhase(user.getBattlerIndex(), healAmount, message, false, true)); globalScene.phaseManager.unshiftNew("PokemonHealPhase", user.getBattlerIndex(), healAmount, message, false, true);
return true; return true;
} }
@ -2547,8 +2549,8 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const rand = Phaser.Math.RND.realInRange(0, 1); const rand = randSeedFloat();
if (rand >= this.chance) { if (rand > this.chance) {
return false; return false;
} }
@ -2565,7 +2567,7 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
return false; return false;
} }
globalScene.queueMessage(i18next.t("moveTriggers:stoleItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:stoleItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name }));
return true; return true;
} }
@ -2643,9 +2645,9 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
globalScene.updateModifiers(target.isPlayer()); globalScene.updateModifiers(target.isPlayer());
if (this.berriesOnly) { if (this.berriesOnly) {
globalScene.queueMessage(i18next.t("moveTriggers:incineratedItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:incineratedItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name }));
} else { } else {
globalScene.queueMessage(i18next.t("moveTriggers:knockedOffItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:knockedOffItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name }));
} }
return true; return true;
@ -2777,7 +2779,7 @@ export class StealEatBerryAttr extends EatBerryAttr {
this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)]; this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)];
applyPostItemLostAbAttrs(PostItemLostAbAttr, target, false); applyPostItemLostAbAttrs(PostItemLostAbAttr, target, false);
const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name }); const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name });
globalScene.queueMessage(message); globalScene.phaseManager.queueMessage(message);
this.reduceBerryModifier(target); this.reduceBerryModifier(target);
this.eatBerry(user, target); this.eatBerry(user, target);
@ -2822,7 +2824,7 @@ export class HealStatusEffectAttr extends MoveEffectAttr {
const pokemon = this.selfTarget ? user : target; const pokemon = this.selfTarget ? user : target;
if (pokemon.status && this.effects.includes(pokemon.status.effect)) { if (pokemon.status && this.effects.includes(pokemon.status.effect)) {
globalScene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); globalScene.phaseManager.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)));
pokemon.resetStatus(); pokemon.resetStatus();
pokemon.updateInfo(); pokemon.updateInfo();
@ -3067,13 +3069,13 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr {
if (!virtual) { if (!virtual) {
overridden.value = true; overridden.value = true;
globalScene.unshiftPhase(new MoveAnimPhase(new MoveChargeAnim(this.chargeAnim, move.id, user))); globalScene.phaseManager.unshiftNew("MoveAnimPhase", new MoveChargeAnim(this.chargeAnim, move.id, user));
globalScene.queueMessage(this.chargeText.replace("{TARGET}", getPokemonNameWithAffix(target)).replace("{USER}", getPokemonNameWithAffix(user))); globalScene.phaseManager.queueMessage(this.chargeText.replace("{TARGET}", getPokemonNameWithAffix(target)).replace("{USER}", getPokemonNameWithAffix(user)));
user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER }); user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER });
const side = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const side = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
globalScene.arena.addTag(this.tagType, 3, move.id, user.id, side, false, target.getBattlerIndex()); globalScene.arena.addTag(this.tagType, 3, move.id, user.id, side, false, target.getBattlerIndex());
} else { } else {
globalScene.queueMessage(i18next.t("moveTriggers:tookMoveAttack", { pokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(target.id) ?? undefined), moveName: move.name })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:tookMoveAttack", { pokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(target.id) ?? undefined), moveName: move.name }));
} }
return true; return true;
@ -3103,29 +3105,29 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr {
override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (user.turnData.combiningPledge) { if (user.turnData.combiningPledge) {
// "The two moves have become one!\nIt's a combined move!" // "The two moves have become one!\nIt's a combined move!"
globalScene.queueMessage(i18next.t("moveTriggers:combiningPledge")); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:combiningPledge"));
return false; return false;
} }
const overridden = args[0] as BooleanHolder; const overridden = args[0] as BooleanHolder;
const allyMovePhase = globalScene.findPhase<MovePhase>((phase) => phase instanceof MovePhase && phase.pokemon.isPlayer() === user.isPlayer()); const allyMovePhase = globalScene.phaseManager.findPhase<MovePhase>((phase) => phase.is("MovePhase") && phase.pokemon.isPlayer() === user.isPlayer());
if (allyMovePhase) { if (allyMovePhase) {
const allyMove = allyMovePhase.move.getMove(); const allyMove = allyMovePhase.move.getMove();
if (allyMove !== move && allyMove.hasAttr(AwaitCombinedPledgeAttr)) { if (allyMove !== move && allyMove.hasAttr(AwaitCombinedPledgeAttr)) {
[ user, allyMovePhase.pokemon ].forEach((p) => p.turnData.combiningPledge = move.id); [ user, allyMovePhase.pokemon ].forEach((p) => p.turnData.combiningPledge = move.id);
// "{userPokemonName} is waiting for {allyPokemonName}'s move..." // "{userPokemonName} is waiting for {allyPokemonName}'s move..."
globalScene.queueMessage(i18next.t("moveTriggers:awaitingPledge", { globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:awaitingPledge", {
userPokemonName: getPokemonNameWithAffix(user), userPokemonName: getPokemonNameWithAffix(user),
allyPokemonName: getPokemonNameWithAffix(allyMovePhase.pokemon) allyPokemonName: getPokemonNameWithAffix(allyMovePhase.pokemon)
})); }));
// Move the ally's MovePhase (if needed) so that the ally moves next // Move the ally's MovePhase (if needed) so that the ally moves next
const allyMovePhaseIndex = globalScene.phaseQueue.indexOf(allyMovePhase); const allyMovePhaseIndex = globalScene.phaseManager.phaseQueue.indexOf(allyMovePhase);
const firstMovePhaseIndex = globalScene.phaseQueue.findIndex((phase) => phase instanceof MovePhase); const firstMovePhaseIndex = globalScene.phaseManager.phaseQueue.findIndex((phase) => phase.is("MovePhase"));
if (allyMovePhaseIndex !== firstMovePhaseIndex) { if (allyMovePhaseIndex !== firstMovePhaseIndex) {
globalScene.prependToPhase(globalScene.phaseQueue.splice(allyMovePhaseIndex, 1)[0], MovePhase); globalScene.phaseManager.prependToPhase(globalScene.phaseManager.phaseQueue.splice(allyMovePhaseIndex, 1)[0], "MovePhase");
} }
overridden.value = true; overridden.value = true;
@ -3207,7 +3209,7 @@ export class StatStageChangeAttr extends MoveEffectAttr {
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
if (moveChance < 0 || moveChance === 100 || user.randBattleSeedInt(100) < moveChance) { if (moveChance < 0 || moveChance === 100 || user.randBattleSeedInt(100) < moveChance) {
const stages = this.getLevels(user); const stages = this.getLevels(user);
globalScene.unshiftPhase(new StatStageChangePhase((this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, stages, this.showMessage)); globalScene.phaseManager.unshiftNew("StatStageChangePhase", (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, stages, this.showMessage);
return true; return true;
} }
@ -3432,7 +3434,7 @@ export class AcupressureStatStageChangeAttr extends MoveEffectAttr {
const randStats = BATTLE_STATS.filter((s) => target.getStatStage(s) < 6); const randStats = BATTLE_STATS.filter((s) => target.getStatStage(s) < 6);
if (randStats.length > 0) { if (randStats.length > 0) {
const boostStat = [ randStats[user.randBattleSeedInt(randStats.length)] ]; const boostStat = [ randStats[user.randBattleSeedInt(randStats.length)] ];
globalScene.unshiftPhase(new StatStageChangePhase(target.getBattlerIndex(), this.selfTarget, boostStat, 2)); globalScene.phaseManager.unshiftNew("StatStageChangePhase", target.getBattlerIndex(), this.selfTarget, boostStat, 2);
return true; return true;
} }
return false; return false;
@ -3510,7 +3512,7 @@ export class OrderUpStatBoostAttr extends MoveEffectAttr {
break; break;
} }
globalScene.unshiftPhase(new StatStageChangePhase(user.getBattlerIndex(), this.selfTarget, [ increasedStat ], 1)); globalScene.phaseManager.unshiftNew("StatStageChangePhase", user.getBattlerIndex(), this.selfTarget, [ increasedStat ], 1);
return true; return true;
} }
} }
@ -3533,7 +3535,7 @@ export class CopyStatsAttr extends MoveEffectAttr {
} }
target.updateInfo(); target.updateInfo();
user.updateInfo(); user.updateInfo();
globalScene.queueMessage(i18next.t("moveTriggers:copiedStatChanges", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:copiedStatChanges", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) }));
return true; return true;
} }
@ -3552,7 +3554,7 @@ export class InvertStatsAttr extends MoveEffectAttr {
target.updateInfo(); target.updateInfo();
user.updateInfo(); user.updateInfo();
globalScene.queueMessage(i18next.t("moveTriggers:invertStats", { pokemonName: getPokemonNameWithAffix(target) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:invertStats", { pokemonName: getPokemonNameWithAffix(target) }));
return true; return true;
} }
@ -3570,10 +3572,10 @@ export class ResetStatsAttr extends MoveEffectAttr {
// Target all pokemon on the field when Freezy Frost or Haze are used // Target all pokemon on the field when Freezy Frost or Haze are used
const activePokemon = globalScene.getField(true); const activePokemon = globalScene.getField(true);
activePokemon.forEach((p) => this.resetStats(p)); activePokemon.forEach((p) => this.resetStats(p));
globalScene.queueMessage(i18next.t("moveTriggers:statEliminated")); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:statEliminated"));
} else { // Affects only the single target when Clear Smog is used } else { // Affects only the single target when Clear Smog is used
this.resetStats(target); this.resetStats(target);
globalScene.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) }));
} }
return true; return true;
} }
@ -3623,9 +3625,9 @@ export class SwapStatStagesAttr extends MoveEffectAttr {
user.updateInfo(); user.updateInfo();
if (this.stats.length === 7) { if (this.stats.length === 7) {
globalScene.queueMessage(i18next.t("moveTriggers:switchedStatChanges", { pokemonName: getPokemonNameWithAffix(user) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:switchedStatChanges", { pokemonName: getPokemonNameWithAffix(user) }));
} else if (this.stats.length === 2) { } else if (this.stats.length === 2) {
globalScene.queueMessage(i18next.t("moveTriggers:switchedTwoStatChanges", { globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:switchedTwoStatChanges", {
pokemonName: getPokemonNameWithAffix(user), pokemonName: getPokemonNameWithAffix(user),
firstStat: i18next.t(getStatKey(this.stats[0])), firstStat: i18next.t(getStatKey(this.stats[0])),
secondStat: i18next.t(getStatKey(this.stats[1])) secondStat: i18next.t(getStatKey(this.stats[1]))
@ -4125,7 +4127,7 @@ export class FriendshipPowerAttr extends VariablePowerAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const power = args[0] as NumberHolder; const power = args[0] as NumberHolder;
const friendshipPower = Math.floor(Math.min(user instanceof PlayerPokemon ? user.friendship : user.species.baseFriendship, 255) / 2.5); const friendshipPower = Math.floor(Math.min(user.isPlayer() ? user.friendship : user.species.baseFriendship, 255) / 2.5);
power.value = Math.max(!this.invert ? friendshipPower : 102 - friendshipPower, 1); power.value = Math.max(!this.invert ? friendshipPower : 102 - friendshipPower, 1);
return true; return true;
@ -4227,8 +4229,8 @@ export class PresentPowerAttr extends VariablePowerAttr {
// If this move is multi-hit, disable all other hits // If this move is multi-hit, disable all other hits
user.turnData.hitCount = 1; user.turnData.hitCount = 1;
user.turnData.hitsLeft = 1; user.turnData.hitsLeft = 1;
globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), globalScene.phaseManager.unshiftNew("PokemonHealPhase", target.getBattlerIndex(),
toDmgValue(target.getMaxHp() / 4), i18next.t("moveTriggers:regainedHealth", { pokemonName: getPokemonNameWithAffix(target) }), true)); toDmgValue(target.getMaxHp() / 4), i18next.t("moveTriggers:regainedHealth", { pokemonName: getPokemonNameWithAffix(target) }), true);
} }
return true; return true;
@ -4476,8 +4478,8 @@ export class CueNextRoundAttr extends MoveEffectAttr {
} }
override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean { override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean {
const nextRoundPhase = globalScene.findPhase<MovePhase>(phase => const nextRoundPhase = globalScene.phaseManager.findPhase<MovePhase>(phase =>
phase instanceof MovePhase && phase.move.moveId === MoveId.ROUND phase.is("MovePhase") && phase.move.moveId === MoveId.ROUND
); );
if (!nextRoundPhase) { if (!nextRoundPhase) {
@ -4485,10 +4487,10 @@ export class CueNextRoundAttr extends MoveEffectAttr {
} }
// Update the phase queue so that the next Pokemon using Round moves next // Update the phase queue so that the next Pokemon using Round moves next
const nextRoundIndex = globalScene.phaseQueue.indexOf(nextRoundPhase); const nextRoundIndex = globalScene.phaseManager.phaseQueue.indexOf(nextRoundPhase);
const nextMoveIndex = globalScene.phaseQueue.findIndex(phase => phase instanceof MovePhase); const nextMoveIndex = globalScene.phaseManager.phaseQueue.findIndex(phase => phase.is("MovePhase"));
if (nextRoundIndex !== nextMoveIndex) { if (nextRoundIndex !== nextMoveIndex) {
globalScene.prependToPhase(globalScene.phaseQueue.splice(nextRoundIndex, 1)[0], MovePhase); globalScene.phaseManager.prependToPhase(globalScene.phaseManager.phaseQueue.splice(nextRoundIndex, 1)[0], "MovePhase");
} }
// Mark the corresponding Pokemon as having "joined the Round" (for doubling power later) // Mark the corresponding Pokemon as having "joined the Round" (for doubling power later)
@ -4546,14 +4548,14 @@ export class SpectralThiefAttr extends StatChangeBeforeDmgCalcAttr {
*/ */
const availableToSteal = Math.min(statStageValueTarget, 6 - statStageValueUser); const availableToSteal = Math.min(statStageValueTarget, 6 - statStageValueUser);
globalScene.unshiftPhase(new StatStageChangePhase(user.getBattlerIndex(), this.selfTarget, [ s ], availableToSteal)); globalScene.phaseManager.unshiftNew("StatStageChangePhase", user.getBattlerIndex(), this.selfTarget, [ s ], availableToSteal);
target.setStatStage(s, statStageValueTarget - availableToSteal); target.setStatStage(s, statStageValueTarget - availableToSteal);
} }
} }
target.updateInfo(); target.updateInfo();
user.updateInfo(); user.updateInfo();
globalScene.queueMessage(i18next.t("moveTriggers:stealPositiveStats", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:stealPositiveStats", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) }));
return true; return true;
} }
@ -5368,7 +5370,7 @@ const crashDamageFunc = (user: Pokemon, move: Move) => {
} }
user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT }); user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT });
globalScene.queueMessage(i18next.t("moveTriggers:keptGoingAndCrashed", { pokemonName: getPokemonNameWithAffix(user) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:keptGoingAndCrashed", { pokemonName: getPokemonNameWithAffix(user) }));
user.turnData.damageTaken += toDmgValue(user.getMaxHp() / 2); user.turnData.damageTaken += toDmgValue(user.getMaxHp() / 2);
return true; return true;
@ -5472,13 +5474,6 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
this.failOnOverlap = !!failOnOverlap; this.failOnOverlap = !!failOnOverlap;
} }
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!super.canApply(user, target, move, args)) {
return false;
}
return true;
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!super.apply(user, target, move, args)) { if (!super.apply(user, target, move, args)) {
return false; return false;
@ -5588,7 +5583,7 @@ export class FallDownAttr extends AddBattlerTagAttr {
*/ */
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!target.isGrounded()) { if (!target.isGrounded()) {
globalScene.queueMessage(i18next.t("moveTriggers:fallDown", { targetPokemonName: getPokemonNameWithAffix(target) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:fallDown", { targetPokemonName: getPokemonNameWithAffix(target) }));
} }
return super.apply(user, target, move, args); return super.apply(user, target, move, args);
} }
@ -5672,12 +5667,12 @@ export class CurseAttr extends MoveEffectAttr {
apply(user: Pokemon, target: Pokemon, move:Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move:Move, args: any[]): boolean {
if (user.getTypes(true).includes(PokemonType.GHOST)) { if (user.getTypes(true).includes(PokemonType.GHOST)) {
if (target.getTag(BattlerTagType.CURSED)) { if (target.getTag(BattlerTagType.CURSED)) {
globalScene.queueMessage(i18next.t("battle:attackFailed")); globalScene.phaseManager.queueMessage(i18next.t("battle:attackFailed"));
return false; return false;
} }
const curseRecoilDamage = Math.max(1, Math.floor(user.getMaxHp() / 2)); const curseRecoilDamage = Math.max(1, Math.floor(user.getMaxHp() / 2));
user.damageAndUpdate(curseRecoilDamage, { result: HitResult.INDIRECT, ignoreSegments: true }); user.damageAndUpdate(curseRecoilDamage, { result: HitResult.INDIRECT, ignoreSegments: true });
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("battlerTags:cursedOnAdd", { i18next.t("battlerTags:cursedOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(user), pokemonNameWithAffix: getPokemonNameWithAffix(user),
pokemonName: getPokemonNameWithAffix(target) pokemonName: getPokemonNameWithAffix(target)
@ -5687,8 +5682,8 @@ export class CurseAttr extends MoveEffectAttr {
target.addTag(BattlerTagType.CURSED, 0, move.id, user.id); target.addTag(BattlerTagType.CURSED, 0, move.id, user.id);
return true; return true;
} else { } else {
globalScene.unshiftPhase(new StatStageChangePhase(user.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF ], 1)); globalScene.phaseManager.unshiftNew("StatStageChangePhase", user.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF ], 1);
globalScene.unshiftPhase(new StatStageChangePhase(user.getBattlerIndex(), true, [ Stat.SPD ], -1)); globalScene.phaseManager.unshiftNew("StatStageChangePhase", user.getBattlerIndex(), true, [ Stat.SPD ], -1);
return true; return true;
} }
} }
@ -5752,7 +5747,7 @@ export class ConfuseAttr extends AddBattlerTagAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!this.selfTarget && target.isSafeguarded(user)) { if (!this.selfTarget && target.isSafeguarded(user)) {
if (move.category === MoveCategory.STATUS) { if (move.category === MoveCategory.STATUS) {
globalScene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) }));
} }
return false; return false;
} }
@ -5809,7 +5804,7 @@ export class IgnoreAccuracyAttr extends AddBattlerTagAttr {
return false; return false;
} }
globalScene.queueMessage(i18next.t("moveTriggers:tookAimAtTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:tookAimAtTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) }));
return true; return true;
} }
@ -5825,7 +5820,7 @@ export class FaintCountdownAttr extends AddBattlerTagAttr {
return false; return false;
} }
globalScene.queueMessage(i18next.t("moveTriggers:faintCountdown", { pokemonName: getPokemonNameWithAffix(target), turnCount: this.turnCountMin - 1 })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:faintCountdown", { pokemonName: getPokemonNameWithAffix(target), turnCount: this.turnCountMin - 1 }));
return true; return true;
} }
@ -6109,7 +6104,7 @@ export class SwapArenaTagsAttr extends MoveEffectAttr {
} }
globalScene.queueMessage(i18next.t("moveTriggers:swapArenaTags", { pokemonName: getPokemonNameWithAffix(user) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:swapArenaTags", { pokemonName: getPokemonNameWithAffix(user) }));
return true; return true;
} }
} }
@ -6156,14 +6151,14 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
* @param target {@linkcode Pokemon} target of this move * @param target {@linkcode Pokemon} target of this move
* @param move {@linkcode Move} being used * @param move {@linkcode Move} being used
* @param args N/A * @param args N/A
* @returns Promise, true if function succeeds. * @returns `true` if function succeeds.
*/ */
override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
// If user is player, checks if the user has fainted pokemon // If user is player, checks if the user has fainted pokemon
if (user instanceof PlayerPokemon) { if (user.isPlayer()) {
globalScene.unshiftPhase(new RevivalBlessingPhase(user)); globalScene.phaseManager.unshiftNew("RevivalBlessingPhase", user);
return true; return true;
} else if (user instanceof EnemyPokemon && user.hasTrainer() && globalScene.getEnemyParty().findIndex((p) => p.isFainted() && !p.isBoss()) > -1) { } else if (user.isEnemy() && user.hasTrainer() && globalScene.getEnemyParty().findIndex((p) => p.isFainted() && !p.isBoss()) > -1) {
// If used by an enemy trainer with at least one fainted non-boss Pokemon, this // If used by an enemy trainer with at least one fainted non-boss Pokemon, this
// revives one of said Pokemon selected at random. // revives one of said Pokemon selected at random.
const faintedPokemon = globalScene.getEnemyParty().filter((p) => p.isFainted() && !p.isBoss()); const faintedPokemon = globalScene.getEnemyParty().filter((p) => p.isFainted() && !p.isBoss());
@ -6171,20 +6166,20 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
const slotIndex = globalScene.getEnemyParty().findIndex((p) => pokemon.id === p.id); const slotIndex = globalScene.getEnemyParty().findIndex((p) => pokemon.id === p.id);
pokemon.resetStatus(true, false, false, true); pokemon.resetStatus(true, false, false, true);
pokemon.heal(Math.min(toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp())); pokemon.heal(Math.min(toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp()));
globalScene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: getPokemonNameWithAffix(pokemon) }), 0, true); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: getPokemonNameWithAffix(pokemon) }), 0, true);
const allyPokemon = user.getAlly(); const allyPokemon = user.getAlly();
if (globalScene.currentBattle.double && globalScene.getEnemyParty().length > 1 && !isNullOrUndefined(allyPokemon)) { if (globalScene.currentBattle.double && globalScene.getEnemyParty().length > 1 && !isNullOrUndefined(allyPokemon)) {
// Handle cases where revived pokemon needs to get switched in on same turn // Handle cases where revived pokemon needs to get switched in on same turn
if (allyPokemon.isFainted() || allyPokemon === pokemon) { if (allyPokemon.isFainted() || allyPokemon === pokemon) {
// Enemy switch phase should be removed and replaced with the revived pkmn switching in // Enemy switch phase should be removed and replaced with the revived pkmn switching in
globalScene.tryRemovePhase((phase: SwitchSummonPhase) => phase instanceof SwitchSummonPhase && phase.getPokemon() === pokemon); globalScene.phaseManager.tryRemovePhase((phase: SwitchSummonPhase) => phase.is("SwitchSummonPhase") && phase.getPokemon() === pokemon);
// If the pokemon being revived was alive earlier in the turn, cancel its move // If the pokemon being revived was alive earlier in the turn, cancel its move
// (revived pokemon can't move in the turn they're brought back) // (revived pokemon can't move in the turn they're brought back)
globalScene.findPhase((phase: MovePhase) => phase.pokemon === pokemon)?.cancel(); globalScene.phaseManager.findPhase((phase: MovePhase) => phase.pokemon === pokemon)?.cancel();
if (user.fieldPosition === FieldPosition.CENTER) { if (user.fieldPosition === FieldPosition.CENTER) {
user.setFieldPosition(FieldPosition.LEFT); user.setFieldPosition(FieldPosition.LEFT);
} }
globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, false)); globalScene.phaseManager.unshiftNew("SwitchSummonPhase", SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, false);
} }
} }
return true; return true;
@ -6194,10 +6189,8 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
getCondition(): MoveConditionFunc { getCondition(): MoveConditionFunc {
return (user, target, move) => return (user, target, move) =>
(user instanceof PlayerPokemon && globalScene.getPlayerParty().some((p) => p.isFainted())) || user.hasTrainer() &&
(user instanceof EnemyPokemon && (user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).some((p: Pokemon) => p.isFainted() && !p.isBoss());
user.hasTrainer() &&
globalScene.getEnemyParty().some((p) => p.isFainted() && !p.isBoss()));
} }
override getUserBenefitScore(user: Pokemon, _target: Pokemon, _move: Move): number { override getUserBenefitScore(user: Pokemon, _target: Pokemon, _move: Move): number {
@ -6235,7 +6228,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
// (e.g. when it uses Flip Turn), make it spit out the Tatsugiri before switching out. // (e.g. when it uses Flip Turn), make it spit out the Tatsugiri before switching out.
switchOutTarget.lapseTag(BattlerTagType.COMMANDED); switchOutTarget.lapseTag(BattlerTagType.COMMANDED);
if (switchOutTarget instanceof PlayerPokemon) { if (switchOutTarget.isPlayer()) {
/** /**
* Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch * Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch
* If it did, the user of U-turn or Volt Switch will not be switched out. * If it did, the user of U-turn or Volt Switch will not be switched out.
@ -6264,26 +6257,23 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
if (this.switchType === SwitchType.FORCE_SWITCH) { if (this.switchType === SwitchType.FORCE_SWITCH) {
switchOutTarget.leaveField(true); switchOutTarget.leaveField(true);
const slotIndex = eligibleNewIndices[user.randBattleSeedInt(eligibleNewIndices.length)]; const slotIndex = eligibleNewIndices[user.randBattleSeedInt(eligibleNewIndices.length)];
globalScene.prependToPhase( globalScene.phaseManager.prependNewToPhase(
new SwitchSummonPhase( "MoveEndPhase",
this.switchType, "SwitchSummonPhase",
switchOutTarget.getFieldIndex(), this.switchType,
slotIndex, switchOutTarget.getFieldIndex(),
false, slotIndex,
true false,
), true
MoveEndPhase
); );
} else { } else {
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
globalScene.prependToPhase( globalScene.phaseManager.prependNewToPhase("MoveEndPhase",
new SwitchPhase( "SwitchPhase",
this.switchType, this.switchType,
switchOutTarget.getFieldIndex(), switchOutTarget.getFieldIndex(),
true, true,
true true
),
MoveEndPhase
); );
return true; return true;
} }
@ -6307,27 +6297,23 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
if (this.switchType === SwitchType.FORCE_SWITCH) { if (this.switchType === SwitchType.FORCE_SWITCH) {
switchOutTarget.leaveField(true); switchOutTarget.leaveField(true);
const slotIndex = eligibleNewIndices[user.randBattleSeedInt(eligibleNewIndices.length)]; const slotIndex = eligibleNewIndices[user.randBattleSeedInt(eligibleNewIndices.length)];
globalScene.prependToPhase( globalScene.phaseManager.prependNewToPhase("MoveEndPhase",
new SwitchSummonPhase( "SwitchSummonPhase",
this.switchType, this.switchType,
switchOutTarget.getFieldIndex(), switchOutTarget.getFieldIndex(),
slotIndex, slotIndex,
false, false,
false false
),
MoveEndPhase
); );
} else { } else {
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
globalScene.prependToPhase( globalScene.phaseManager.prependNewToPhase("MoveEndPhase",
new SwitchSummonPhase( "SwitchSummonPhase",
this.switchType, this.switchType,
switchOutTarget.getFieldIndex(), switchOutTarget.getFieldIndex(),
(globalScene.currentBattle.trainer ? globalScene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0), (globalScene.currentBattle.trainer ? globalScene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0),
false, false,
false false
),
MoveEndPhase
); );
} }
} }
@ -6348,7 +6334,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
if (switchOutTarget.hp > 0) { if (switchOutTarget.hp > 0) {
switchOutTarget.leaveField(false); switchOutTarget.leaveField(false);
globalScene.queueMessage(i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }), null, true, 500); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }), null, true, 500);
// in double battles redirect potential moves off fled pokemon // in double battles redirect potential moves off fled pokemon
if (globalScene.currentBattle.double && !isNullOrUndefined(allyPokemon)) { if (globalScene.currentBattle.double && !isNullOrUndefined(allyPokemon)) {
@ -6360,13 +6346,13 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
globalScene.clearEnemyHeldItemModifiers(switchOutTarget); globalScene.clearEnemyHeldItemModifiers(switchOutTarget);
if (!allyPokemon?.isActive(true) && switchOutTarget.hp) { if (!allyPokemon?.isActive(true) && switchOutTarget.hp) {
globalScene.pushPhase(new BattleEndPhase(false)); globalScene.phaseManager.pushNew("BattleEndPhase", false);
if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) { if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) {
globalScene.pushPhase(new SelectBiomePhase()); globalScene.phaseManager.pushNew("SelectBiomePhase");
} }
globalScene.pushPhase(new NewBattlePhase()); globalScene.phaseManager.pushNew("NewBattlePhase");
} }
} }
@ -6389,7 +6375,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
getSwitchOutCondition(): MoveConditionFunc { getSwitchOutCondition(): MoveConditionFunc {
return (user, target, move) => { return (user, target, move) => {
const switchOutTarget = (this.selfSwitch ? user : target); const switchOutTarget = (this.selfSwitch ? user : target);
const player = switchOutTarget instanceof PlayerPokemon; const player = switchOutTarget.isPlayer();
const forceSwitchAttr = move.getAttrs(ForceSwitchOutAttr).find(attr => attr.switchType === SwitchType.FORCE_SWITCH); const forceSwitchAttr = move.getAttrs(ForceSwitchOutAttr).find(attr => attr.switchType === SwitchType.FORCE_SWITCH);
if (!this.selfSwitch) { if (!this.selfSwitch) {
@ -6534,7 +6520,7 @@ export class CopyTypeAttr extends MoveEffectAttr {
user.summonData.types = targetTypes; user.summonData.types = targetTypes;
user.updateInfo(); user.updateInfo();
globalScene.queueMessage(i18next.t("moveTriggers:copyType", { pokemonName: getPokemonNameWithAffix(user), targetPokemonName: getPokemonNameWithAffix(target) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:copyType", { pokemonName: getPokemonNameWithAffix(user), targetPokemonName: getPokemonNameWithAffix(target) }));
return true; return true;
} }
@ -6565,7 +6551,7 @@ export class CopyBiomeTypeAttr extends MoveEffectAttr {
user.summonData.types = [ typeChange ]; user.summonData.types = [ typeChange ];
user.updateInfo(); user.updateInfo();
globalScene.queueMessage(i18next.t("moveTriggers:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), typeName: i18next.t(`pokemonInfo:Type.${PokemonType[typeChange]}`) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), typeName: i18next.t(`pokemonInfo:Type.${PokemonType[typeChange]}`) }));
return true; return true;
} }
@ -6670,7 +6656,7 @@ export class ChangeTypeAttr extends MoveEffectAttr {
target.summonData.types = [ this.type ]; target.summonData.types = [ this.type ];
target.updateInfo(); target.updateInfo();
globalScene.queueMessage(i18next.t("moveTriggers:transformedIntoType", { pokemonName: getPokemonNameWithAffix(target), typeName: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:transformedIntoType", { pokemonName: getPokemonNameWithAffix(target), typeName: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`) }));
return true; return true;
} }
@ -6693,7 +6679,7 @@ export class AddTypeAttr extends MoveEffectAttr {
target.summonData.addedType = this.type; target.summonData.addedType = this.type;
target.updateInfo(); target.updateInfo();
globalScene.queueMessage(i18next.t("moveTriggers:addType", { typeName: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`), pokemonName: getPokemonNameWithAffix(target) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:addType", { typeName: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`), pokemonName: getPokemonNameWithAffix(target) }));
return true; return true;
} }
@ -6715,7 +6701,7 @@ export class FirstMoveTypeAttr extends MoveEffectAttr {
const firstMoveType = target.getMoveset()[0].getMove().type; const firstMoveType = target.getMoveset()[0].getMove().type;
user.summonData.types = [ firstMoveType ]; user.summonData.types = [ firstMoveType ];
globalScene.queueMessage(i18next.t("battle:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), type: i18next.t(`pokemonInfo:Type.${PokemonType[firstMoveType]}`) })); globalScene.phaseManager.queueMessage(i18next.t("battle:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), type: i18next.t(`pokemonInfo:Type.${PokemonType[firstMoveType]}`) }));
return true; return true;
} }
@ -6734,7 +6720,7 @@ class CallMoveAttr extends OverrideMoveEffectAttr {
const replaceMoveTarget = move.moveTarget === MoveTarget.NEAR_OTHER ? MoveTarget.NEAR_ENEMY : undefined; const replaceMoveTarget = move.moveTarget === MoveTarget.NEAR_OTHER ? MoveTarget.NEAR_ENEMY : undefined;
const moveTargets = getMoveTargets(user, move.id, replaceMoveTarget); const moveTargets = getMoveTargets(user, move.id, replaceMoveTarget);
if (moveTargets.targets.length === 0) { if (moveTargets.targets.length === 0) {
globalScene.queueMessage(i18next.t("battle:attackFailed")); globalScene.phaseManager.queueMessage(i18next.t("battle:attackFailed"));
console.log("CallMoveAttr failed due to no targets."); console.log("CallMoveAttr failed due to no targets.");
return false; return false;
} }
@ -6742,8 +6728,8 @@ class CallMoveAttr extends OverrideMoveEffectAttr {
? moveTargets.targets ? moveTargets.targets
: [ this.hasTarget ? target.getBattlerIndex() : moveTargets.targets[user.randBattleSeedInt(moveTargets.targets.length)] ]; // account for Mirror Move having a target already : [ this.hasTarget ? target.getBattlerIndex() : moveTargets.targets[user.randBattleSeedInt(moveTargets.targets.length)] ]; // account for Mirror Move having a target already
user.getMoveQueue().push({ move: move.id, targets: targets, virtual: true, ignorePP: true }); user.getMoveQueue().push({ move: move.id, targets: targets, virtual: true, ignorePP: true });
globalScene.unshiftPhase(new LoadMoveAnimPhase(move.id)); globalScene.phaseManager.unshiftNew("LoadMoveAnimPhase", move.id);
globalScene.unshiftPhase(new MovePhase(user, targets, new PokemonMove(move.id, 0, 0, true), true, true)); globalScene.phaseManager.unshiftNew("MovePhase", user, targets, new PokemonMove(move.id, 0, 0, true), true, true);
return true; return true;
} }
} }
@ -6971,8 +6957,8 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr {
} }
user.getMoveQueue().push({ move: moveId, targets: [ target.getBattlerIndex() ], ignorePP: true }); user.getMoveQueue().push({ move: moveId, targets: [ target.getBattlerIndex() ], ignorePP: true });
globalScene.unshiftPhase(new LoadMoveAnimPhase(moveId)); globalScene.phaseManager.unshiftNew("LoadMoveAnimPhase", moveId);
globalScene.unshiftPhase(new MovePhase(user, [ target.getBattlerIndex() ], new PokemonMove(moveId, 0, 0, true), true)); globalScene.phaseManager.unshiftNew("MovePhase", user, [ target.getBattlerIndex() ], new PokemonMove(moveId, 0, 0, true), true);
return true; return true;
} }
} }
@ -7053,13 +7039,13 @@ export class RepeatMoveAttr extends MoveEffectAttr {
} }
} }
globalScene.queueMessage(i18next.t("moveTriggers:instructingMove", { globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:instructingMove", {
userPokemonName: getPokemonNameWithAffix(user), userPokemonName: getPokemonNameWithAffix(user),
targetPokemonName: getPokemonNameWithAffix(target) targetPokemonName: getPokemonNameWithAffix(target)
})); }));
target.getMoveQueue().unshift({ move: lastMove.move, targets: moveTargets, ignorePP: false }); target.getMoveQueue().unshift({ move: lastMove.move, targets: moveTargets, ignorePP: false });
target.turnData.extraTurns++; target.turnData.extraTurns++;
globalScene.appendToPhase(new MovePhase(target, moveTargets, movesetMove), MoveEndPhase); globalScene.phaseManager.appendNewToPhase("MoveEndPhase", "MovePhase", target, moveTargets, movesetMove);
return true; return true;
} }
@ -7174,7 +7160,7 @@ export class ReducePpMoveAttr extends MoveEffectAttr {
const message = i18next.t("battle:ppReduced", { targetName: getPokemonNameWithAffix(target), moveName: movesetMove.getName(), reduction: (movesetMove.ppUsed) - lastPpUsed }); const message = i18next.t("battle:ppReduced", { targetName: getPokemonNameWithAffix(target), moveName: movesetMove.getName(), reduction: (movesetMove.ppUsed) - lastPpUsed });
globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(target.id, movesetMove.getMove(), movesetMove.ppUsed)); globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(target.id, movesetMove.getMove(), movesetMove.ppUsed));
globalScene.queueMessage(message); globalScene.phaseManager.queueMessage(message);
return true; return true;
} }
@ -7285,7 +7271,7 @@ export class MovesetCopyMoveAttr extends OverrideMoveEffectAttr {
user.summonData.moveset = user.getMoveset().slice(0); user.summonData.moveset = user.getMoveset().slice(0);
user.summonData.moveset[thisMoveIndex] = new PokemonMove(copiedMove.id, 0, 0); user.summonData.moveset[thisMoveIndex] = new PokemonMove(copiedMove.id, 0, 0);
globalScene.queueMessage(i18next.t("moveTriggers:copiedMove", { pokemonName: getPokemonNameWithAffix(user), moveName: copiedMove.name })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:copiedMove", { pokemonName: getPokemonNameWithAffix(user), moveName: copiedMove.name }));
return true; return true;
} }
@ -7335,7 +7321,7 @@ export class SketchAttr extends MoveEffectAttr {
user.setMove(sketchIndex, sketchedMove.id); user.setMove(sketchIndex, sketchedMove.id);
globalScene.queueMessage(i18next.t("moveTriggers:sketchedMove", { pokemonName: getPokemonNameWithAffix(user), moveName: sketchedMove.name })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:sketchedMove", { pokemonName: getPokemonNameWithAffix(user), moveName: sketchedMove.name }));
return true; return true;
} }
@ -7367,11 +7353,7 @@ export class SketchAttr extends MoveEffectAttr {
return false; return false;
} }
if (user.getMoveset().find(m => m.moveId === targetMove.move)) { return !user.getMoveset().some(m => m.moveId === targetMove.move);
return false;
}
return true;
}; };
} }
} }
@ -7394,9 +7376,9 @@ export class AbilityChangeAttr extends MoveEffectAttr {
globalScene.triggerPokemonFormChange(moveTarget, SpeciesFormChangeRevertWeatherFormTrigger); globalScene.triggerPokemonFormChange(moveTarget, SpeciesFormChangeRevertWeatherFormTrigger);
if (moveTarget.breakIllusion()) { if (moveTarget.breakIllusion()) {
globalScene.queueMessage(i18next.t("abilityTriggers:illusionBreak", { pokemonName: getPokemonNameWithAffix(moveTarget) })); globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:illusionBreak", { pokemonName: getPokemonNameWithAffix(moveTarget) }));
} }
globalScene.queueMessage(i18next.t("moveTriggers:acquiredAbility", { pokemonName: getPokemonNameWithAffix(moveTarget), abilityName: allAbilities[this.ability].name })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:acquiredAbility", { pokemonName: getPokemonNameWithAffix(moveTarget), abilityName: allAbilities[this.ability].name }));
moveTarget.setTempAbility(allAbilities[this.ability]); moveTarget.setTempAbility(allAbilities[this.ability]);
globalScene.triggerPokemonFormChange(moveTarget, SpeciesFormChangeRevertWeatherFormTrigger); globalScene.triggerPokemonFormChange(moveTarget, SpeciesFormChangeRevertWeatherFormTrigger);
return true; return true;
@ -7421,13 +7403,13 @@ export class AbilityCopyAttr extends MoveEffectAttr {
return false; return false;
} }
globalScene.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name }));
user.setTempAbility(target.getAbility()); user.setTempAbility(target.getAbility());
const ally = user.getAlly(); const ally = user.getAlly();
if (this.copyToPartner && globalScene.currentBattle?.double && !isNullOrUndefined(ally) && ally.hp) { // TODO is this the best way to check that the ally is active? if (this.copyToPartner && globalScene.currentBattle?.double && !isNullOrUndefined(ally) && ally.hp) { // TODO is this the best way to check that the ally is active?
globalScene.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", { pokemonName: getPokemonNameWithAffix(ally), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", { pokemonName: getPokemonNameWithAffix(ally), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name }));
ally.setTempAbility(target.getAbility()); ally.setTempAbility(target.getAbility());
} }
@ -7460,7 +7442,7 @@ export class AbilityGiveAttr extends MoveEffectAttr {
return false; return false;
} }
globalScene.queueMessage(i18next.t("moveTriggers:acquiredAbility", { pokemonName: getPokemonNameWithAffix(target), abilityName: allAbilities[user.getAbility().id].name })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:acquiredAbility", { pokemonName: getPokemonNameWithAffix(target), abilityName: allAbilities[user.getAbility().id].name }));
target.setTempAbility(user.getAbility()); target.setTempAbility(user.getAbility());
@ -7480,7 +7462,7 @@ export class SwitchAbilitiesAttr extends MoveEffectAttr {
const tempAbility = user.getAbility(); const tempAbility = user.getAbility();
globalScene.queueMessage(i18next.t("moveTriggers:swappedAbilitiesWithTarget", { pokemonName: getPokemonNameWithAffix(user) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:swappedAbilitiesWithTarget", { pokemonName: getPokemonNameWithAffix(user) }));
user.setTempAbility(target.getAbility()); user.setTempAbility(target.getAbility());
target.setTempAbility(tempAbility); target.setTempAbility(tempAbility);
@ -7510,7 +7492,7 @@ export class SuppressAbilitiesAttr extends MoveEffectAttr {
return false; return false;
} }
globalScene.queueMessage(i18next.t("moveTriggers:suppressAbilities", { pokemonName: getPokemonNameWithAffix(target) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:suppressAbilities", { pokemonName: getPokemonNameWithAffix(target) }));
target.suppressAbility(); target.suppressAbility();
@ -7563,9 +7545,9 @@ export class TransformAttr extends MoveEffectAttr {
return false; return false;
} }
globalScene.unshiftPhase(new PokemonTransformPhase(user.getBattlerIndex(), target.getBattlerIndex())); globalScene.phaseManager.unshiftNew("PokemonTransformPhase", user.getBattlerIndex(), target.getBattlerIndex());
globalScene.queueMessage(i18next.t("moveTriggers:transformedIntoTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:transformedIntoTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) }));
return true; return true;
} }
@ -7602,7 +7584,7 @@ export class SwapStatAttr extends MoveEffectAttr {
user.setStat(this.stat, target.getStat(this.stat, false), false); user.setStat(this.stat, target.getStat(this.stat, false), false);
target.setStat(this.stat, temp, false); target.setStat(this.stat, temp, false);
globalScene.queueMessage(i18next.t("moveTriggers:switchedStat", { globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:switchedStat", {
pokemonName: getPokemonNameWithAffix(user), pokemonName: getPokemonNameWithAffix(user),
stat: i18next.t(getStatKey(this.stat)), stat: i18next.t(getStatKey(this.stat)),
})); }));
@ -7648,7 +7630,7 @@ export class ShiftStatAttr extends MoveEffectAttr {
user.setStat(this.statToSwitch, secondStat, false); user.setStat(this.statToSwitch, secondStat, false);
user.setStat(this.statToSwitchWith, firstStat, false); user.setStat(this.statToSwitchWith, firstStat, false);
globalScene.queueMessage(i18next.t("moveTriggers:shiftedStats", { globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:shiftedStats", {
pokemonName: getPokemonNameWithAffix(user), pokemonName: getPokemonNameWithAffix(user),
statToSwitch: i18next.t(getStatKey(this.statToSwitch)), statToSwitch: i18next.t(getStatKey(this.statToSwitch)),
statToSwitchWith: i18next.t(getStatKey(this.statToSwitchWith)) statToSwitchWith: i18next.t(getStatKey(this.statToSwitchWith))
@ -7707,7 +7689,7 @@ export class AverageStatsAttr extends MoveEffectAttr {
target.setStat(s, avg, false); target.setStat(s, avg, false);
} }
globalScene.queueMessage(i18next.t(this.msgKey, { pokemonName: getPokemonNameWithAffix(user) })); globalScene.phaseManager.queueMessage(i18next.t(this.msgKey, { pokemonName: getPokemonNameWithAffix(user) }));
return true; return true;
} }
@ -7722,7 +7704,7 @@ export class MoneyAttr extends MoveEffectAttr {
apply(user: Pokemon, target: Pokemon, move: Move): boolean { apply(user: Pokemon, target: Pokemon, move: Move): boolean {
globalScene.currentBattle.moneyScattered += globalScene.getWaveMoneyAmount(0.2); globalScene.currentBattle.moneyScattered += globalScene.getWaveMoneyAmount(0.2);
globalScene.queueMessage(i18next.t("moveTriggers:coinsScatteredEverywhere")); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:coinsScatteredEverywhere"));
return true; return true;
} }
} }
@ -7746,7 +7728,7 @@ export class DestinyBondAttr extends MoveEffectAttr {
* @returns true * @returns true
*/ */
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
globalScene.queueMessage(`${i18next.t("moveTriggers:tryingToTakeFoeDown", { pokemonName: getPokemonNameWithAffix(user) })}`); globalScene.phaseManager.queueMessage(`${i18next.t("moveTriggers:tryingToTakeFoeDown", { pokemonName: getPokemonNameWithAffix(user) })}`);
user.addTag(BattlerTagType.DESTINY_BOND, undefined, move.id, user.id); user.addTag(BattlerTagType.DESTINY_BOND, undefined, move.id, user.id);
return true; return true;
} }
@ -7860,12 +7842,12 @@ export class AfterYouAttr extends MoveEffectAttr {
* @returns true * @returns true
*/ */
override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
globalScene.queueMessage(i18next.t("moveTriggers:afterYou", { targetName: getPokemonNameWithAffix(target) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:afterYou", { targetName: getPokemonNameWithAffix(target) }));
//Will find next acting phase of the targeted pokémon, delete it and queue it next on successful delete. //Will find next acting phase of the targeted pokémon, delete it and queue it next on successful delete.
const nextAttackPhase = globalScene.findPhase<MovePhase>((phase) => phase.pokemon === target); const nextAttackPhase = globalScene.phaseManager.findPhase<MovePhase>((phase) => phase.pokemon === target);
if (nextAttackPhase && globalScene.tryRemovePhase((phase: MovePhase) => phase.pokemon === target)) { if (nextAttackPhase && globalScene.phaseManager.tryRemovePhase((phase: MovePhase) => phase.pokemon === target)) {
globalScene.prependToPhase(new MovePhase(target, [ ...nextAttackPhase.targets ], nextAttackPhase.move), MovePhase); globalScene.phaseManager.prependNewToPhase("MovePhase", "MovePhase", target, [ ...nextAttackPhase.targets ], nextAttackPhase.move);
} }
return true; return true;
@ -7888,21 +7870,21 @@ export class ForceLastAttr extends MoveEffectAttr {
* @returns true * @returns true
*/ */
override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
globalScene.queueMessage(i18next.t("moveTriggers:forceLast", { targetPokemonName: getPokemonNameWithAffix(target) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:forceLast", { targetPokemonName: getPokemonNameWithAffix(target) }));
const targetMovePhase = globalScene.findPhase<MovePhase>((phase) => phase.pokemon === target); const targetMovePhase = globalScene.phaseManager.findPhase<MovePhase>((phase) => phase.pokemon === target);
if (targetMovePhase && !targetMovePhase.isForcedLast() && globalScene.tryRemovePhase((phase: MovePhase) => phase.pokemon === target)) { if (targetMovePhase && !targetMovePhase.isForcedLast() && globalScene.phaseManager.tryRemovePhase((phase: MovePhase) => phase.pokemon === target)) {
// Finding the phase to insert the move in front of - // Finding the phase to insert the move in front of -
// Either the end of the turn or in front of another, slower move which has also been forced last // Either the end of the turn or in front of another, slower move which has also been forced last
const prependPhase = globalScene.findPhase((phase) => const prependPhase = globalScene.phaseManager.findPhase((phase) =>
[ MovePhase, MoveEndPhase ].every(cls => !(phase instanceof cls)) [ MovePhase, MoveEndPhase ].every(cls => !(phase instanceof cls))
|| (phase instanceof MovePhase) && phaseForcedSlower(phase, target, !!globalScene.arena.getTag(ArenaTagType.TRICK_ROOM)) || (phase.is("MovePhase")) && phaseForcedSlower(phase, target, !!globalScene.arena.getTag(ArenaTagType.TRICK_ROOM))
); );
if (prependPhase) { if (prependPhase) {
globalScene.phaseQueue.splice( globalScene.phaseManager.phaseQueue.splice(
globalScene.phaseQueue.indexOf(prependPhase), globalScene.phaseManager.phaseQueue.indexOf(prependPhase),
0, 0,
new MovePhase(target, [ ...targetMovePhase.targets ], targetMovePhase.move, false, false, false, true) globalScene.phaseManager.create("MovePhase", target, [ ...targetMovePhase.targets ], targetMovePhase.move, false, false, false, true)
); );
} }
} }
@ -7933,7 +7915,7 @@ const failIfDampCondition: MoveConditionFunc = (user, target, move) => {
globalScene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled)); globalScene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled));
// Queue a message if an ability prevented usage of the move // Queue a message if an ability prevented usage of the move
if (cancelled.value) { if (cancelled.value) {
globalScene.queueMessage(i18next.t("moveTriggers:cannotUseMove", { pokemonName: getPokemonNameWithAffix(user), moveName: move.name })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cannotUseMove", { pokemonName: getPokemonNameWithAffix(user), moveName: move.name }));
} }
return !cancelled.value; return !cancelled.value;
}; };
@ -7942,7 +7924,7 @@ const userSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target:
const targetSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.status?.effect === StatusEffect.SLEEP || target.hasAbility(AbilityId.COMATOSE); const targetSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.status?.effect === StatusEffect.SLEEP || target.hasAbility(AbilityId.COMATOSE);
const failIfLastCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => globalScene.phaseQueue.find(phase => phase instanceof MovePhase) !== undefined; const failIfLastCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => globalScene.phaseManager.phaseQueue.find(phase => phase.is("MovePhase")) !== undefined;
const failIfLastInPartyCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => { const failIfLastInPartyCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => {
const party: Pokemon[] = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); const party: Pokemon[] = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty();
@ -8120,7 +8102,7 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr {
} }
const type = validTypes[user.randBattleSeedInt(validTypes.length)]; const type = validTypes[user.randBattleSeedInt(validTypes.length)];
user.summonData.types = [ type ]; user.summonData.types = [ type ];
globalScene.queueMessage(i18next.t("battle:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), type: toReadableString(PokemonType[type]) })); globalScene.phaseManager.queueMessage(i18next.t("battle:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), type: toReadableString(PokemonType[type]) }));
user.updateInfo(); user.updateInfo();
return true; return true;
@ -8179,7 +8161,7 @@ export class ExposedMoveAttr extends AddBattlerTagAttr {
return false; return false;
} }
globalScene.queueMessage(i18next.t("moveTriggers:exposedMove", { pokemonName: getPokemonNameWithAffix(user), targetPokemonName: getPokemonNameWithAffix(target) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:exposedMove", { pokemonName: getPokemonNameWithAffix(user), targetPokemonName: getPokemonNameWithAffix(target) }));
return true; return true;
} }
@ -8815,7 +8797,7 @@ export function initMoves() {
.reflectable(), .reflectable(),
new SelfStatusMove(MoveId.BELLY_DRUM, PokemonType.NORMAL, -1, 10, -1, 0, 2) new SelfStatusMove(MoveId.BELLY_DRUM, PokemonType.NORMAL, -1, 10, -1, 0, 2)
.attr(CutHpStatStageBoostAttr, [ Stat.ATK ], 12, 2, (user) => { .attr(CutHpStatStageBoostAttr, [ Stat.ATK ], 12, 2, (user) => {
globalScene.queueMessage(i18next.t("moveTriggers:cutOwnHpAndMaximizedStat", { pokemonName: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.ATK)) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cutOwnHpAndMaximizedStat", { pokemonName: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.ATK)) }));
}), }),
new AttackMove(MoveId.SLUDGE_BOMB, PokemonType.POISON, MoveCategory.SPECIAL, 90, 100, 10, 30, 0, 2) new AttackMove(MoveId.SLUDGE_BOMB, PokemonType.POISON, MoveCategory.SPECIAL, 90, 100, 10, 30, 0, 2)
.attr(StatusEffectAttr, StatusEffect.POISON) .attr(StatusEffectAttr, StatusEffect.POISON)
@ -9868,7 +9850,7 @@ export function initMoves() {
const lastEnemyFaint = globalScene.currentBattle.enemyFaintsHistory[globalScene.currentBattle.enemyFaintsHistory.length - 1]; const lastEnemyFaint = globalScene.currentBattle.enemyFaintsHistory[globalScene.currentBattle.enemyFaintsHistory.length - 1];
return ( return (
(lastPlayerFaint !== undefined && turn - lastPlayerFaint.turn === 1 && user.isPlayer()) || (lastPlayerFaint !== undefined && turn - lastPlayerFaint.turn === 1 && user.isPlayer()) ||
(lastEnemyFaint !== undefined && turn - lastEnemyFaint.turn === 1 && !user.isPlayer()) (lastEnemyFaint !== undefined && turn - lastEnemyFaint.turn === 1 && user.isEnemy())
) ? 2 : 1; ) ? 2 : 1;
}), }),
new AttackMove(MoveId.FINAL_GAMBIT, PokemonType.FIGHTING, MoveCategory.SPECIAL, -1, 100, 5, -1, 0, 5) new AttackMove(MoveId.FINAL_GAMBIT, PokemonType.FIGHTING, MoveCategory.SPECIAL, -1, 100, 5, -1, 0, 5)
@ -10383,7 +10365,7 @@ export function initMoves() {
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
.attr(AddBattlerTagAttr, BattlerTagType.BURNED_UP, true, false) .attr(AddBattlerTagAttr, BattlerTagType.BURNED_UP, true, false)
.attr(RemoveTypeAttr, PokemonType.FIRE, (user) => { .attr(RemoveTypeAttr, PokemonType.FIRE, (user) => {
globalScene.queueMessage(i18next.t("moveTriggers:burnedItselfOut", { pokemonName: getPokemonNameWithAffix(user) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:burnedItselfOut", { pokemonName: getPokemonNameWithAffix(user) }));
}), }),
new StatusMove(MoveId.SPEED_SWAP, PokemonType.PSYCHIC, -1, 10, -1, 0, 7) new StatusMove(MoveId.SPEED_SWAP, PokemonType.PSYCHIC, -1, 10, -1, 0, 7)
.attr(SwapStatAttr, Stat.SPD) .attr(SwapStatAttr, Stat.SPD)
@ -11167,7 +11149,7 @@ export function initMoves() {
}) })
.attr(AddBattlerTagAttr, BattlerTagType.DOUBLE_SHOCKED, true, false) .attr(AddBattlerTagAttr, BattlerTagType.DOUBLE_SHOCKED, true, false)
.attr(RemoveTypeAttr, PokemonType.ELECTRIC, (user) => { .attr(RemoveTypeAttr, PokemonType.ELECTRIC, (user) => {
globalScene.queueMessage(i18next.t("moveTriggers:usedUpAllElectricity", { pokemonName: getPokemonNameWithAffix(user) })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:usedUpAllElectricity", { pokemonName: getPokemonNameWithAffix(user) }));
}), }),
new AttackMove(MoveId.GIGATON_HAMMER, PokemonType.STEEL, MoveCategory.PHYSICAL, 160, 100, 5, -1, 0, 9) new AttackMove(MoveId.GIGATON_HAMMER, PokemonType.STEEL, MoveCategory.PHYSICAL, 160, 100, 5, -1, 0, 9)
.makesContact(false) .makesContact(false)

View File

@ -19,7 +19,6 @@ import i18next from "i18next";
import type { IEggOptions } from "#app/data/egg"; import type { IEggOptions } from "#app/data/egg";
import { EggSourceType } from "#enums/egg-source-types"; import { EggSourceType } from "#enums/egg-source-types";
import { EggTier } from "#enums/egg-type"; import { EggTier } from "#enums/egg-type";
import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
@ -182,7 +181,7 @@ export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder.
async () => { async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
// Full heal party // Full heal party
globalScene.unshiftPhase(new PartyHealPhase(true)); globalScene.phaseManager.unshiftNew("PartyHealPhase", true);
const eggOptions: IEggOptions = { const eggOptions: IEggOptions = {
pulled: false, pulled: false,

View File

@ -33,9 +33,8 @@ import {
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerSlot } from "#enums/trainer-slot";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; import type HeldModifierConfig from "#app/@types/held-modifier-config";
import type { BerryType } from "#enums/berry-type"; import type { BerryType } from "#enums/berry-type";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import i18next from "i18next"; import i18next from "i18next";
@ -237,8 +236,12 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.1.boss_enraged`); queueEncounterMessage(`${namespace}:option.1.boss_enraged`);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1), "StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
statChangesForBattle,
1,
); );
}, },
}, },

View File

@ -22,7 +22,6 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import i18next from "i18next"; import i18next from "i18next";
@ -137,7 +136,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterB
}) })
.withOptionPhase(async () => { .withOptionPhase(async () => {
// Give the player a Shiny Charm // Give the player a Shiny Charm
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.SHINY_CHARM)); globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.SHINY_CHARM);
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
}) })
.build(), .build(),

View File

@ -35,7 +35,6 @@ import { BerryModifier } from "#app/modifier/modifier";
import i18next from "#app/plugins/i18n"; import i18next from "#app/plugins/i18n";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { PERMANENT_STATS, Stat } from "#enums/stat"; import { PERMANENT_STATS, Stat } from "#enums/stat";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
@ -237,8 +236,12 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON]; config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => { config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.2.boss_enraged`); queueEncounterMessage(`${namespace}:option.2.boss_enraged`);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1), "StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
statChangesForBattle,
1,
); );
}; };
setEncounterRewards( setEncounterRewards(

View File

@ -26,7 +26,6 @@ import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon";
import { getEncounterText, showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getEncounterText, showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
@ -766,8 +765,10 @@ function doBugTypeMoveTutor(): Promise<void> {
// Option select complete, handle if they are learning a move // Option select complete, handle if they are learning a move
if (result && result.selectedOptionIndex < moveOptions.length) { if (result && result.selectedOptionIndex < moveOptions.length) {
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new LearnMovePhase(result.selectedPokemonIndex, moveOptions[result.selectedOptionIndex].moveId), "LearnMovePhase",
result.selectedPokemonIndex,
moveOptions[result.selectedOptionIndex].moveId,
); );
} }

View File

@ -26,8 +26,6 @@ import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
@ -176,13 +174,12 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.1.boss_enraged`); queueEncounterMessage(`${namespace}:option.1.boss_enraged`);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase( "StatStageChangePhase",
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
true, true,
[Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF], [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF],
1, 1,
),
); );
}, },
}, },
@ -245,8 +242,10 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder
const onPokemonSelected = (pokemon: PlayerPokemon) => { const onPokemonSelected = (pokemon: PlayerPokemon) => {
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender()); encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new LearnMovePhase(globalScene.getPlayerParty().indexOf(pokemon), MoveId.REVELATION_DANCE), "LearnMovePhase",
globalScene.getPlayerParty().indexOf(pokemon),
MoveId.REVELATION_DANCE,
); );
// Play animation again to "learn" the dance // Play animation again to "learn" the dance

View File

@ -16,7 +16,6 @@ import {
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { PokemonFormChangeItemModifier } from "#app/modifier/modifier"; import { PokemonFormChangeItemModifier } from "#app/modifier/modifier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
@ -165,7 +164,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE
.withOptionPhase(async () => { .withOptionPhase(async () => {
// Give the player 5 Rogue Balls // Give the player 5 Rogue Balls
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.ROGUE_BALL)); globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.ROGUE_BALL);
// Start encounter with random legendary (7-10 starter strength) that has level additive // Start encounter with random legendary (7-10 starter strength) that has level additive
// If this is a mono-type challenge, always ensure the required type is filtered for // If this is a mono-type challenge, always ensure the required type is filtered for

View File

@ -29,7 +29,6 @@ import {
} from "#app/modifier/modifier"; } from "#app/modifier/modifier";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import i18next from "#app/plugins/i18n"; import i18next from "#app/plugins/i18n";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { randSeedItem } from "#app/utils/common"; import { randSeedItem } from "#app/utils/common";
@ -65,10 +64,10 @@ const doEventReward = () => {
return !(existingCharm && existingCharm.getStackCount() >= existingCharm.getMaxStackCount()); return !(existingCharm && existingCharm.getStackCount() >= existingCharm.getMaxStackCount());
}); });
if (candidates.length > 0) { if (candidates.length > 0) {
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes[randSeedItem(candidates)])); globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes[randSeedItem(candidates)]);
} else { } else {
// At max stacks, give a Voucher instead // At max stacks, give a Voucher instead
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.VOUCHER)); globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.VOUCHER);
} }
} }
}; };
@ -181,7 +180,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
); );
doEventReward(); doEventReward();
} else { } else {
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.AMULET_COIN)); globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.AMULET_COIN);
doEventReward(); doEventReward();
} }
@ -266,7 +265,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
); );
doEventReward(); doEventReward();
} else { } else {
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.CANDY_JAR)); globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.CANDY_JAR);
doEventReward(); doEventReward();
} }
} else { } else {
@ -288,7 +287,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
); );
doEventReward(); doEventReward();
} else { } else {
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.BERRY_POUCH)); globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.BERRY_POUCH);
doEventReward(); doEventReward();
} }
} }
@ -372,7 +371,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
); );
doEventReward(); doEventReward();
} else { } else {
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.HEALING_CHARM)); globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.HEALING_CHARM);
doEventReward(); doEventReward();
} }

View File

@ -44,7 +44,6 @@ import { EncounterAnim } from "#enums/encounter-anims";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { Ability } from "#app/data/abilities/ability-class"; import { Ability } from "#app/data/abilities/ability-class";
import { FIRE_RESISTANT_ABILITIES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { FIRE_RESISTANT_ABILITIES } from "#app/data/mystery-encounters/requirements/requirement-groups";
@ -92,8 +91,12 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w
gender: Gender.MALE, gender: Gender.MALE,
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.SPDEF, Stat.SPD], 1), "StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
[Stat.SPDEF, Stat.SPD],
1,
); );
}, },
}, },
@ -103,8 +106,12 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w
gender: Gender.FEMALE, gender: Gender.FEMALE,
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.SPDEF, Stat.SPD], 1), "StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
[Stat.SPDEF, Stat.SPD],
1,
); );
}, },
}, },

View File

@ -32,7 +32,6 @@ import PokemonData from "#app/system/pokemon-data";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { randSeedInt } from "#app/utils/common"; import { randSeedInt } from "#app/utils/common";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
@ -76,7 +75,13 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder.
queueEncounterMessage(`${namespace}:option.1.stat_boost`); queueEncounterMessage(`${namespace}:option.1.stat_boost`);
// Randomly boost 1 stat 2 stages // Randomly boost 1 stat 2 stages
// Cannot boost Spd, Acc, or Evasion // Cannot boost Spd, Acc, or Evasion
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [randSeedInt(4, 1)], 2)); globalScene.phaseManager.unshiftNew(
"StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
[randSeedInt(4, 1)],
2,
);
}, },
}, },
], ],

View File

@ -25,9 +25,7 @@ import { getPokemonNameWithAffix } from "#app/messages";
import { PlayerGender } from "#enums/player-gender"; import { PlayerGender } from "#enums/player-gender";
import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball"; import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball";
import { addPokeballOpenParticles } from "#app/field/anims"; import { addPokeballOpenParticles } from "#app/field/anims";
import { ShinySparklePhase } from "#app/phases/shiny-sparkle-phase";
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
import { PostSummonPhase } from "#app/phases/post-summon-phase";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
@ -411,13 +409,13 @@ function summonPlayerPokemonAnimation(pokemon: PlayerPokemon): Promise<void> {
pokemon.resetSummonData(); pokemon.resetSummonData();
globalScene.time.delayedCall(1000, () => { globalScene.time.delayedCall(1000, () => {
if (pokemon.isShiny()) { if (pokemon.isShiny()) {
globalScene.unshiftPhase(new ShinySparklePhase(pokemon.getBattlerIndex())); globalScene.phaseManager.unshiftNew("ShinySparklePhase", pokemon.getBattlerIndex());
} }
pokemon.resetTurnData(); pokemon.resetTurnData();
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);
globalScene.pushPhase(new PostSummonPhase(pokemon.getBattlerIndex())); globalScene.phaseManager.pushNew("PostSummonPhase", pokemon.getBattlerIndex());
resolve(); resolve();
}); });
}, },

View File

@ -17,7 +17,6 @@ import {
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { GameOverPhase } from "#app/phases/game-over-phase";
import { randSeedInt } from "#app/utils/common"; import { randSeedInt } from "#app/utils/common";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
@ -189,8 +188,8 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde
const allowedPokemon = globalScene.getPokemonAllowedInBattle(); const allowedPokemon = globalScene.getPokemonAllowedInBattle();
if (allowedPokemon.length === 0) { if (allowedPokemon.length === 0) {
// If there are no longer any legal pokemon in the party, game over. // If there are no longer any legal pokemon in the party, game over.
globalScene.clearPhaseQueue(); globalScene.phaseManager.clearPhaseQueue();
globalScene.unshiftPhase(new GameOverPhase()); globalScene.phaseManager.unshiftNew("GameOverPhase");
} else { } else {
// Show which Pokemon was KOed, then start battle against Gimmighoul // Show which Pokemon was KOed, then start battle against Gimmighoul
await transitionMysteryEncounterIntroVisuals(true, true, 500); await transitionMysteryEncounterIntroVisuals(true, true, 500);

View File

@ -29,8 +29,6 @@ import { getEncounterText, showEncounterText } from "#app/data/mystery-encounter
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ScanIvsPhase } from "#app/phases/scan-ivs-phase";
import { SummonPhase } from "#app/phases/summon-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-groups"; import { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-groups";
@ -276,7 +274,7 @@ async function summonSafariPokemon() {
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
// Message pokemon remaining // Message pokemon remaining
encounter.setDialogueToken("remainingCount", encounter.misc.safariPokemonRemaining); encounter.setDialogueToken("remainingCount", encounter.misc.safariPokemonRemaining);
globalScene.queueMessage(getEncounterText(`${namespace}:safari.remaining_count`) ?? "", null, true); globalScene.phaseManager.queueMessage(getEncounterText(`${namespace}:safari.remaining_count`) ?? "", null, true);
// Generate pokemon using safariPokemonRemaining so they are always the same pokemon no matter how many turns are taken // Generate pokemon using safariPokemonRemaining so they are always the same pokemon no matter how many turns are taken
// Safari pokemon roll twice on shiny and HA chances, but are otherwise normal // Safari pokemon roll twice on shiny and HA chances, but are otherwise normal
@ -325,7 +323,7 @@ async function summonSafariPokemon() {
encounter.misc.pokemon = pokemon; encounter.misc.pokemon = pokemon;
encounter.misc.safariPokemonRemaining -= 1; encounter.misc.safariPokemonRemaining -= 1;
globalScene.unshiftPhase(new SummonPhase(0, false)); globalScene.phaseManager.unshiftNew("SummonPhase", 0, false);
encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon)); encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon));
@ -336,7 +334,7 @@ async function summonSafariPokemon() {
const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier); const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier);
if (ivScannerModifier) { if (ivScannerModifier) {
globalScene.pushPhase(new ScanIvsPhase(pokemon.getBattlerIndex())); globalScene.phaseManager.pushNew("ScanIvsPhase", pokemon.getBattlerIndex());
} }
} }
@ -559,7 +557,7 @@ async function doEndTurn(cursorIndex: number) {
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
} }
} else { } else {
globalScene.queueMessage(getEncounterText(`${namespace}:safari.watching`) ?? "", 0, null, 1000); globalScene.phaseManager.queueMessage(getEncounterText(`${namespace}:safari.watching`) ?? "", 0, null, 1000);
initSubsequentOptionSelect({ initSubsequentOptionSelect({
overrideOptions: safariZoneGameOptions, overrideOptions: safariZoneGameOptions,
startingCursorIndex: cursorIndex, startingCursorIndex: cursorIndex,

View File

@ -26,7 +26,6 @@ import { AiType, PokemonMove } from "#app/field/pokemon";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
@ -155,7 +154,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil
async () => { async () => {
// Fall asleep waiting for Snorlax // Fall asleep waiting for Snorlax
// Full heal party // Full heal party
globalScene.unshiftPhase(new PartyHealPhase(true)); globalScene.phaseManager.unshiftNew("PartyHealPhase", true);
queueEncounterMessage(`${namespace}:option.2.rest_result`); queueEncounterMessage(`${namespace}:option.2.rest_result`);
leaveEncounterWithoutBattle(); leaveEncounterWithoutBattle();
}, },

View File

@ -27,7 +27,6 @@ import { getPartyLuckValue, modifierTypes } from "#app/modifier/modifier-type";
import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerSlot } from "#enums/trainer-slot";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { import {
@ -227,7 +226,13 @@ async function doBiomeTransitionDialogueAndBattleInit() {
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:boss_enraged`); queueEncounterMessage(`${namespace}:boss_enraged`);
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); globalScene.phaseManager.unshiftNew(
"StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
statChangesForBattle,
1,
);
}, },
}, },
], ],

View File

@ -658,8 +658,8 @@ function onGameOver() {
globalScene.playBgm(globalScene.arena.bgm); globalScene.playBgm(globalScene.arena.bgm);
// Clear any leftover battle phases // Clear any leftover battle phases
globalScene.clearPhaseQueue(); globalScene.phaseManager.clearPhaseQueue();
globalScene.clearPhaseQueueSplice(); globalScene.phaseManager.clearPhaseQueueSplice();
// Return enemy Pokemon // Return enemy Pokemon
const pokemon = globalScene.getEnemyPokemon(); const pokemon = globalScene.getEnemyPokemon();

View File

@ -27,7 +27,6 @@ import { BerryType } from "#enums/berry-type";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
@ -116,8 +115,12 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.2.stat_boost`); queueEncounterMessage(`${namespace}:option.2.stat_boost`);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.DEF, Stat.SPDEF], 1), "StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
[Stat.DEF, Stat.SPDEF],
1,
); );
}, },
}, },

View File

@ -27,9 +27,6 @@ import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms";
import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/abilities/ability"; import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/abilities/ability";
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { ShowTrainerPhase } from "#app/phases/show-trainer-phase";
import { ReturnPhase } from "#app/phases/return-phase";
import i18next from "i18next"; import i18next from "i18next";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
@ -143,7 +140,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = MysteryEncounter
}, },
async () => { async () => {
// Refuse the challenge, they full heal the party and give the player a Rarer Candy // Refuse the challenge, they full heal the party and give the player a Rarer Candy
globalScene.unshiftPhase(new PartyHealPhase(true)); globalScene.phaseManager.unshiftNew("PartyHealPhase", true);
setEncounterRewards({ setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.RARER_CANDY], guaranteedModifierTypeFuncs: [modifierTypes.RARER_CANDY],
fillRemaining: false, fillRemaining: false,
@ -209,7 +206,7 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
for (const pokemon of playerField) { for (const pokemon of playerField) {
pokemon.lapseTag(BattlerTagType.COMMANDED); pokemon.lapseTag(BattlerTagType.COMMANDED);
} }
playerField.forEach((_, p) => globalScene.unshiftPhase(new ReturnPhase(p))); playerField.forEach((_, p) => globalScene.phaseManager.unshiftNew("ReturnPhase", p));
for (const pokemon of globalScene.getPlayerParty()) { for (const pokemon of globalScene.getPlayerParty()) {
// Only trigger form change when Eiscue is in Noice form // Only trigger form change when Eiscue is in Noice form
@ -227,7 +224,7 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon); applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
} }
globalScene.unshiftPhase(new ShowTrainerPhase()); globalScene.phaseManager.unshiftNew("ShowTrainerPhase");
// Hide the trainer and init next battle // Hide the trainer and init next battle
const trainer = globalScene.currentBattle.trainer; const trainer = globalScene.currentBattle.trainer;
// Unassign previous trainer from battle so it isn't destroyed before animation completes // Unassign previous trainer from battle so it isn't destroyed before animation completes

View File

@ -25,7 +25,7 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; import type HeldModifierConfig from "#app/@types/held-modifier-config";
import i18next from "i18next"; import i18next from "i18next";
import { getStatKey } from "#enums/stat"; import { getStatKey } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";

View File

@ -35,7 +35,6 @@ import { PokeballType } from "#enums/pokeball";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { BerryModifier } from "#app/modifier/modifier"; import { BerryModifier } from "#app/modifier/modifier";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
@ -103,8 +102,12 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder.
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.1.stat_boost`); queueEncounterMessage(`${namespace}:option.1.stat_boost`);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1), "StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
statChangesForBattle,
1,
); );
}, },
}, },

View File

@ -39,7 +39,7 @@ import { PlayerGender } from "#enums/player-gender";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; import type HeldModifierConfig from "#app/@types/held-modifier-config";
import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";

View File

@ -275,15 +275,11 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement {
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
const timeOfDay = globalScene.arena?.getTimeOfDay(); const timeOfDay = globalScene.arena?.getTimeOfDay();
if ( return !(
!isNullOrUndefined(timeOfDay) && !isNullOrUndefined(timeOfDay) &&
this.requiredTimeOfDay?.length > 0 && this.requiredTimeOfDay?.length > 0 &&
!this.requiredTimeOfDay.includes(timeOfDay) !this.requiredTimeOfDay.includes(timeOfDay)
) { );
return false;
}
return true;
} }
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] { override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
@ -301,15 +297,11 @@ export class WeatherRequirement extends EncounterSceneRequirement {
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
const currentWeather = globalScene.arena.weather?.weatherType; const currentWeather = globalScene.arena.weather?.weatherType;
if ( return !(
!isNullOrUndefined(currentWeather) && !isNullOrUndefined(currentWeather) &&
this.requiredWeather?.length > 0 && this.requiredWeather?.length > 0 &&
!this.requiredWeather.includes(currentWeather!) !this.requiredWeather.includes(currentWeather!)
) { );
return false;
}
return true;
} }
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] { override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
@ -803,7 +795,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
} }
filterByForm(pokemon, formChangeItem) { filterByForm(pokemon, formChangeItem) {
if ( return (
pokemonFormChanges.hasOwnProperty(pokemon.species.speciesId) && pokemonFormChanges.hasOwnProperty(pokemon.species.speciesId) &&
// Get all form changes for this species with an item trigger, including any compound triggers // Get all form changes for this species with an item trigger, including any compound triggers
pokemonFormChanges[pokemon.species.speciesId] pokemonFormChanges[pokemon.species.speciesId]
@ -812,10 +804,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
.flatMap(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger) .flatMap(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger)
.flatMap(fc => fc.item) .flatMap(fc => fc.item)
.includes(formChangeItem) .includes(formChangeItem)
) { );
return true;
}
return false;
} }
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
@ -873,17 +862,15 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
) { ) {
return true; return true;
} }
if (
return (
pokemon.isFusion() && pokemon.isFusion() &&
pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) && pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) &&
pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter( pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter(
e => e.item === evolutionItem && (!e.condition || e.condition.predicate(pokemon)), e => e.item === evolutionItem && (!e.condition || e.condition.predicate(pokemon)),
).length && ).length &&
pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX
) { );
return true;
}
return false;
} }
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {

View File

@ -51,7 +51,7 @@ function getTextWithDialogueTokens(keyOrString: string): string | null {
*/ */
export function queueEncounterMessage(contentKey: string): void { export function queueEncounterMessage(contentKey: string): void {
const text: string | null = getEncounterText(contentKey); const text: string | null = getEncounterText(contentKey);
globalScene.queueMessage(text ?? "", null, true); globalScene.phaseManager.queueMessage(text ?? "", null, true);
} }
/** /**

View File

@ -20,12 +20,6 @@ import {
modifierTypes, modifierTypes,
regenerateModifierPoolThresholds, regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type"; } from "#app/modifier/modifier-type";
import {
MysteryEncounterBattlePhase,
MysteryEncounterBattleStartCleanupPhase,
MysteryEncounterPhase,
MysteryEncounterRewardsPhase,
} from "#app/phases/mystery-encounter-phases";
import type PokemonData from "#app/system/pokemon-data"; import type PokemonData from "#app/system/pokemon-data";
import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler"; import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler";
@ -50,14 +44,7 @@ import type PokemonSpecies from "#app/data/pokemon-species";
import type { IEggOptions } from "#app/data/egg"; import type { IEggOptions } from "#app/data/egg";
import { Egg } from "#app/data/egg"; import { Egg } from "#app/data/egg";
import type { CustomPokemonData } from "#app/data/custom-pokemon-data"; import type { CustomPokemonData } from "#app/data/custom-pokemon-data";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; import type HeldModifierConfig from "#app/@types/held-modifier-config";
import { MovePhase } from "#app/phases/move-phase";
import { EggLapsePhase } from "#app/phases/egg-lapse-phase";
import { TrainerVictoryPhase } from "#app/phases/trainer-victory-phase";
import { BattleEndPhase } from "#app/phases/battle-end-phase";
import { GameOverPhase } from "#app/phases/game-over-phase";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
import { PartyExpPhase } from "#app/phases/party-exp-phase";
import type { Variant } from "#app/sprites/variant"; import type { Variant } from "#app/sprites/variant";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
@ -428,7 +415,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
console.log("Moveset:", moveset); console.log("Moveset:", moveset);
}); });
globalScene.pushPhase(new MysteryEncounterBattlePhase(partyConfig.disableSwitch)); globalScene.phaseManager.pushNew("MysteryEncounterBattlePhase", partyConfig.disableSwitch);
await Promise.all(loadEnemyAssets); await Promise.all(loadEnemyAssets);
battle.enemyParty.forEach((enemyPokemon_2, e_1) => { battle.enemyParty.forEach((enemyPokemon_2, e_1) => {
@ -480,7 +467,7 @@ export function updatePlayerMoney(changeValue: number, playSound = true, showMes
} }
if (showMessage) { if (showMessage) {
if (changeValue < 0) { if (changeValue < 0) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("mysteryEncounterMessages:paid_money", { i18next.t("mysteryEncounterMessages:paid_money", {
amount: -changeValue, amount: -changeValue,
}), }),
@ -488,7 +475,7 @@ export function updatePlayerMoney(changeValue: number, playSound = true, showMes
true, true,
); );
} else { } else {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("mysteryEncounterMessages:receive_money", { i18next.t("mysteryEncounterMessages:receive_money", {
amount: changeValue, amount: changeValue,
}), }),
@ -767,9 +754,9 @@ export function setEncounterRewards(
} }
if (customShopRewards) { if (customShopRewards) {
globalScene.unshiftPhase(new SelectModifierPhase(0, undefined, customShopRewards)); globalScene.phaseManager.unshiftNew("SelectModifierPhase", 0, undefined, customShopRewards);
} else { } else {
globalScene.tryRemovePhase(p => p instanceof SelectModifierPhase); globalScene.phaseManager.tryRemovePhase(p => p.is("MysteryEncounterRewardsPhase"));
} }
if (eggRewards) { if (eggRewards) {
@ -807,7 +794,7 @@ export function setEncounterExp(participantId: number | number[], baseExpValue:
const participantIds = Array.isArray(participantId) ? participantId : [participantId]; const participantIds = Array.isArray(participantId) ? participantId : [participantId];
globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => { globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => {
globalScene.unshiftPhase(new PartyExpPhase(baseExpValue, useWaveIndex, new Set(participantIds))); globalScene.phaseManager.unshiftNew("PartyExpPhase", baseExpValue, useWaveIndex, new Set(participantIds));
return true; return true;
}; };
@ -829,7 +816,7 @@ export class OptionSelectSettings {
* @param optionSelectSettings * @param optionSelectSettings
*/ */
export function initSubsequentOptionSelect(optionSelectSettings: OptionSelectSettings) { export function initSubsequentOptionSelect(optionSelectSettings: OptionSelectSettings) {
globalScene.pushPhase(new MysteryEncounterPhase(optionSelectSettings)); globalScene.phaseManager.pushNew("MysteryEncounterPhase", optionSelectSettings);
} }
/** /**
@ -843,8 +830,8 @@ export function leaveEncounterWithoutBattle(
encounterMode: MysteryEncounterMode = MysteryEncounterMode.NO_BATTLE, encounterMode: MysteryEncounterMode = MysteryEncounterMode.NO_BATTLE,
) { ) {
globalScene.currentBattle.mysteryEncounter!.encounterMode = encounterMode; globalScene.currentBattle.mysteryEncounter!.encounterMode = encounterMode;
globalScene.clearPhaseQueue(); globalScene.phaseManager.clearPhaseQueue();
globalScene.clearPhaseQueueSplice(); globalScene.phaseManager.clearPhaseQueueSplice();
handleMysteryEncounterVictory(addHealPhase); handleMysteryEncounterVictory(addHealPhase);
} }
@ -857,8 +844,8 @@ export function handleMysteryEncounterVictory(addHealPhase = false, doNotContinu
const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle()); const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle());
if (allowedPkm.length === 0) { if (allowedPkm.length === 0) {
globalScene.clearPhaseQueue(); globalScene.phaseManager.clearPhaseQueue();
globalScene.unshiftPhase(new GameOverPhase()); globalScene.phaseManager.unshiftNew("GameOverPhase");
return; return;
} }
@ -869,8 +856,8 @@ export function handleMysteryEncounterVictory(addHealPhase = false, doNotContinu
return; return;
} }
if (encounter.encounterMode === MysteryEncounterMode.NO_BATTLE) { if (encounter.encounterMode === MysteryEncounterMode.NO_BATTLE) {
globalScene.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase)); globalScene.phaseManager.pushNew("MysteryEncounterRewardsPhase", addHealPhase);
globalScene.pushPhase(new EggLapsePhase()); globalScene.phaseManager.pushNew("EggLapsePhase");
} else if ( } else if (
!globalScene !globalScene
.getEnemyParty() .getEnemyParty()
@ -878,15 +865,15 @@ export function handleMysteryEncounterVictory(addHealPhase = false, doNotContinu
encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true), encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true),
) )
) { ) {
globalScene.pushPhase(new BattleEndPhase(true)); globalScene.phaseManager.pushNew("BattleEndPhase", true);
if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) { if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
globalScene.pushPhase(new TrainerVictoryPhase()); globalScene.phaseManager.pushNew("TrainerVictoryPhase");
} }
if (globalScene.gameMode.isEndless || !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) { if (globalScene.gameMode.isEndless || !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) {
globalScene.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase)); globalScene.phaseManager.pushNew("MysteryEncounterRewardsPhase", addHealPhase);
if (!encounter.doContinueEncounter) { if (!encounter.doContinueEncounter) {
// Only lapse eggs once for multi-battle encounters // Only lapse eggs once for multi-battle encounters
globalScene.pushPhase(new EggLapsePhase()); globalScene.phaseManager.pushNew("EggLapsePhase");
} }
} }
} }
@ -900,8 +887,8 @@ export function handleMysteryEncounterBattleFailed(addHealPhase = false, doNotCo
const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle()); const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle());
if (allowedPkm.length === 0) { if (allowedPkm.length === 0) {
globalScene.clearPhaseQueue(); globalScene.phaseManager.clearPhaseQueue();
globalScene.unshiftPhase(new GameOverPhase()); globalScene.phaseManager.unshiftNew("GameOverPhase");
return; return;
} }
@ -912,14 +899,14 @@ export function handleMysteryEncounterBattleFailed(addHealPhase = false, doNotCo
return; return;
} }
if (encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE) { if (encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE) {
globalScene.pushPhase(new BattleEndPhase(false)); globalScene.phaseManager.pushNew("BattleEndPhase", false);
} }
globalScene.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase)); globalScene.phaseManager.pushNew("MysteryEncounterRewardsPhase", addHealPhase);
if (!encounter.doContinueEncounter) { if (!encounter.doContinueEncounter) {
// Only lapse eggs once for multi-battle encounters // Only lapse eggs once for multi-battle encounters
globalScene.pushPhase(new EggLapsePhase()); globalScene.phaseManager.pushNew("EggLapsePhase");
} }
} }
@ -1004,12 +991,19 @@ export function handleMysteryEncounterBattleStartEffects() {
} else { } else {
source = globalScene.getEnemyField()[0]; source = globalScene.getEnemyField()[0];
} }
// @ts-ignore: source cannot be undefined globalScene.phaseManager.pushNew(
globalScene.pushPhase(new MovePhase(source, effect.targets, effect.move, effect.followUp, effect.ignorePp)); "MovePhase",
// @ts-expect-error: source is guaranteed to be defined
source,
effect.targets,
effect.move,
effect.followUp,
effect.ignorePp,
);
}); });
// Pseudo turn end phase to reset flinch states, Endure, etc. // Pseudo turn end phase to reset flinch states, Endure, etc.
globalScene.pushPhase(new MysteryEncounterBattleStartCleanupPhase()); globalScene.phaseManager.pushNew("MysteryEncounterBattleStartCleanupPhase");
encounter.startOfBattleEffectsComplete = true; encounter.startOfBattleEffectsComplete = true;
} }

View File

@ -32,7 +32,6 @@ import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import type { PermanentStat } from "#enums/stat"; import type { PermanentStat } from "#enums/stat";
import { VictoryPhase } from "#app/phases/victory-phase";
import { SummaryUiMode } from "#app/ui/summary-ui-handler"; import { SummaryUiMode } from "#app/ui/summary-ui-handler";
import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import type { AbilityId } from "#enums/ability-id"; import type { AbilityId } from "#enums/ability-id";
@ -675,7 +674,7 @@ export async function catchPokemon(
if (!globalScene.getEnemyParty().some(p => p.id === pokemon.id)) { if (!globalScene.getEnemyParty().some(p => p.id === pokemon.id)) {
globalScene.getEnemyParty().push(pokemon); globalScene.getEnemyParty().push(pokemon);
} }
globalScene.unshiftPhase(new VictoryPhase(pokemon.id, true)); globalScene.phaseManager.unshiftNew("VictoryPhase", pokemon.id, true);
globalScene.pokemonInfoContainer.hide(); globalScene.pokemonInfoContainer.hide();
if (pokeball) { if (pokeball) {
removePb(pokeball); removePb(pokeball);

View File

@ -186,11 +186,7 @@ export class SpeciesFormChange {
} }
} }
if (!this.trigger.canChange(pokemon)) { return this.trigger.canChange(pokemon);
return false;
}
return true;
} }
findTrigger(triggerType: Constructor<SpeciesFormChangeTrigger>): SpeciesFormChangeTrigger | nil { findTrigger(triggerType: Constructor<SpeciesFormChangeTrigger>): SpeciesFormChangeTrigger | nil {

View File

@ -1,4 +1,4 @@
import type { Localizable } from "#app/interfaces/locales"; import type { Localizable } from "#app/@types/locales";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
@ -8,7 +8,14 @@ import type { AnySound } from "#app/battle-scene";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { GameMode } from "#app/game-mode"; import type { GameMode } from "#app/game-mode";
import { DexAttr, type StarterMoveset } from "#app/system/game-data"; import { DexAttr, type StarterMoveset } from "#app/system/game-data";
import { isNullOrUndefined, capitalizeString, randSeedInt, randSeedGauss, randSeedItem } from "#app/utils/common"; import {
isNullOrUndefined,
capitalizeString,
randSeedInt,
randSeedGauss,
randSeedItem,
randSeedFloat,
} from "#app/utils/common";
import { uncatchableSpecies } from "#app/data/balance/biomes"; import { uncatchableSpecies } from "#app/data/balance/biomes";
import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { speciesEggMoves } from "#app/data/balance/egg-moves";
import { GrowthRate } from "#app/data/exp"; import { GrowthRate } from "#app/data/exp";
@ -750,7 +757,7 @@ export abstract class PokemonSpeciesForm {
let paletteColors: Map<number, number> = new Map(); let paletteColors: Map<number, number> = new Map();
const originalRandom = Math.random; const originalRandom = Math.random;
Math.random = Phaser.Math.RND.frac; Math.random = randSeedFloat;
globalScene.executeWithSeedOffset( globalScene.executeWithSeedOffset(
() => { () => {
@ -773,6 +780,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
readonly mythical: boolean; readonly mythical: boolean;
readonly species: string; readonly species: string;
readonly growthRate: GrowthRate; readonly growthRate: GrowthRate;
/** The chance (as a decimal) for this Species to be male, or `null` for genderless species */
readonly malePercent: number | null; readonly malePercent: number | null;
readonly genderDiffs: boolean; readonly genderDiffs: boolean;
readonly canChangeForm: boolean; readonly canChangeForm: boolean;
@ -889,7 +897,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
return Gender.GENDERLESS; return Gender.GENDERLESS;
} }
if (Phaser.Math.RND.realInRange(0, 1) <= this.malePercent) { if (randSeedFloat() <= this.malePercent) {
return Gender.MALE; return Gender.MALE;
} }
return Gender.FEMALE; return Gender.FEMALE;
@ -1138,7 +1146,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
} }
} }
if (noEvolutionChance === 1 || Phaser.Math.RND.realInRange(0, 1) < noEvolutionChance) { if (noEvolutionChance === 1 || randSeedFloat() <= noEvolutionChance) {
return this.speciesId; return this.speciesId;
} }
@ -1281,10 +1289,6 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
}; };
} }
isObtainable() {
return super.isObtainable();
}
hasVariants() { hasVariants() {
let variantDataIndex: string | number = this.speciesId; let variantDataIndex: string | number = this.speciesId;
if (this.forms.length > 0) { if (this.forms.length > 0) {
@ -2026,9 +2030,9 @@ export function initSpecies() {
new PokemonForm("Normal", "", PokemonType.GROUND, null, 3.5, 950, AbilityId.DROUGHT, AbilityId.NONE, AbilityId.NONE, 670, 100, 150, 140, 100, 90, 90, 3, 0, 335, false, null, true), new PokemonForm("Normal", "", PokemonType.GROUND, null, 3.5, 950, AbilityId.DROUGHT, AbilityId.NONE, AbilityId.NONE, 670, 100, 150, 140, 100, 90, 90, 3, 0, 335, false, null, true),
new PokemonForm("Primal", "primal", PokemonType.GROUND, PokemonType.FIRE, 5, 999.7, AbilityId.DESOLATE_LAND, AbilityId.NONE, AbilityId.NONE, 770, 100, 180, 160, 150, 90, 90, 3, 0, 335), new PokemonForm("Primal", "primal", PokemonType.GROUND, PokemonType.FIRE, 5, 999.7, AbilityId.DESOLATE_LAND, AbilityId.NONE, AbilityId.NONE, 770, 100, 180, 160, 150, 90, 90, 3, 0, 335),
), ),
new PokemonSpecies(SpeciesId.RAYQUAZA, 3, false, true, false, "Sky High Pokémon", PokemonType.DRAGON, PokemonType.FLYING, 7, 206.5, AbilityId.AIR_LOCK, AbilityId.NONE, AbilityId.NONE, 680, 105, 150, 90, 150, 90, 95, 45, 0, 340, GrowthRate.SLOW, null, false, true, new PokemonSpecies(SpeciesId.RAYQUAZA, 3, false, true, false, "Sky High Pokémon", PokemonType.DRAGON, PokemonType.FLYING, 7, 206.5, AbilityId.AIR_LOCK, AbilityId.NONE, AbilityId.NONE, 680, 105, 150, 90, 150, 90, 95, 3, 0, 340, GrowthRate.SLOW, null, false, true,
new PokemonForm("Normal", "", PokemonType.DRAGON, PokemonType.FLYING, 7, 206.5, AbilityId.AIR_LOCK, AbilityId.NONE, AbilityId.NONE, 680, 105, 150, 90, 150, 90, 95, 45, 0, 340, false, null, true), new PokemonForm("Normal", "", PokemonType.DRAGON, PokemonType.FLYING, 7, 206.5, AbilityId.AIR_LOCK, AbilityId.NONE, AbilityId.NONE, 680, 105, 150, 90, 150, 90, 95, 3, 0, 340, false, null, true),
new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DRAGON, PokemonType.FLYING, 10.8, 392, AbilityId.DELTA_STREAM, AbilityId.NONE, AbilityId.NONE, 780, 105, 180, 100, 180, 100, 115, 45, 0, 340), new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DRAGON, PokemonType.FLYING, 10.8, 392, AbilityId.DELTA_STREAM, AbilityId.NONE, AbilityId.NONE, 780, 105, 180, 100, 180, 100, 115, 3, 0, 340),
), ),
new PokemonSpecies(SpeciesId.JIRACHI, 3, false, false, true, "Wish Pokémon", PokemonType.STEEL, PokemonType.PSYCHIC, 0.3, 1.1, AbilityId.SERENE_GRACE, AbilityId.NONE, AbilityId.NONE, 600, 100, 100, 100, 100, 100, 100, 3, 100, 300, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.JIRACHI, 3, false, false, true, "Wish Pokémon", PokemonType.STEEL, PokemonType.PSYCHIC, 0.3, 1.1, AbilityId.SERENE_GRACE, AbilityId.NONE, AbilityId.NONE, 600, 100, 100, 100, 100, 100, 100, 3, 100, 300, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.DEOXYS, 3, false, false, true, "DNA Pokémon", PokemonType.PSYCHIC, null, 1.7, 60.8, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 600, 50, 150, 50, 150, 50, 150, 3, 0, 300, GrowthRate.SLOW, null, false, true, new PokemonSpecies(SpeciesId.DEOXYS, 3, false, false, true, "DNA Pokémon", PokemonType.PSYCHIC, null, 1.7, 60.8, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 600, 50, 150, 50, 150, 50, 150, 3, 0, 300, GrowthRate.SLOW, null, false, true,
@ -2274,10 +2278,10 @@ export function initSpecies() {
new PokemonSpecies(SpeciesId.WHIMSICOTT, 5, false, false, false, "Windveiled Pokémon", PokemonType.GRASS, PokemonType.FAIRY, 0.7, 6.6, AbilityId.PRANKSTER, AbilityId.INFILTRATOR, AbilityId.CHLOROPHYLL, 480, 60, 67, 85, 77, 75, 116, 75, 50, 168, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(SpeciesId.WHIMSICOTT, 5, false, false, false, "Windveiled Pokémon", PokemonType.GRASS, PokemonType.FAIRY, 0.7, 6.6, AbilityId.PRANKSTER, AbilityId.INFILTRATOR, AbilityId.CHLOROPHYLL, 480, 60, 67, 85, 77, 75, 116, 75, 50, 168, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(SpeciesId.PETILIL, 5, false, false, false, "Bulb Pokémon", PokemonType.GRASS, null, 0.5, 6.6, AbilityId.CHLOROPHYLL, AbilityId.OWN_TEMPO, AbilityId.LEAF_GUARD, 280, 45, 35, 50, 70, 50, 30, 190, 50, 56, GrowthRate.MEDIUM_FAST, 0, false), new PokemonSpecies(SpeciesId.PETILIL, 5, false, false, false, "Bulb Pokémon", PokemonType.GRASS, null, 0.5, 6.6, AbilityId.CHLOROPHYLL, AbilityId.OWN_TEMPO, AbilityId.LEAF_GUARD, 280, 45, 35, 50, 70, 50, 30, 190, 50, 56, GrowthRate.MEDIUM_FAST, 0, false),
new PokemonSpecies(SpeciesId.LILLIGANT, 5, false, false, false, "Flowering Pokémon", PokemonType.GRASS, null, 1.1, 16.3, AbilityId.CHLOROPHYLL, AbilityId.OWN_TEMPO, AbilityId.LEAF_GUARD, 480, 70, 60, 75, 110, 75, 90, 75, 50, 168, GrowthRate.MEDIUM_FAST, 0, false), new PokemonSpecies(SpeciesId.LILLIGANT, 5, false, false, false, "Flowering Pokémon", PokemonType.GRASS, null, 1.1, 16.3, AbilityId.CHLOROPHYLL, AbilityId.OWN_TEMPO, AbilityId.LEAF_GUARD, 480, 70, 60, 75, 110, 75, 90, 75, 50, 168, GrowthRate.MEDIUM_FAST, 0, false),
new PokemonSpecies(SpeciesId.BASCULIN, 5, false, false, false, "Hostile Pokémon", PokemonType.WATER, null, 1, 18, AbilityId.RECKLESS, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 25, 50, 161, GrowthRate.MEDIUM_FAST, 50, false, false, new PokemonSpecies(SpeciesId.BASCULIN, 5, false, false, false, "Hostile Pokémon", PokemonType.WATER, null, 1, 18, AbilityId.RECKLESS, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 190, 50, 161, GrowthRate.MEDIUM_FAST, 50, false, false,
new PokemonForm("Red-Striped Form", "red-striped", PokemonType.WATER, null, 1, 18, AbilityId.RECKLESS, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 25, 50, 161, false, null, true), new PokemonForm("Red-Striped Form", "red-striped", PokemonType.WATER, null, 1, 18, AbilityId.RECKLESS, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 190, 50, 161, false, null, true),
new PokemonForm("Blue-Striped Form", "blue-striped", PokemonType.WATER, null, 1, 18, AbilityId.ROCK_HEAD, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 25, 50, 161, false, null, true), new PokemonForm("Blue-Striped Form", "blue-striped", PokemonType.WATER, null, 1, 18, AbilityId.ROCK_HEAD, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 190, 50, 161, false, null, true),
new PokemonForm("White-Striped Form", "white-striped", PokemonType.WATER, null, 1, 18, AbilityId.RATTLED, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 25, 50, 161, false, null, true), new PokemonForm("White-Striped Form", "white-striped", PokemonType.WATER, null, 1, 18, AbilityId.RATTLED, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 190, 50, 161, false, null, true),
), ),
new PokemonSpecies(SpeciesId.SANDILE, 5, false, false, false, "Desert Croc Pokémon", PokemonType.GROUND, PokemonType.DARK, 0.7, 15.2, AbilityId.INTIMIDATE, AbilityId.MOXIE, AbilityId.ANGER_POINT, 292, 50, 72, 35, 35, 35, 65, 180, 50, 58, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(SpeciesId.SANDILE, 5, false, false, false, "Desert Croc Pokémon", PokemonType.GROUND, PokemonType.DARK, 0.7, 15.2, AbilityId.INTIMIDATE, AbilityId.MOXIE, AbilityId.ANGER_POINT, 292, 50, 72, 35, 35, 35, 65, 180, 50, 58, GrowthRate.MEDIUM_SLOW, 50, false),
new PokemonSpecies(SpeciesId.KROKOROK, 5, false, false, false, "Desert Croc Pokémon", PokemonType.GROUND, PokemonType.DARK, 1, 33.4, AbilityId.INTIMIDATE, AbilityId.MOXIE, AbilityId.ANGER_POINT, 351, 60, 82, 45, 45, 45, 74, 90, 50, 123, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(SpeciesId.KROKOROK, 5, false, false, false, "Desert Croc Pokémon", PokemonType.GROUND, PokemonType.DARK, 1, 33.4, AbilityId.INTIMIDATE, AbilityId.MOXIE, AbilityId.ANGER_POINT, 351, 60, 82, 45, 45, 45, 74, 90, 50, 123, GrowthRate.MEDIUM_SLOW, 50, false),
@ -2744,10 +2748,10 @@ export function initSpecies() {
new PokemonSpecies(SpeciesId.TAPU_LELE, 7, true, false, false, "Land Spirit Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 1.2, 18.6, AbilityId.PSYCHIC_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 85, 75, 130, 115, 95, 3, 50, 285, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.TAPU_LELE, 7, true, false, false, "Land Spirit Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 1.2, 18.6, AbilityId.PSYCHIC_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 85, 75, 130, 115, 95, 3, 50, 285, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.TAPU_BULU, 7, true, false, false, "Land Spirit Pokémon", PokemonType.GRASS, PokemonType.FAIRY, 1.9, 45.5, AbilityId.GRASSY_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 130, 115, 85, 95, 75, 3, 50, 285, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.TAPU_BULU, 7, true, false, false, "Land Spirit Pokémon", PokemonType.GRASS, PokemonType.FAIRY, 1.9, 45.5, AbilityId.GRASSY_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 130, 115, 85, 95, 75, 3, 50, 285, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.TAPU_FINI, 7, true, false, false, "Land Spirit Pokémon", PokemonType.WATER, PokemonType.FAIRY, 1.3, 21.2, AbilityId.MISTY_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 75, 115, 95, 130, 85, 3, 50, 285, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.TAPU_FINI, 7, true, false, false, "Land Spirit Pokémon", PokemonType.WATER, PokemonType.FAIRY, 1.3, 21.2, AbilityId.MISTY_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 75, 115, 95, 130, 85, 3, 50, 285, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.COSMOG, 7, true, false, false, "Nebula Pokémon", PokemonType.PSYCHIC, null, 0.2, 0.1, AbilityId.UNAWARE, AbilityId.NONE, AbilityId.NONE, 200, 43, 29, 31, 29, 31, 37, 45, 0, 40, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.COSMOG, 7, true, false, false, "Nebula Pokémon", PokemonType.PSYCHIC, null, 0.2, 0.1, AbilityId.UNAWARE, AbilityId.NONE, AbilityId.NONE, 200, 43, 29, 31, 29, 31, 37, 3, 0, 40, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.COSMOEM, 7, true, false, false, "Protostar Pokémon", PokemonType.PSYCHIC, null, 0.1, 999.9, AbilityId.STURDY, AbilityId.NONE, AbilityId.NONE, 400, 43, 29, 131, 29, 131, 37, 45, 0, 140, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.COSMOEM, 7, true, false, false, "Protostar Pokémon", PokemonType.PSYCHIC, null, 0.1, 999.9, AbilityId.STURDY, AbilityId.NONE, AbilityId.NONE, 400, 43, 29, 131, 29, 131, 37, 3, 0, 140, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.SOLGALEO, 7, false, true, false, "Sunne Pokémon", PokemonType.PSYCHIC, PokemonType.STEEL, 3.4, 230, AbilityId.FULL_METAL_BODY, AbilityId.NONE, AbilityId.NONE, 680, 137, 137, 107, 113, 89, 97, 45, 0, 340, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.SOLGALEO, 7, false, true, false, "Sunne Pokémon", PokemonType.PSYCHIC, PokemonType.STEEL, 3.4, 230, AbilityId.FULL_METAL_BODY, AbilityId.NONE, AbilityId.NONE, 680, 137, 137, 107, 113, 89, 97, 3, 0, 340, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.LUNALA, 7, false, true, false, "Moone Pokémon", PokemonType.PSYCHIC, PokemonType.GHOST, 4, 120, AbilityId.SHADOW_SHIELD, AbilityId.NONE, AbilityId.NONE, 680, 137, 113, 89, 137, 107, 97, 45, 0, 340, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.LUNALA, 7, false, true, false, "Moone Pokémon", PokemonType.PSYCHIC, PokemonType.GHOST, 4, 120, AbilityId.SHADOW_SHIELD, AbilityId.NONE, AbilityId.NONE, 680, 137, 113, 89, 137, 107, 97, 3, 0, 340, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.NIHILEGO, 7, true, false, false, "Parasite Pokémon", PokemonType.ROCK, PokemonType.POISON, 1.2, 55.5, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 109, 53, 47, 127, 131, 103, 45, 0, 285, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.NIHILEGO, 7, true, false, false, "Parasite Pokémon", PokemonType.ROCK, PokemonType.POISON, 1.2, 55.5, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 109, 53, 47, 127, 131, 103, 45, 0, 285, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.BUZZWOLE, 7, true, false, false, "Swollen Pokémon", PokemonType.BUG, PokemonType.FIGHTING, 2.4, 333.6, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 107, 139, 139, 53, 53, 79, 45, 0, 285, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.BUZZWOLE, 7, true, false, false, "Swollen Pokémon", PokemonType.BUG, PokemonType.FIGHTING, 2.4, 333.6, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 107, 139, 139, 53, 53, 79, 45, 0, 285, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.PHEROMOSA, 7, true, false, false, "Lissome Pokémon", PokemonType.BUG, PokemonType.FIGHTING, 1.8, 25, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 71, 137, 37, 137, 37, 151, 45, 0, 285, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.PHEROMOSA, 7, true, false, false, "Lissome Pokémon", PokemonType.BUG, PokemonType.FIGHTING, 1.8, 25, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 71, 137, 37, 137, 37, 151, 45, 0, 285, GrowthRate.SLOW, null, false),
@ -2755,11 +2759,11 @@ export function initSpecies() {
new PokemonSpecies(SpeciesId.CELESTEELA, 7, true, false, false, "Launch Pokémon", PokemonType.STEEL, PokemonType.FLYING, 9.2, 999.9, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 97, 101, 103, 107, 101, 61, 45, 0, 285, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.CELESTEELA, 7, true, false, false, "Launch Pokémon", PokemonType.STEEL, PokemonType.FLYING, 9.2, 999.9, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 97, 101, 103, 107, 101, 61, 45, 0, 285, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.KARTANA, 7, true, false, false, "Drawn Sword Pokémon", PokemonType.GRASS, PokemonType.STEEL, 0.3, 0.1, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 59, 181, 131, 59, 31, 109, 45, 0, 285, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.KARTANA, 7, true, false, false, "Drawn Sword Pokémon", PokemonType.GRASS, PokemonType.STEEL, 0.3, 0.1, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 59, 181, 131, 59, 31, 109, 45, 0, 285, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.GUZZLORD, 7, true, false, false, "Junkivore Pokémon", PokemonType.DARK, PokemonType.DRAGON, 5.5, 888, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 223, 101, 53, 97, 53, 43, 45, 0, 285, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.GUZZLORD, 7, true, false, false, "Junkivore Pokémon", PokemonType.DARK, PokemonType.DRAGON, 5.5, 888, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 223, 101, 53, 97, 53, 43, 45, 0, 285, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.NECROZMA, 7, false, true, false, "Prism Pokémon", PokemonType.PSYCHIC, null, 2.4, 230, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 600, 97, 107, 101, 127, 89, 79, 255, 0, 300, GrowthRate.SLOW, null, false, false, new PokemonSpecies(SpeciesId.NECROZMA, 7, false, true, false, "Prism Pokémon", PokemonType.PSYCHIC, null, 2.4, 230, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 600, 97, 107, 101, 127, 89, 79, 3, 0, 300, GrowthRate.SLOW, null, false, false,
new PokemonForm("Normal", "", PokemonType.PSYCHIC, null, 2.4, 230, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 600, 97, 107, 101, 127, 89, 79, 255, 0, 300, false, null, true), new PokemonForm("Normal", "", PokemonType.PSYCHIC, null, 2.4, 230, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 600, 97, 107, 101, 127, 89, 79, 3, 0, 300, false, null, true),
new PokemonForm("Dusk Mane", "dusk-mane", PokemonType.PSYCHIC, PokemonType.STEEL, 3.8, 460, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 680, 97, 157, 127, 113, 109, 77, 255, 0, 340), new PokemonForm("Dusk Mane", "dusk-mane", PokemonType.PSYCHIC, PokemonType.STEEL, 3.8, 460, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 680, 97, 157, 127, 113, 109, 77, 3, 0, 340),
new PokemonForm("Dawn Wings", "dawn-wings", PokemonType.PSYCHIC, PokemonType.GHOST, 4.2, 350, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 680, 97, 113, 109, 157, 127, 77, 255, 0, 340), new PokemonForm("Dawn Wings", "dawn-wings", PokemonType.PSYCHIC, PokemonType.GHOST, 4.2, 350, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 680, 97, 113, 109, 157, 127, 77, 3, 0, 340),
new PokemonForm("Ultra", "ultra", PokemonType.PSYCHIC, PokemonType.DRAGON, 7.5, 230, AbilityId.NEUROFORCE, AbilityId.NONE, AbilityId.NONE, 754, 97, 167, 97, 167, 97, 129, 255, 0, 377), new PokemonForm("Ultra", "ultra", PokemonType.PSYCHIC, PokemonType.DRAGON, 7.5, 230, AbilityId.NEUROFORCE, AbilityId.NONE, AbilityId.NONE, 754, 97, 167, 97, 167, 97, 129, 3, 0, 377),
), ),
new PokemonSpecies(SpeciesId.MAGEARNA, 7, false, false, true, "Artificial Pokémon", PokemonType.STEEL, PokemonType.FAIRY, 1, 80.5, AbilityId.SOUL_HEART, AbilityId.NONE, AbilityId.NONE, 600, 80, 95, 115, 130, 115, 65, 3, 0, 300, GrowthRate.SLOW, null, false, false, new PokemonSpecies(SpeciesId.MAGEARNA, 7, false, false, true, "Artificial Pokémon", PokemonType.STEEL, PokemonType.FAIRY, 1, 80.5, AbilityId.SOUL_HEART, AbilityId.NONE, AbilityId.NONE, 600, 80, 95, 115, 130, 115, 65, 3, 0, 300, GrowthRate.SLOW, null, false, false,
new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.FAIRY, 1, 80.5, AbilityId.SOUL_HEART, AbilityId.NONE, AbilityId.NONE, 600, 80, 95, 115, 130, 115, 65, 3, 0, 300, false, null, true), new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.FAIRY, 1, 80.5, AbilityId.SOUL_HEART, AbilityId.NONE, AbilityId.NONE, 600, 80, 95, 115, 130, 115, 65, 3, 0, 300, false, null, true),
@ -2968,15 +2972,15 @@ export function initSpecies() {
new PokemonForm("Ice", "ice", PokemonType.PSYCHIC, PokemonType.ICE, 2.4, 809.1, AbilityId.AS_ONE_GLASTRIER, AbilityId.NONE, AbilityId.NONE, 680, 100, 165, 150, 85, 130, 50, 3, 100, 340), new PokemonForm("Ice", "ice", PokemonType.PSYCHIC, PokemonType.ICE, 2.4, 809.1, AbilityId.AS_ONE_GLASTRIER, AbilityId.NONE, AbilityId.NONE, 680, 100, 165, 150, 85, 130, 50, 3, 100, 340),
new PokemonForm("Shadow", "shadow", PokemonType.PSYCHIC, PokemonType.GHOST, 2.4, 53.6, AbilityId.AS_ONE_SPECTRIER, AbilityId.NONE, AbilityId.NONE, 680, 100, 85, 80, 165, 100, 150, 3, 100, 340), new PokemonForm("Shadow", "shadow", PokemonType.PSYCHIC, PokemonType.GHOST, 2.4, 53.6, AbilityId.AS_ONE_SPECTRIER, AbilityId.NONE, AbilityId.NONE, 680, 100, 85, 80, 165, 100, 150, 3, 100, 340),
), ),
new PokemonSpecies(SpeciesId.WYRDEER, 8, false, false, false, "Big Horn Pokémon", PokemonType.NORMAL, PokemonType.PSYCHIC, 1.8, 95.1, AbilityId.INTIMIDATE, AbilityId.FRISK, AbilityId.SAP_SIPPER, 525, 103, 105, 72, 105, 75, 65, 135, 50, 263, GrowthRate.SLOW, 50, false), new PokemonSpecies(SpeciesId.WYRDEER, 8, false, false, false, "Big Horn Pokémon", PokemonType.NORMAL, PokemonType.PSYCHIC, 1.8, 95.1, AbilityId.INTIMIDATE, AbilityId.FRISK, AbilityId.SAP_SIPPER, 525, 103, 105, 72, 105, 75, 65, 45, 50, 263, GrowthRate.SLOW, 50, false),
new PokemonSpecies(SpeciesId.KLEAVOR, 8, false, false, false, "Axe Pokémon", PokemonType.BUG, PokemonType.ROCK, 1.8, 89, AbilityId.SWARM, AbilityId.SHEER_FORCE, AbilityId.SHARPNESS, 500, 70, 135, 95, 45, 70, 85, 115, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(SpeciesId.KLEAVOR, 8, false, false, false, "Axe Pokémon", PokemonType.BUG, PokemonType.ROCK, 1.8, 89, AbilityId.SWARM, AbilityId.SHEER_FORCE, AbilityId.SHARPNESS, 500, 70, 135, 95, 45, 70, 85, 15, 50, 175, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(SpeciesId.URSALUNA, 8, false, false, false, "Peat Pokémon", PokemonType.GROUND, PokemonType.NORMAL, 2.4, 290, AbilityId.GUTS, AbilityId.BULLETPROOF, AbilityId.UNNERVE, 550, 130, 140, 105, 45, 80, 50, 75, 50, 275, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(SpeciesId.URSALUNA, 8, false, false, false, "Peat Pokémon", PokemonType.GROUND, PokemonType.NORMAL, 2.4, 290, AbilityId.GUTS, AbilityId.BULLETPROOF, AbilityId.UNNERVE, 550, 130, 140, 105, 45, 80, 50, 20, 50, 275, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(SpeciesId.BASCULEGION, 8, false, false, false, "Big Fish Pokémon", PokemonType.WATER, PokemonType.GHOST, 3, 110, AbilityId.SWIFT_SWIM, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 530, 120, 112, 65, 80, 75, 78, 135, 50, 265, GrowthRate.MEDIUM_FAST, 50, false, false, new PokemonSpecies(SpeciesId.BASCULEGION, 8, false, false, false, "Big Fish Pokémon", PokemonType.WATER, PokemonType.GHOST, 3, 110, AbilityId.SWIFT_SWIM, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 530, 120, 112, 65, 80, 75, 78, 45, 50, 265, GrowthRate.MEDIUM_FAST, 50, false, false,
new PokemonForm("Male", "male", PokemonType.WATER, PokemonType.GHOST, 3, 110, AbilityId.SWIFT_SWIM, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 530, 120, 112, 65, 80, 75, 78, 135, 50, 265, false, "", true), new PokemonForm("Male", "male", PokemonType.WATER, PokemonType.GHOST, 3, 110, AbilityId.SWIFT_SWIM, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 530, 120, 112, 65, 80, 75, 78, 45, 50, 265, false, "", true),
new PokemonForm("Female", "female", PokemonType.WATER, PokemonType.GHOST, 3, 110, AbilityId.SWIFT_SWIM, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 530, 120, 92, 65, 100, 75, 78, 135, 50, 265, false, null, true), new PokemonForm("Female", "female", PokemonType.WATER, PokemonType.GHOST, 3, 110, AbilityId.SWIFT_SWIM, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 530, 120, 92, 65, 100, 75, 78, 45, 50, 265, false, null, true),
), ),
new PokemonSpecies(SpeciesId.SNEASLER, 8, false, false, false, "Free Climb Pokémon", PokemonType.FIGHTING, PokemonType.POISON, 1.3, 43, AbilityId.PRESSURE, AbilityId.UNBURDEN, AbilityId.POISON_TOUCH, 510, 80, 130, 60, 40, 80, 120, 135, 50, 102, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(SpeciesId.SNEASLER, 8, false, false, false, "Free Climb Pokémon", PokemonType.FIGHTING, PokemonType.POISON, 1.3, 43, AbilityId.PRESSURE, AbilityId.UNBURDEN, AbilityId.POISON_TOUCH, 510, 80, 130, 60, 40, 80, 120, 20, 50, 102, GrowthRate.MEDIUM_SLOW, 50, false),
new PokemonSpecies(SpeciesId.OVERQWIL, 8, false, false, false, "Pin Cluster Pokémon", PokemonType.DARK, PokemonType.POISON, 2.5, 60.5, AbilityId.POISON_POINT, AbilityId.SWIFT_SWIM, AbilityId.INTIMIDATE, 510, 85, 115, 95, 65, 65, 85, 135, 50, 179, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(SpeciesId.OVERQWIL, 8, false, false, false, "Pin Cluster Pokémon", PokemonType.DARK, PokemonType.POISON, 2.5, 60.5, AbilityId.POISON_POINT, AbilityId.SWIFT_SWIM, AbilityId.INTIMIDATE, 510, 85, 115, 95, 65, 65, 85, 45, 50, 179, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(SpeciesId.ENAMORUS, 8, true, false, false, "Love-Hate Pokémon", PokemonType.FAIRY, PokemonType.FLYING, 1.6, 48, AbilityId.CUTE_CHARM, AbilityId.NONE, AbilityId.CONTRARY, 580, 74, 115, 70, 135, 80, 106, 3, 50, 116, GrowthRate.SLOW, 0, false, true, new PokemonSpecies(SpeciesId.ENAMORUS, 8, true, false, false, "Love-Hate Pokémon", PokemonType.FAIRY, PokemonType.FLYING, 1.6, 48, AbilityId.CUTE_CHARM, AbilityId.NONE, AbilityId.CONTRARY, 580, 74, 115, 70, 135, 80, 106, 3, 50, 116, GrowthRate.SLOW, 0, false, true,
new PokemonForm("Incarnate Forme", "incarnate", PokemonType.FAIRY, PokemonType.FLYING, 1.6, 48, AbilityId.CUTE_CHARM, AbilityId.NONE, AbilityId.CONTRARY, 580, 74, 115, 70, 135, 80, 106, 3, 50, 116, false, null, true), new PokemonForm("Incarnate Forme", "incarnate", PokemonType.FAIRY, PokemonType.FLYING, 1.6, 48, AbilityId.CUTE_CHARM, AbilityId.NONE, AbilityId.CONTRARY, 580, 74, 115, 70, 135, 80, 106, 3, 50, 116, false, null, true),
new PokemonForm("Therian Forme", "therian", PokemonType.FAIRY, PokemonType.FLYING, 1.6, 48, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.OVERCOAT, 580, 74, 115, 110, 135, 100, 46, 3, 50, 116), new PokemonForm("Therian Forme", "therian", PokemonType.FAIRY, PokemonType.FLYING, 1.6, 48, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.OVERCOAT, 580, 74, 115, 110, 135, 100, 46, 3, 50, 116),

View File

@ -1,4 +1,4 @@
import type { TrainerTierPools } from "#app/data/trainers/typedefs"; import type { TrainerTierPools } from "#app/@types/trainer-funcs";
import { TrainerPoolTier } from "#enums/trainer-pool-tier"; import { TrainerPoolTier } from "#enums/trainer-pool-tier";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";

View File

@ -48,7 +48,7 @@ import type {
TrainerTierPools, TrainerTierPools,
TrainerConfigs, TrainerConfigs,
PartyMemberFuncs, PartyMemberFuncs,
} from "./typedefs"; } from "../../@types/trainer-funcs";
/** Minimum BST for Pokemon generated onto the Elite Four's teams */ /** Minimum BST for Pokemon generated onto the Elite Four's teams */
const ELITE_FOUR_MINIMUM_BST = 460; const ELITE_FOUR_MINIMUM_BST = 460;

View File

@ -1,17 +0,0 @@
export function getData() {
const dataStr = localStorage.getItem("data");
if (!dataStr) {
return null;
}
return JSON.parse(atob(dataStr), (k, v) =>
k.endsWith("Attr") && !["natureAttr", "abilityAttr", "passiveAttr"].includes(k) ? BigInt(v) : v,
);
}
export function getSession() {
const sessionStr = localStorage.getItem("sessionData");
if (!sessionStr) {
return null;
}
return JSON.parse(atob(sessionStr));
}

View File

@ -38,7 +38,6 @@ import { TimeOfDay } from "#enums/time-of-day";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "#app/data/pokemon-forms";
import { CommonAnimPhase } from "#app/phases/common-anim-phase";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import { FieldEffectModifier } from "#app/modifier/modifier"; import { FieldEffectModifier } from "#app/modifier/modifier";
@ -297,8 +296,8 @@ export class Arena {
*/ */
trySetWeatherOverride(weather: WeatherType): boolean { trySetWeatherOverride(weather: WeatherType): boolean {
this.weather = new Weather(weather, 0); this.weather = new Weather(weather, 0);
globalScene.unshiftPhase(new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (weather - 1))); globalScene.phaseManager.unshiftNew("CommonAnimPhase", undefined, undefined, CommonAnim.SUNNY + (weather - 1));
globalScene.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct? globalScene.phaseManager.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct?
return true; return true;
} }
@ -328,10 +327,14 @@ export class Arena {
this.weather?.isImmutable() && this.weather?.isImmutable() &&
![WeatherType.HARSH_SUN, WeatherType.HEAVY_RAIN, WeatherType.STRONG_WINDS, WeatherType.NONE].includes(weather) ![WeatherType.HARSH_SUN, WeatherType.HEAVY_RAIN, WeatherType.STRONG_WINDS, WeatherType.NONE].includes(weather)
) { ) {
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (oldWeatherType - 1), true), "CommonAnimPhase",
undefined,
undefined,
CommonAnim.SUNNY + (oldWeatherType - 1),
true,
); );
globalScene.queueMessage(getLegendaryWeatherContinuesMessage(oldWeatherType)!); globalScene.phaseManager.queueMessage(getLegendaryWeatherContinuesMessage(oldWeatherType)!);
return false; return false;
} }
@ -348,10 +351,16 @@ export class Arena {
); // TODO: is this bang correct? ); // TODO: is this bang correct?
if (this.weather) { if (this.weather) {
globalScene.unshiftPhase(new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (weather - 1), true)); globalScene.phaseManager.unshiftNew(
globalScene.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct? "CommonAnimPhase",
undefined,
undefined,
CommonAnim.SUNNY + (weather - 1),
true,
);
globalScene.phaseManager.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct?
} else { } else {
globalScene.queueMessage(getWeatherClearMessage(oldWeatherType)!); // TODO: is this bang correct? globalScene.phaseManager.queueMessage(getWeatherClearMessage(oldWeatherType)!); // TODO: is this bang correct?
} }
globalScene globalScene
@ -431,11 +440,16 @@ export class Arena {
if (this.terrain) { if (this.terrain) {
if (!ignoreAnim) { if (!ignoreAnim) {
globalScene.unshiftPhase(new CommonAnimPhase(undefined, undefined, CommonAnim.MISTY_TERRAIN + (terrain - 1))); globalScene.phaseManager.unshiftNew(
"CommonAnimPhase",
undefined,
undefined,
CommonAnim.MISTY_TERRAIN + (terrain - 1),
);
} }
globalScene.queueMessage(getTerrainStartMessage(terrain)!); // TODO: is this bang correct? globalScene.phaseManager.queueMessage(getTerrainStartMessage(terrain)!); // TODO: is this bang correct?
} else { } else {
globalScene.queueMessage(getTerrainClearMessage(oldTerrainType)!); // TODO: is this bang correct? globalScene.phaseManager.queueMessage(getTerrainClearMessage(oldTerrainType)!); // TODO: is this bang correct?
} }
globalScene globalScene

View File

@ -65,6 +65,7 @@ import {
rgbToHsv, rgbToHsv,
deltaRgb, deltaRgb,
isBetween, isBetween,
randSeedFloat,
type nil, type nil,
type Constructor, type Constructor,
randSeedIntRange, randSeedIntRange,
@ -229,14 +230,6 @@ import { BiomeId } from "#enums/biome-id";
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 { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
import { FaintPhase } from "#app/phases/faint-phase";
import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { MoveEndPhase } from "#app/phases/move-end-phase";
import { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
import { PokemonAnimType } from "#enums/pokemon-anim-type"; import { PokemonAnimType } from "#enums/pokemon-anim-type";
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
@ -256,7 +249,6 @@ import { doShinySparkleAnim } from "#app/field/anims";
import { MoveFlags } from "#enums/MoveFlags"; import { MoveFlags } from "#enums/MoveFlags";
import { timedEventManager } from "#app/global-event-manager"; import { timedEventManager } from "#app/global-event-manager";
import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader";
import { ResetStatusPhase } from "#app/phases/reset-status-phase";
export enum LearnMoveSituation { export enum LearnMoveSituation {
MISC, MISC,
@ -504,7 +496,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (level > 1) { if (level > 1) {
const fused = new BooleanHolder(globalScene.gameMode.isSplicedOnly); const fused = new BooleanHolder(globalScene.gameMode.isSplicedOnly);
if (!fused.value && !this.isPlayer() && !this.hasTrainer()) { if (!fused.value && this.isEnemy() && !this.hasTrainer()) {
globalScene.applyModifier(EnemyFusionChanceModifier, false, fused); globalScene.applyModifier(EnemyFusionChanceModifier, false, fused);
} }
@ -789,7 +781,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return true; return true;
} }
abstract isPlayer(): boolean; abstract isPlayer(): this is PlayerPokemon;
abstract isEnemy(): this is EnemyPokemon;
abstract hasTrainer(): boolean; abstract hasTrainer(): boolean;
@ -1293,19 +1287,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/ */
isOffsetBySubstitute(): boolean { isOffsetBySubstitute(): boolean {
const substitute = this.getTag(SubstituteTag); const substitute = this.getTag(SubstituteTag);
if (substitute) { if (!substitute || substitute.sprite === undefined) {
if (substitute.sprite === undefined) { return false;
return false;
}
// During the Pokemon's MoveEffect phase, the offset is removed to put the Pokemon "in focus"
const currentPhase = globalScene.getCurrentPhase();
if (currentPhase instanceof MoveEffectPhase && currentPhase.getPokemon() === this) {
return false;
}
return true;
} }
return false; // During the Pokemon's MoveEffect phase, the offset is removed to put the Pokemon "in focus"
const currentPhase = globalScene.phaseManager.getCurrentPhase();
return !(currentPhase?.is("MoveEffectPhase") && currentPhase.getPokemon() === this);
} }
/** If this Pokemon has a Substitute on the field, removes its sprite from the field. */ /** If this Pokemon has a Substitute on the field, removes its sprite from the field. */
@ -2058,7 +2045,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (Overrides.ABILITY_OVERRIDE && this.isPlayer()) { if (Overrides.ABILITY_OVERRIDE && this.isPlayer()) {
return allAbilities[Overrides.ABILITY_OVERRIDE]; return allAbilities[Overrides.ABILITY_OVERRIDE];
} }
if (Overrides.OPP_ABILITY_OVERRIDE && !this.isPlayer()) { if (Overrides.OPP_ABILITY_OVERRIDE && this.isEnemy()) {
return allAbilities[Overrides.OPP_ABILITY_OVERRIDE]; return allAbilities[Overrides.OPP_ABILITY_OVERRIDE];
} }
if (this.isFusion()) { if (this.isFusion()) {
@ -2088,7 +2075,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (Overrides.PASSIVE_ABILITY_OVERRIDE && this.isPlayer()) { if (Overrides.PASSIVE_ABILITY_OVERRIDE && this.isPlayer()) {
return allAbilities[Overrides.PASSIVE_ABILITY_OVERRIDE]; return allAbilities[Overrides.PASSIVE_ABILITY_OVERRIDE];
} }
if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) { if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && this.isEnemy()) {
return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE]; return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE];
} }
if (!isNullOrUndefined(this.customPokemonData.passive) && this.customPokemonData.passive !== -1) { if (!isNullOrUndefined(this.customPokemonData.passive) && this.customPokemonData.passive !== -1) {
@ -2160,7 +2147,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// returns override if valid for current case // returns override if valid for current case
if ( if (
(Overrides.HAS_PASSIVE_ABILITY_OVERRIDE === false && this.isPlayer()) || (Overrides.HAS_PASSIVE_ABILITY_OVERRIDE === false && this.isPlayer()) ||
(Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE === false && !this.isPlayer()) (Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE === false && this.isEnemy())
) { ) {
return false; return false;
} }
@ -2168,7 +2155,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
((Overrides.PASSIVE_ABILITY_OVERRIDE !== AbilityId.NONE || Overrides.HAS_PASSIVE_ABILITY_OVERRIDE) && ((Overrides.PASSIVE_ABILITY_OVERRIDE !== AbilityId.NONE || Overrides.HAS_PASSIVE_ABILITY_OVERRIDE) &&
this.isPlayer()) || this.isPlayer()) ||
((Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== AbilityId.NONE || Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE) && ((Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== AbilityId.NONE || Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE) &&
!this.isPlayer()) this.isEnemy())
) { ) {
return true; return true;
} }
@ -2177,7 +2164,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const { currentBattle, gameMode } = globalScene; const { currentBattle, gameMode } = globalScene;
const waveIndex = currentBattle?.waveIndex; const waveIndex = currentBattle?.waveIndex;
if ( if (
this instanceof EnemyPokemon && this.isEnemy() &&
(currentBattle?.battleSpec === BattleSpec.FINAL_BOSS || (currentBattle?.battleSpec === BattleSpec.FINAL_BOSS ||
gameMode.isEndlessMinorBoss(waveIndex) || gameMode.isEndlessMinorBoss(waveIndex) ||
gameMode.isEndlessMajorBoss(waveIndex)) gameMode.isEndlessMajorBoss(waveIndex))
@ -2242,10 +2229,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (this.getAbility(ignoreOverride).id === ability && (!canApply || this.canApplyAbility())) { if (this.getAbility(ignoreOverride).id === ability && (!canApply || this.canApplyAbility())) {
return true; return true;
} }
if (this.getPassiveAbility().id === ability && this.hasPassive() && (!canApply || this.canApplyAbility(true))) { return this.getPassiveAbility().id === ability && this.hasPassive() && (!canApply || this.canApplyAbility(true));
return true;
}
return false;
} }
/** /**
@ -2262,10 +2246,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).hasAttr(attrType)) { if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).hasAttr(attrType)) {
return true; return true;
} }
if (this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().hasAttr(attrType)) { return this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().hasAttr(attrType);
return true;
}
return false;
} }
/** /**
@ -2549,7 +2530,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
) { ) {
multiplier /= 2; multiplier /= 2;
if (!simulated) { if (!simulated) {
globalScene.queueMessage(i18next.t("weather:strongWindsEffectMessage")); globalScene.phaseManager.queueMessage(i18next.t("weather:strongWindsEffectMessage"));
} }
} }
return multiplier as TypeDamageMultiplier; return multiplier as TypeDamageMultiplier;
@ -2993,9 +2974,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
let fusionOverride: PokemonSpecies | undefined = undefined; let fusionOverride: PokemonSpecies | undefined = undefined;
if (forStarter && this instanceof PlayerPokemon && Overrides.STARTER_FUSION_SPECIES_OVERRIDE) { if (forStarter && this.isPlayer() && Overrides.STARTER_FUSION_SPECIES_OVERRIDE) {
fusionOverride = getPokemonSpecies(Overrides.STARTER_FUSION_SPECIES_OVERRIDE); fusionOverride = getPokemonSpecies(Overrides.STARTER_FUSION_SPECIES_OVERRIDE);
} else if (this instanceof EnemyPokemon && Overrides.OPP_FUSION_SPECIES_OVERRIDE) { } else if (this.isEnemy() && Overrides.OPP_FUSION_SPECIES_OVERRIDE) {
fusionOverride = getPokemonSpecies(Overrides.OPP_FUSION_SPECIES_OVERRIDE); fusionOverride = getPokemonSpecies(Overrides.OPP_FUSION_SPECIES_OVERRIDE);
} }
@ -3306,7 +3287,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.battleInfo.setX(this.battleInfo.x + (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198)); this.battleInfo.setX(this.battleInfo.x + (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198));
this.battleInfo.setVisible(true); this.battleInfo.setVisible(true);
if (this.isPlayer()) { if (this.isPlayer()) {
this.battleInfo.expMaskRect.x += 150; // TODO: How do you get this to not require a private property access?
this["battleInfo"].expMaskRect.x += 150;
} }
globalScene.tweens.add({ globalScene.tweens.add({
targets: [this.battleInfo, this.battleInfo.expMaskRect], targets: [this.battleInfo, this.battleInfo.expMaskRect],
@ -3327,7 +3309,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
ease: "Cubic.easeIn", ease: "Cubic.easeIn",
onComplete: () => { onComplete: () => {
if (this.isPlayer()) { if (this.isPlayer()) {
this.battleInfo.expMaskRect.x -= 150; // TODO: How do you get this to not require a private property access?
this["battleInfo"].expMaskRect.x -= 150;
} }
this.battleInfo.setVisible(false); this.battleInfo.setVisible(false);
this.battleInfo.setX(this.battleInfo.x - (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198)); this.battleInfo.setX(this.battleInfo.x - (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198));
@ -3422,7 +3405,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @returns An array of Pokémon on the allied field. * @returns An array of Pokémon on the allied field.
*/ */
getAlliedField(): Pokemon[] { getAlliedField(): Pokemon[] {
return this instanceof PlayerPokemon ? globalScene.getPlayerField() : globalScene.getEnemyField(); return this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
} }
/** /**
@ -4021,8 +4004,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* *
* Once the MoveEffectPhase is over (and calls it's .end() function, shiftPhase() will reset the PhaseQueueSplice via clearPhaseQueueSplice() ) * Once the MoveEffectPhase is over (and calls it's .end() function, shiftPhase() will reset the PhaseQueueSplice via clearPhaseQueueSplice() )
*/ */
globalScene.setPhaseQueueSplice(); globalScene.phaseManager.setPhaseQueueSplice();
globalScene.unshiftPhase(new FaintPhase(this.getBattlerIndex(), preventEndure)); globalScene.phaseManager.unshiftNew("FaintPhase", this.getBattlerIndex(), preventEndure);
this.destroySubstitute(); this.destroySubstitute();
this.lapseTag(BattlerTagType.COMMANDED); this.lapseTag(BattlerTagType.COMMANDED);
} }
@ -4058,8 +4041,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} = {}, } = {},
): number { ): number {
const isIndirectDamage = [HitResult.INDIRECT, HitResult.INDIRECT_KO].includes(result); const isIndirectDamage = [HitResult.INDIRECT, HitResult.INDIRECT_KO].includes(result);
const damagePhase = new DamageAnimPhase(this.getBattlerIndex(), damage, result as DamageResult, isCritical); const damagePhase = globalScene.phaseManager.create(
globalScene.unshiftPhase(damagePhase); "DamageAnimPhase",
this.getBattlerIndex(),
damage,
result as DamageResult,
isCritical,
);
globalScene.phaseManager.unshiftPhase(damagePhase);
if (this.switchOutStatus && source) { if (this.switchOutStatus && source) {
damage = 0; damage = 0;
} }
@ -4274,7 +4263,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// Copy all stat stages // Copy all stat stages
for (const s of BATTLE_STATS) { for (const s of BATTLE_STATS) {
const sourceStage = source.getStatStage(s); const sourceStage = source.getStatStage(s);
if (this instanceof PlayerPokemon && sourceStage === 6) { if (this.isPlayer() && sourceStage === 6) {
globalScene.validateAchv(achvs.TRANSFER_MAX_STAT_STAGE); globalScene.validateAchv(achvs.TRANSFER_MAX_STAT_STAGE);
} }
this.setStatStage(s, sourceStage); this.setStatStage(s, sourceStage);
@ -4625,7 +4614,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
: i18next.t("abilityTriggers:moveImmunity", { : i18next.t("abilityTriggers:moveImmunity", {
pokemonNameWithAffix: getPokemonNameWithAffix(this), pokemonNameWithAffix: getPokemonNameWithAffix(this),
}); });
globalScene.queueMessage(message); globalScene.phaseManager.queueMessage(message);
} }
/** /**
@ -4745,7 +4734,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (sourcePokemon && sourcePokemon !== this && this.isSafeguarded(sourcePokemon)) { if (sourcePokemon && sourcePokemon !== this && this.isSafeguarded(sourcePokemon)) {
if (!quiet) { if (!quiet) {
globalScene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(this) })); globalScene.phaseManager.queueMessage(
i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(this) }),
);
} }
return false; return false;
} }
@ -4774,8 +4765,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* cancel the attack's subsequent hits. * cancel the attack's subsequent hits.
*/ */
if (effect === StatusEffect.SLEEP || effect === StatusEffect.FREEZE) { if (effect === StatusEffect.SLEEP || effect === StatusEffect.FREEZE) {
const currentPhase = globalScene.getCurrentPhase(); const currentPhase = globalScene.phaseManager.getCurrentPhase();
if (currentPhase instanceof MoveEffectPhase && currentPhase.getUserPokemon() === this) { if (currentPhase?.is("MoveEffectPhase") && currentPhase.getUserPokemon() === this) {
this.turnData.hitCount = 1; this.turnData.hitCount = 1;
this.turnData.hitsLeft = 1; this.turnData.hitsLeft = 1;
} }
@ -4785,8 +4776,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (overrideStatus) { if (overrideStatus) {
this.resetStatus(false); this.resetStatus(false);
} }
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new ObtainStatusEffectPhase(this.getBattlerIndex(), effect, turnsRemaining, sourceText, sourcePokemon), "ObtainStatusEffectPhase",
this.getBattlerIndex(),
effect,
turnsRemaining,
sourceText,
sourcePokemon,
); );
return true; return true;
} }
@ -4835,7 +4831,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
if (asPhase) { if (asPhase) {
globalScene.unshiftPhase(new ResetStatusPhase(this, confusion, reloadAssets)); globalScene.phaseManager.unshiftNew("ResetStatusPhase", this, confusion, reloadAssets);
} else { } else {
this.clearStatus(confusion, reloadAssets); this.clearStatus(confusion, reloadAssets);
} }
@ -5135,7 +5131,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
}); });
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("battleInfo:newKey2", { i18next.t("battleInfo:newKey2", {
// arguments for the locale key go here; // arguments for the locale key go here;
pokemonNameWithAffix: getPokemonNameWithAffix(this), pokemonNameWithAffix: getPokemonNameWithAffix(this),
@ -5240,7 +5236,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
let fusionPaletteColors: Map<number, number>; let fusionPaletteColors: Map<number, number>;
const originalRandom = Math.random; const originalRandom = Math.random;
Math.random = () => Phaser.Math.RND.realInRange(0, 1); Math.random = () => randSeedFloat();
globalScene.executeWithSeedOffset( globalScene.executeWithSeedOffset(
() => { () => {
@ -5472,10 +5468,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if ((ownedAbilityAttrs & 2) > 0 && this.hasSameAbilityInRootForm(1)) { if ((ownedAbilityAttrs & 2) > 0 && this.hasSameAbilityInRootForm(1)) {
return true; return true;
} }
if ((ownedAbilityAttrs & 4) > 0 && this.hasSameAbilityInRootForm(2)) { return (ownedAbilityAttrs & 4) > 0 && this.hasSameAbilityInRootForm(2);
return true;
}
return false;
} }
/** /**
@ -5492,7 +5485,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
heldItem.stackCount--; heldItem.stackCount--;
if (heldItem.stackCount <= 0) { if (heldItem.stackCount <= 0) {
globalScene.removeModifier(heldItem, !this.isPlayer()); globalScene.removeModifier(heldItem, this.isEnemy());
} }
if (forBattle) { if (forBattle) {
applyPostItemLostAbAttrs(PostItemLostAbAttr, this, false); applyPostItemLostAbAttrs(PostItemLostAbAttr, this, false);
@ -5565,15 +5558,19 @@ export class PlayerPokemon extends Pokemon {
this.battleInfo.initInfo(this); this.battleInfo.initInfo(this);
} }
isPlayer(): boolean { override isPlayer(): this is PlayerPokemon {
return true; return true;
} }
hasTrainer(): boolean { override isEnemy(): this is EnemyPokemon {
return false;
}
override hasTrainer(): boolean {
return true; return true;
} }
isBoss(): boolean { override isBoss(): boolean {
return false; return false;
} }
@ -5648,9 +5645,13 @@ export class PlayerPokemon extends Pokemon {
this.getFieldIndex(), this.getFieldIndex(),
(slotIndex: number, _option: PartyOption) => { (slotIndex: number, _option: PartyOption) => {
if (slotIndex >= globalScene.currentBattle.getBattlerCount() && slotIndex < 6) { if (slotIndex >= globalScene.currentBattle.getBattlerCount() && slotIndex < 6) {
globalScene.prependToPhase( globalScene.phaseManager.prependNewToPhase(
new SwitchSummonPhase(switchType, this.getFieldIndex(), slotIndex, false), "MoveEndPhase",
MoveEndPhase, "SwitchSummonPhase",
switchType,
this.getFieldIndex(),
slotIndex,
false,
); );
} }
globalScene.ui.setMode(UiMode.MESSAGE).then(resolve); globalScene.ui.setMode(UiMode.MESSAGE).then(resolve);
@ -6012,7 +6013,9 @@ export class PlayerPokemon extends Pokemon {
const newPartyMemberIndex = globalScene.getPlayerParty().indexOf(this); const newPartyMemberIndex = globalScene.getPlayerParty().indexOf(this);
pokemon pokemon
.getMoveset(true) .getMoveset(true)
.map((m: PokemonMove) => globalScene.unshiftPhase(new LearnMovePhase(newPartyMemberIndex, m.getMove().id))); .map((m: PokemonMove) =>
globalScene.phaseManager.unshiftNew("LearnMovePhase", newPartyMemberIndex, m.getMove().id),
);
pokemon.destroy(); pokemon.destroy();
this.updateFusionPalette(); this.updateFusionPalette();
} }
@ -6517,15 +6520,19 @@ export class EnemyPokemon extends Pokemon {
return [sortedBenefitScores[targetIndex][0]]; return [sortedBenefitScores[targetIndex][0]];
} }
isPlayer() { override isPlayer(): this is PlayerPokemon {
return false; return false;
} }
hasTrainer(): boolean { override isEnemy(): this is EnemyPokemon {
return true;
}
override hasTrainer(): boolean {
return !!this.trainerSlot; return !!this.trainerSlot;
} }
isBoss(): boolean { override isBoss(): boolean {
return !!this.bossSegments; return !!this.bossSegments;
} }
@ -6650,8 +6657,14 @@ export class EnemyPokemon extends Pokemon {
stages++; stages++;
} }
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase(this.getBattlerIndex(), true, [boostedStat!], stages, true, true), "StatStageChangePhase",
this.getBattlerIndex(),
true,
[boostedStat!],
stages,
true,
true,
); );
this.bossSegmentIndex--; this.bossSegmentIndex--;
} }

View File

@ -1,4 +1,3 @@
export const starterColors: StarterColors = {}; export const starterColors: {
interface StarterColors {
[key: string]: [string, string]; [key: string]: [string, string];
} } = {};

View File

@ -16,7 +16,7 @@ export function getPokemonNameWithAffix(pokemon: Pokemon | undefined, useIllusio
switch (globalScene.currentBattle.battleSpec) { switch (globalScene.currentBattle.battleSpec) {
case BattleSpec.DEFAULT: case BattleSpec.DEFAULT:
return !pokemon.isPlayer() return pokemon.isEnemy()
? pokemon.hasTrainer() ? pokemon.hasTrainer()
? i18next.t("battle:foePokemonWithAffix", { ? i18next.t("battle:foePokemonWithAffix", {
pokemonName: pokemon.getNameToRender(useIllusion), pokemonName: pokemon.getNameToRender(useIllusion),
@ -26,7 +26,7 @@ export function getPokemonNameWithAffix(pokemon: Pokemon | undefined, useIllusio
}) })
: pokemon.getNameToRender(useIllusion); : pokemon.getNameToRender(useIllusion);
case BattleSpec.FINAL_BOSS: case BattleSpec.FINAL_BOSS:
return !pokemon.isPlayer() return pokemon.isEnemy()
? i18next.t("battle:foePokemonWithAffix", { pokemonName: pokemon.getNameToRender(useIllusion) }) ? i18next.t("battle:foePokemonWithAffix", { pokemonName: pokemon.getNameToRender(useIllusion) })
: pokemon.getNameToRender(useIllusion); : pokemon.getNameToRender(useIllusion);
default: default:

View File

@ -8,14 +8,11 @@ import { getStatusEffectHealText } from "#app/data/status-effect";
import Pokemon, { type PlayerPokemon } from "#app/field/pokemon"; import Pokemon, { type PlayerPokemon } from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { EvolutionPhase } from "#app/phases/evolution-phase"; import { LearnMoveType } from "#app/phases/learn-move-phase";
import { LearnMovePhase, LearnMoveType } from "#app/phases/learn-move-phase";
import { LevelUpPhase } from "#app/phases/level-up-phase";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import type { VoucherType } from "#app/system/voucher"; import type { VoucherType } from "#app/system/voucher";
import { Command } from "#app/ui/command-ui-handler"; import { Command } from "#app/ui/command-ui-handler";
import { addTextObject, TextStyle } from "#app/ui/text"; import { addTextObject, TextStyle } from "#app/ui/text";
import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, toDmgValue } from "#app/utils/common"; import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, randSeedFloat, toDmgValue } from "#app/utils/common";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import type { MoveId } from "#enums/move-id"; import type { MoveId } from "#enums/move-id";
@ -40,7 +37,6 @@ import {
type TerastallizeModifierType, type TerastallizeModifierType,
type TmModifierType, type TmModifierType,
getModifierType, getModifierType,
ModifierPoolType,
ModifierTypeGenerator, ModifierTypeGenerator,
modifierTypes, modifierTypes,
PokemonHeldItemModifierType, PokemonHeldItemModifierType,
@ -1091,10 +1087,6 @@ export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier {
return new PokemonIncrementingStatModifier(this.type, this.pokemonId, this.stackCount); return new PokemonIncrementingStatModifier(this.type, this.pokemonId, this.stackCount);
} }
getArgs(): any[] {
return super.getArgs();
}
/** /**
* Checks if the {@linkcode PokemonIncrementingStatModifier} should be applied to the {@linkcode Pokemon}. * Checks if the {@linkcode PokemonIncrementingStatModifier} should be applied to the {@linkcode Pokemon}.
* @param pokemon The {@linkcode Pokemon} that holds the item * @param pokemon The {@linkcode Pokemon} that holds the item
@ -1217,10 +1209,6 @@ export class StatBoosterModifier extends PokemonHeldItemModifier {
* @see {@linkcode apply} * @see {@linkcode apply}
*/ */
export class EvolutionStatBoosterModifier extends StatBoosterModifier { export class EvolutionStatBoosterModifier extends StatBoosterModifier {
clone() {
return super.clone() as EvolutionStatBoosterModifier;
}
matchType(modifier: Modifier): boolean { matchType(modifier: Modifier): boolean {
return modifier instanceof EvolutionStatBoosterModifier; return modifier instanceof EvolutionStatBoosterModifier;
} }
@ -1557,7 +1545,7 @@ export class SurviveDamageModifier extends PokemonHeldItemModifier {
if (!surviveDamage.value && pokemon.randBattleSeedInt(10) < this.getStackCount()) { if (!surviveDamage.value && pokemon.randBattleSeedInt(10) < this.getStackCount()) {
surviveDamage.value = true; surviveDamage.value = true;
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("modifier:surviveDamageApply", { i18next.t("modifier:surviveDamageApply", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
typeName: this.type.name, typeName: this.type.name,
@ -1607,7 +1595,7 @@ export class BypassSpeedChanceModifier extends PokemonHeldItemModifier {
const hasQuickClaw = this.type instanceof PokemonHeldItemModifierType && this.type.id === "QUICK_CLAW"; const hasQuickClaw = this.type instanceof PokemonHeldItemModifierType && this.type.id === "QUICK_CLAW";
if (isCommandFight && hasQuickClaw) { if (isCommandFight && hasQuickClaw) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("modifier:bypassSpeedChanceApply", { i18next.t("modifier:bypassSpeedChanceApply", {
pokemonName: getPokemonNameWithAffix(pokemon), pokemonName: getPokemonNameWithAffix(pokemon),
itemName: i18next.t("modifierType:ModifierType.QUICK_CLAW.name"), itemName: i18next.t("modifierType:ModifierType.QUICK_CLAW.name"),
@ -1693,16 +1681,15 @@ export class TurnHealModifier extends PokemonHeldItemModifier {
*/ */
override apply(pokemon: Pokemon): boolean { override apply(pokemon: Pokemon): boolean {
if (!pokemon.isFullHp()) { if (!pokemon.isFullHp()) {
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new PokemonHealPhase( "PokemonHealPhase",
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
toDmgValue(pokemon.getMaxHp() / 16) * this.stackCount, toDmgValue(pokemon.getMaxHp() / 16) * this.stackCount,
i18next.t("modifier:turnHealApply", { i18next.t("modifier:turnHealApply", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
typeName: this.type.name, typeName: this.type.name,
}), }),
true, true,
),
); );
return true; return true;
} }
@ -1791,16 +1778,15 @@ export class HitHealModifier extends PokemonHeldItemModifier {
override apply(pokemon: Pokemon): boolean { override apply(pokemon: Pokemon): boolean {
if (pokemon.turnData.totalDamageDealt && !pokemon.isFullHp()) { if (pokemon.turnData.totalDamageDealt && !pokemon.isFullHp()) {
// TODO: this shouldn't be undefined AFAIK // TODO: this shouldn't be undefined AFAIK
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new PokemonHealPhase( "PokemonHealPhase",
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
toDmgValue(pokemon.turnData.totalDamageDealt / 8) * this.stackCount, toDmgValue(pokemon.turnData.totalDamageDealt / 8) * this.stackCount,
i18next.t("modifier:hitHealApply", { i18next.t("modifier:hitHealApply", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
typeName: this.type.name, typeName: this.type.name,
}), }),
true, true,
),
); );
} }
@ -1959,18 +1945,17 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier {
*/ */
override apply(pokemon: Pokemon): boolean { override apply(pokemon: Pokemon): boolean {
// Restore the Pokemon to half HP // Restore the Pokemon to half HP
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new PokemonHealPhase( "PokemonHealPhase",
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
toDmgValue(pokemon.getMaxHp() / 2), toDmgValue(pokemon.getMaxHp() / 2),
i18next.t("modifier:pokemonInstantReviveApply", { i18next.t("modifier:pokemonInstantReviveApply", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
typeName: this.type.name, typeName: this.type.name,
}), }),
false, false,
false, false,
true, true,
),
); );
// Remove the Pokemon's FAINT status // Remove the Pokemon's FAINT status
@ -2021,7 +2006,7 @@ export class ResetNegativeStatStageModifier extends PokemonHeldItemModifier {
} }
if (statRestored) { if (statRestored) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("modifier:resetNegativeStatStageApply", { i18next.t("modifier:resetNegativeStatStageApply", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
typeName: this.type.name, typeName: this.type.name,
@ -2332,12 +2317,11 @@ export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier {
playerPokemon.addFriendship(FRIENDSHIP_GAIN_FROM_RARE_CANDY); playerPokemon.addFriendship(FRIENDSHIP_GAIN_FROM_RARE_CANDY);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new LevelUpPhase( "LevelUpPhase",
globalScene.getPlayerParty().indexOf(playerPokemon), globalScene.getPlayerParty().indexOf(playerPokemon),
playerPokemon.level - levelCount.value, playerPokemon.level - levelCount.value,
playerPokemon.level, playerPokemon.level,
),
); );
return true; return true;
@ -2353,8 +2337,11 @@ export class TmModifier extends ConsumablePokemonModifier {
* @returns always `true` * @returns always `true`
*/ */
override apply(playerPokemon: PlayerPokemon): boolean { override apply(playerPokemon: PlayerPokemon): boolean {
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new LearnMovePhase(globalScene.getPlayerParty().indexOf(playerPokemon), this.type.moveId, LearnMoveType.TM), "LearnMovePhase",
globalScene.getPlayerParty().indexOf(playerPokemon),
this.type.moveId,
LearnMoveType.TM,
); );
return true; return true;
@ -2376,13 +2363,12 @@ export class RememberMoveModifier extends ConsumablePokemonModifier {
* @returns always `true` * @returns always `true`
*/ */
override apply(playerPokemon: PlayerPokemon, cost?: number): boolean { override apply(playerPokemon: PlayerPokemon, cost?: number): boolean {
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new LearnMovePhase( "LearnMovePhase",
globalScene.getPlayerParty().indexOf(playerPokemon), globalScene.getPlayerParty().indexOf(playerPokemon),
playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex], playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex],
LearnMoveType.MEMORY, LearnMoveType.MEMORY,
cost, cost,
),
); );
return true; return true;
@ -2419,7 +2405,7 @@ export class EvolutionItemModifier extends ConsumablePokemonModifier {
} }
if (matchingEvolution) { if (matchingEvolution) {
globalScene.unshiftPhase(new EvolutionPhase(playerPokemon, matchingEvolution, playerPokemon.level - 1)); globalScene.phaseManager.unshiftNew("EvolutionPhase", playerPokemon, matchingEvolution, playerPokemon.level - 1);
return true; return true;
} }
@ -3017,7 +3003,7 @@ export class MoneyInterestModifier extends PersistentModifier {
moneyAmount: formattedMoneyAmount, moneyAmount: formattedMoneyAmount,
typeName: this.type.name, typeName: this.type.name,
}); });
globalScene.queueMessage(message, undefined, true); globalScene.phaseManager.queueMessage(message, undefined, true);
return true; return true;
} }
@ -3232,8 +3218,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier {
} }
/** /**
* Steals an item from a set of target Pokemon. * Steals an item, chosen randomly, from a set of target Pokemon.
* This prioritizes high-tier held items when selecting the item to steal.
* @param pokemon The {@linkcode Pokemon} holding this item * @param pokemon The {@linkcode Pokemon} holding this item
* @param target The {@linkcode Pokemon} to steal from (optional) * @param target The {@linkcode Pokemon} to steal from (optional)
* @param _args N/A * @param _args N/A
@ -3253,30 +3238,15 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier {
return false; return false;
} }
const poolType = pokemon.isPlayer()
? ModifierPoolType.PLAYER
: pokemon.hasTrainer()
? ModifierPoolType.TRAINER
: ModifierPoolType.WILD;
const transferredModifierTypes: ModifierType[] = []; const transferredModifierTypes: ModifierType[] = [];
const itemModifiers = globalScene.findModifiers( const itemModifiers = globalScene.findModifiers(
m => m instanceof PokemonHeldItemModifier && m.pokemonId === targetPokemon.id && m.isTransferable, m => m instanceof PokemonHeldItemModifier && m.pokemonId === targetPokemon.id && m.isTransferable,
targetPokemon.isPlayer(), targetPokemon.isPlayer(),
) as PokemonHeldItemModifier[]; ) as PokemonHeldItemModifier[];
let highestItemTier = itemModifiers
.map(m => m.type.getOrInferTier(poolType))
.reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is this bang correct?
let tierItemModifiers = itemModifiers.filter(m => m.type.getOrInferTier(poolType) === highestItemTier);
for (let i = 0; i < transferredItemCount; i++) { for (let i = 0; i < transferredItemCount; i++) {
if (!tierItemModifiers.length) { if (!itemModifiers.length) {
while (highestItemTier-- && !tierItemModifiers.length) { break;
tierItemModifiers = itemModifiers.filter(m => m.type.tier === highestItemTier);
}
if (!tierItemModifiers.length) {
break;
}
} }
const randItemIndex = pokemon.randBattleSeedInt(itemModifiers.length); const randItemIndex = pokemon.randBattleSeedInt(itemModifiers.length);
const randItem = itemModifiers[randItemIndex]; const randItem = itemModifiers[randItemIndex];
@ -3287,7 +3257,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier {
} }
for (const mt of transferredModifierTypes) { for (const mt of transferredModifierTypes) {
globalScene.queueMessage(this.getTransferMessage(pokemon, targetPokemon, mt)); globalScene.phaseManager.queueMessage(this.getTransferMessage(pokemon, targetPokemon, mt));
} }
return !!transferredModifierTypes.length; return !!transferredModifierTypes.length;
@ -3374,7 +3344,7 @@ export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModif
} }
getTransferredItemCount(): number { getTransferredItemCount(): number {
return Phaser.Math.RND.realInRange(0, 1) < this.chance * this.getStackCount() ? 1 : 0; return randSeedFloat() <= this.chance * this.getStackCount() ? 1 : 0;
} }
getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierType): string { getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierType): string {
@ -3597,19 +3567,18 @@ export class EnemyTurnHealModifier extends EnemyPersistentModifier {
*/ */
override apply(enemyPokemon: Pokemon): boolean { override apply(enemyPokemon: Pokemon): boolean {
if (!enemyPokemon.isFullHp()) { if (!enemyPokemon.isFullHp()) {
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new PokemonHealPhase( "PokemonHealPhase",
enemyPokemon.getBattlerIndex(), enemyPokemon.getBattlerIndex(),
Math.max(Math.floor(enemyPokemon.getMaxHp() / (100 / this.healPercent)) * this.stackCount, 1), Math.max(Math.floor(enemyPokemon.getMaxHp() / (100 / this.healPercent)) * this.stackCount, 1),
i18next.t("modifier:enemyTurnHealApply", { i18next.t("modifier:enemyTurnHealApply", {
pokemonNameWithAffix: getPokemonNameWithAffix(enemyPokemon), pokemonNameWithAffix: getPokemonNameWithAffix(enemyPokemon),
}), }),
true, true,
false, false,
false, false,
false, false,
true, true,
),
); );
return true; return true;
} }
@ -3652,7 +3621,7 @@ export class EnemyAttackStatusEffectChanceModifier extends EnemyPersistentModifi
* @returns `true` if the {@linkcode Pokemon} was affected * @returns `true` if the {@linkcode Pokemon} was affected
*/ */
override apply(enemyPokemon: Pokemon): boolean { override apply(enemyPokemon: Pokemon): boolean {
if (Phaser.Math.RND.realInRange(0, 1) < this.chance * this.getStackCount()) { if (randSeedFloat() <= this.chance * this.getStackCount()) {
return enemyPokemon.trySetStatus(this.effect, true); return enemyPokemon.trySetStatus(this.effect, true);
} }
@ -3687,21 +3656,21 @@ export class EnemyStatusEffectHealChanceModifier extends EnemyPersistentModifier
} }
/** /**
* Applies {@linkcode EnemyStatusEffectHealChanceModifier} * Applies {@linkcode EnemyStatusEffectHealChanceModifier} to randomly heal status.
* @param enemyPokemon The {@linkcode Pokemon} to heal * @param enemyPokemon - The {@linkcode Pokemon} to heal
* @returns `true` if the {@linkcode Pokemon} was healed * @returns `true` if the {@linkcode Pokemon} was healed
*/ */
override apply(enemyPokemon: Pokemon): boolean { override apply(enemyPokemon: Pokemon): boolean {
if (enemyPokemon.status && Phaser.Math.RND.realInRange(0, 1) < this.chance * this.getStackCount()) { if (!enemyPokemon.status || randSeedFloat() > this.chance * this.getStackCount()) {
globalScene.queueMessage( return false;
getStatusEffectHealText(enemyPokemon.status.effect, getPokemonNameWithAffix(enemyPokemon)),
);
enemyPokemon.resetStatus();
enemyPokemon.updateInfo();
return true;
} }
return false; globalScene.phaseManager.queueMessage(
getStatusEffectHealText(enemyPokemon.status.effect, getPokemonNameWithAffix(enemyPokemon)),
);
enemyPokemon.resetStatus();
enemyPokemon.updateInfo();
return true;
} }
getMaxStackCount(): number { getMaxStackCount(): number {
@ -3780,7 +3749,7 @@ export class EnemyFusionChanceModifier extends EnemyPersistentModifier {
* @returns `true` if the {@linkcode EnemyPokemon} is a fusion * @returns `true` if the {@linkcode EnemyPokemon} is a fusion
*/ */
override apply(isFusion: BooleanHolder): boolean { override apply(isFusion: BooleanHolder): boolean {
if (Phaser.Math.RND.realInRange(0, 1) >= this.chance * this.getStackCount()) { if (randSeedFloat() > this.chance * this.getStackCount()) {
return false; return false;
} }

581
src/phase-manager.ts Normal file
View File

@ -0,0 +1,581 @@
import type { Phase } from "#app/phase";
import type { default as Pokemon } from "#app/field/pokemon";
import type { PhaseMap, PhaseString } from "./@types/phase-types";
import { globalScene } from "#app/global-scene";
import { AddEnemyBuffModifierPhase } from "#app/phases/add-enemy-buff-modifier-phase";
import { AttemptCapturePhase } from "#app/phases/attempt-capture-phase";
import { AttemptRunPhase } from "#app/phases/attempt-run-phase";
import { BattleEndPhase } from "#app/phases/battle-end-phase";
import { BerryPhase } from "#app/phases/berry-phase";
import { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase";
import { CheckSwitchPhase } from "#app/phases/check-switch-phase";
import { CommandPhase } from "#app/phases/command-phase";
import { CommonAnimPhase } from "#app/phases/common-anim-phase";
import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
import { EggHatchPhase } from "#app/phases/egg-hatch-phase";
import { EggLapsePhase } from "#app/phases/egg-lapse-phase";
import { EggSummaryPhase } from "#app/phases/egg-summary-phase";
import { EncounterPhase } from "#app/phases/encounter-phase";
import { EndCardPhase } from "#app/phases/end-card-phase";
import { EndEvolutionPhase } from "#app/phases/end-evolution-phase";
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
import { EvolutionPhase } from "#app/phases/evolution-phase";
import { ExpPhase } from "#app/phases/exp-phase";
import { FaintPhase } from "#app/phases/faint-phase";
import { FormChangePhase } from "#app/phases/form-change-phase";
import { GameOverModifierRewardPhase } from "#app/phases/game-over-modifier-reward-phase";
import { GameOverPhase } from "#app/phases/game-over-phase";
import { HideAbilityPhase } from "#app/phases/hide-ability-phase";
import { HidePartyExpBarPhase } from "#app/phases/hide-party-exp-bar-phase";
import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { LevelCapPhase } from "#app/phases/level-cap-phase";
import { LevelUpPhase } from "#app/phases/level-up-phase";
import { LoadMoveAnimPhase } from "#app/phases/load-move-anim-phase";
import { LoginPhase } from "#app/phases/login-phase";
import { MessagePhase } from "#app/phases/message-phase";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import { MoneyRewardPhase } from "#app/phases/money-reward-phase";
import { MoveAnimPhase } from "#app/phases/move-anim-phase";
import { MoveChargePhase } from "#app/phases/move-charge-phase";
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { MoveEndPhase } from "#app/phases/move-end-phase";
import { MoveHeaderPhase } from "#app/phases/move-header-phase";
import { MovePhase } from "#app/phases/move-phase";
import {
MysteryEncounterPhase,
MysteryEncounterOptionSelectedPhase,
MysteryEncounterBattlePhase,
MysteryEncounterRewardsPhase,
PostMysteryEncounterPhase,
MysteryEncounterBattleStartCleanupPhase,
} from "#app/phases/mystery-encounter-phases";
import { NewBattlePhase } from "#app/phases/new-battle-phase";
import { NewBiomeEncounterPhase } from "#app/phases/new-biome-encounter-phase";
import { NextEncounterPhase } from "#app/phases/next-encounter-phase";
import { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase";
import { PartyExpPhase } from "#app/phases/party-exp-phase";
import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { PokemonAnimPhase } from "#app/phases/pokemon-anim-phase";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase";
import { PostGameOverPhase } from "#app/phases/post-game-over-phase";
import { PostSummonPhase } from "#app/phases/post-summon-phase";
import { PostTurnStatusEffectPhase } from "#app/phases/post-turn-status-effect-phase";
import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase";
import { ReloadSessionPhase } from "#app/phases/reload-session-phase";
import { ResetStatusPhase } from "#app/phases/reset-status-phase";
import { ReturnPhase } from "#app/phases/return-phase";
import { RevivalBlessingPhase } from "#app/phases/revival-blessing-phase";
import { RibbonModifierRewardPhase } from "#app/phases/ribbon-modifier-reward-phase";
import { ScanIvsPhase } from "#app/phases/scan-ivs-phase";
import { SelectBiomePhase } from "#app/phases/select-biome-phase";
import { SelectChallengePhase } from "#app/phases/select-challenge-phase";
import { SelectGenderPhase } from "#app/phases/select-gender-phase";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
import { SelectStarterPhase } from "#app/phases/select-starter-phase";
import { SelectTargetPhase } from "#app/phases/select-target-phase";
import { ShinySparklePhase } from "#app/phases/shiny-sparkle-phase";
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
import { ShowPartyExpBarPhase } from "#app/phases/show-party-exp-bar-phase";
import { ShowTrainerPhase } from "#app/phases/show-trainer-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { SummonMissingPhase } from "#app/phases/summon-missing-phase";
import { SummonPhase } from "#app/phases/summon-phase";
import { SwitchBiomePhase } from "#app/phases/switch-biome-phase";
import { SwitchPhase } from "#app/phases/switch-phase";
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
import { TeraPhase } from "#app/phases/tera-phase";
import { TitlePhase } from "#app/phases/title-phase";
import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase";
import { TrainerVictoryPhase } from "#app/phases/trainer-victory-phase";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { TurnInitPhase } from "#app/phases/turn-init-phase";
import { TurnStartPhase } from "#app/phases/turn-start-phase";
import { UnavailablePhase } from "#app/phases/unavailable-phase";
import { UnlockPhase } from "#app/phases/unlock-phase";
import { VictoryPhase } from "#app/phases/victory-phase";
import { WeatherEffectPhase } from "#app/phases/weather-effect-phase";
/**
* Manager for phases used by battle scene.
*
* *This file must not be imported or used directly. The manager is exclusively used by the battle scene and is not intended for external use.*
*/
/**
* Object that holds all of the phase constructors.
* This is used to create new phases dynamically using the `newPhase` method in the `PhaseManager`.
*
* @remarks
* The keys of this object are the names of the phases, and the values are the constructors of the phases.
* This allows for easy creation of new phases without needing to import each phase individually.
*/
const PHASES = Object.freeze({
AddEnemyBuffModifierPhase,
AttemptCapturePhase,
AttemptRunPhase,
BattleEndPhase,
BerryPhase,
CheckStatusEffectPhase,
CheckSwitchPhase,
CommandPhase,
CommonAnimPhase,
DamageAnimPhase,
EggHatchPhase,
EggLapsePhase,
EggSummaryPhase,
EncounterPhase,
EndCardPhase,
EndEvolutionPhase,
EnemyCommandPhase,
EvolutionPhase,
ExpPhase,
FaintPhase,
FormChangePhase,
GameOverPhase,
GameOverModifierRewardPhase,
HideAbilityPhase,
HidePartyExpBarPhase,
LearnMovePhase,
LevelCapPhase,
LevelUpPhase,
LoadMoveAnimPhase,
LoginPhase,
MessagePhase,
ModifierRewardPhase,
MoneyRewardPhase,
MoveAnimPhase,
MoveChargePhase,
MoveEffectPhase,
MoveEndPhase,
MoveHeaderPhase,
MovePhase,
MysteryEncounterPhase,
MysteryEncounterOptionSelectedPhase,
MysteryEncounterBattlePhase,
MysteryEncounterBattleStartCleanupPhase,
MysteryEncounterRewardsPhase,
PostMysteryEncounterPhase,
NewBattlePhase,
NewBiomeEncounterPhase,
NextEncounterPhase,
ObtainStatusEffectPhase,
PartyExpPhase,
PartyHealPhase,
PokemonAnimPhase,
PokemonHealPhase,
PokemonTransformPhase,
PostGameOverPhase,
PostSummonPhase,
PostTurnStatusEffectPhase,
QuietFormChangePhase,
ReloadSessionPhase,
ResetStatusPhase,
ReturnPhase,
RevivalBlessingPhase,
RibbonModifierRewardPhase,
ScanIvsPhase,
SelectBiomePhase,
SelectChallengePhase,
SelectGenderPhase,
SelectModifierPhase,
SelectStarterPhase,
SelectTargetPhase,
ShinySparklePhase,
ShowAbilityPhase,
ShowPartyExpBarPhase,
ShowTrainerPhase,
StatStageChangePhase,
SummonMissingPhase,
SummonPhase,
SwitchBiomePhase,
SwitchPhase,
SwitchSummonPhase,
TeraPhase,
TitlePhase,
ToggleDoublePositionPhase,
TrainerVictoryPhase,
TurnEndPhase,
TurnInitPhase,
TurnStartPhase,
UnavailablePhase,
UnlockPhase,
VictoryPhase,
WeatherEffectPhase,
});
// This type export cannot be moved to `@types`, as `Phases` is intentionally private to this file
/** Maps Phase strings to their constructors */
export type PhaseConstructorMap = typeof PHASES;
/**
* PhaseManager is responsible for managing the phases in the battle scene
*/
export class PhaseManager {
/** PhaseQueue: dequeue/remove the first element to get the next phase */
public phaseQueue: Phase[] = [];
public conditionalQueue: Array<[() => boolean, Phase]> = [];
/** PhaseQueuePrepend: is a temp storage of what will be added to PhaseQueue */
private phaseQueuePrepend: Phase[] = [];
/** overrides default of inserting phases to end of phaseQueuePrepend array. Useful for inserting Phases "out of order" */
private phaseQueuePrependSpliceIndex = -1;
private nextCommandPhaseQueue: Phase[] = [];
private currentPhase: Phase | null = null;
private standbyPhase: Phase | null = null;
/* Phase Functions */
getCurrentPhase(): Phase | null {
return this.currentPhase;
}
getStandbyPhase(): Phase | null {
return this.standbyPhase;
}
/**
* Adds a phase to the conditional queue and ensures it is executed only when the specified condition is met.
*
* This method allows deferring the execution of a phase until certain conditions are met, which is useful for handling
* situations like abilities and entry hazards that depend on specific game states.
*
* @param phase - The phase to be added to the conditional queue.
* @param condition - A function that returns a boolean indicating whether the phase should be executed.
*
*/
pushConditionalPhase(phase: Phase, condition: () => boolean): void {
this.conditionalQueue.push([condition, phase]);
}
/**
* Adds a phase to nextCommandPhaseQueue, as long as boolean passed in is false
* @param phase {@linkcode Phase} the phase to add
* @param defer boolean on which queue to add to, defaults to false, and adds to phaseQueue
*/
pushPhase(phase: Phase, defer = false): void {
(!defer ? this.phaseQueue : this.nextCommandPhaseQueue).push(phase);
}
/**
* Adds Phase(s) to the end of phaseQueuePrepend, or at phaseQueuePrependSpliceIndex
* @param phases {@linkcode Phase} the phase(s) to add
*/
unshiftPhase(...phases: Phase[]): void {
if (this.phaseQueuePrependSpliceIndex === -1) {
this.phaseQueuePrepend.push(...phases);
} else {
this.phaseQueuePrepend.splice(this.phaseQueuePrependSpliceIndex, 0, ...phases);
}
}
/**
* Clears the phaseQueue
*/
clearPhaseQueue(): void {
this.phaseQueue.splice(0, this.phaseQueue.length);
}
/**
* Clears all phase-related stuff, including all phase queues, the current and standby phases, and a splice index
*/
clearAllPhases(): void {
for (const queue of [this.phaseQueue, this.phaseQueuePrepend, this.conditionalQueue, this.nextCommandPhaseQueue]) {
queue.splice(0, queue.length);
}
this.currentPhase = null;
this.standbyPhase = null;
this.clearPhaseQueueSplice();
}
/**
* Used by function unshiftPhase(), sets index to start inserting at current length instead of the end of the array, useful if phaseQueuePrepend gets longer with Phases
*/
setPhaseQueueSplice(): void {
this.phaseQueuePrependSpliceIndex = this.phaseQueuePrepend.length;
}
/**
* Resets phaseQueuePrependSpliceIndex to -1, implies that calls to unshiftPhase will insert at end of phaseQueuePrepend
*/
clearPhaseQueueSplice(): void {
this.phaseQueuePrependSpliceIndex = -1;
}
/**
* Is called by each Phase implementations "end()" by default
* We dump everything from phaseQueuePrepend to the start of of phaseQueue
* then removes first Phase and starts it
*/
shiftPhase(): void {
if (this.standbyPhase) {
this.currentPhase = this.standbyPhase;
this.standbyPhase = null;
return;
}
if (this.phaseQueuePrependSpliceIndex > -1) {
this.clearPhaseQueueSplice();
}
if (this.phaseQueuePrepend.length) {
while (this.phaseQueuePrepend.length) {
const poppedPhase = this.phaseQueuePrepend.pop();
if (poppedPhase) {
this.phaseQueue.unshift(poppedPhase);
}
}
}
if (!this.phaseQueue.length) {
this.populatePhaseQueue();
// Clear the conditionalQueue if there are no phases left in the phaseQueue
this.conditionalQueue = [];
}
this.currentPhase = this.phaseQueue.shift() ?? null;
// Check if there are any conditional phases queued
if (this.conditionalQueue?.length) {
// Retrieve the first conditional phase from the queue
const conditionalPhase = this.conditionalQueue.shift();
// Evaluate the condition associated with the phase
if (conditionalPhase?.[0]()) {
// If the condition is met, add the phase to the phase queue
this.pushPhase(conditionalPhase[1]);
} else if (conditionalPhase) {
// If the condition is not met, re-add the phase back to the front of the conditional queue
this.conditionalQueue.unshift(conditionalPhase);
} else {
console.warn("condition phase is undefined/null!", conditionalPhase);
}
}
if (this.currentPhase) {
console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;");
this.currentPhase.start();
}
}
overridePhase(phase: Phase): boolean {
if (this.standbyPhase) {
return false;
}
this.standbyPhase = this.currentPhase;
this.currentPhase = phase;
console.log(`%cStart Phase ${phase.constructor.name}`, "color:green;");
phase.start();
return true;
}
/**
* Find a specific {@linkcode Phase} in the phase queue.
*
* @param phaseFilter filter function to use to find the wanted phase
* @returns the found phase or undefined if none found
*/
findPhase<P extends Phase = Phase>(phaseFilter: (phase: P) => boolean): P | undefined {
return this.phaseQueue.find(phaseFilter) as P;
}
tryReplacePhase(phaseFilter: (phase: Phase) => boolean, phase: Phase): boolean {
const phaseIndex = this.phaseQueue.findIndex(phaseFilter);
if (phaseIndex > -1) {
this.phaseQueue[phaseIndex] = phase;
return true;
}
return false;
}
tryRemovePhase(phaseFilter: (phase: Phase) => boolean): boolean {
const phaseIndex = this.phaseQueue.findIndex(phaseFilter);
if (phaseIndex > -1) {
this.phaseQueue.splice(phaseIndex, 1);
return true;
}
return false;
}
/**
* Will search for a specific phase in {@linkcode phaseQueuePrepend} via filter, and remove the first result if a match is found.
* @param phaseFilter filter function
*/
tryRemoveUnshiftedPhase(phaseFilter: (phase: Phase) => boolean): boolean {
const phaseIndex = this.phaseQueuePrepend.findIndex(phaseFilter);
if (phaseIndex > -1) {
this.phaseQueuePrepend.splice(phaseIndex, 1);
return true;
}
return false;
}
/**
* Tries to add the input phase to index before target phase in the phaseQueue, else simply calls unshiftPhase()
* @param phase - The phase to be added
* @param targetPhase - The phase to search for in phaseQueue
* @returns boolean if a targetPhase was found and added
*/
prependToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean {
if (!Array.isArray(phase)) {
phase = [phase];
}
const target = PHASES[targetPhase];
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target);
if (targetIndex !== -1) {
this.phaseQueue.splice(targetIndex, 0, ...phase);
return true;
}
this.unshiftPhase(...phase);
return false;
}
/**
* Attempt to add the input phase(s) to index after target phase in the {@linkcode phaseQueue}, else simply calls {@linkcode unshiftPhase()}
* @param phase - The phase(s) to be added
* @param targetPhase - The phase to search for in phaseQueue
* @returns `true` if a `targetPhase` was found to append to
*/
appendToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean {
if (!Array.isArray(phase)) {
phase = [phase];
}
const target = PHASES[targetPhase];
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target);
if (targetIndex !== -1 && this.phaseQueue.length > targetIndex) {
this.phaseQueue.splice(targetIndex + 1, 0, ...phase);
return true;
}
this.unshiftPhase(...phase);
return false;
}
/**
* Adds a MessagePhase, either to PhaseQueuePrepend or nextCommandPhaseQueue
* @param message - string for MessagePhase
* @param callbackDelay - optional param for MessagePhase constructor
* @param prompt - optional param for MessagePhase constructor
* @param promptDelay - optional param for MessagePhase constructor
* @param defer - Whether to allow the phase to be deferred
*
* @see {@linkcode MessagePhase} for more details on the parameters
*/
queueMessage(
message: string,
callbackDelay?: number | null,
prompt?: boolean | null,
promptDelay?: number | null,
defer?: boolean | null,
) {
const phase = new MessagePhase(message, callbackDelay, prompt, promptDelay);
if (!defer) {
// adds to the end of PhaseQueuePrepend
this.unshiftPhase(phase);
} else {
//remember that pushPhase adds it to nextCommandPhaseQueue
this.pushPhase(phase);
}
}
/**
* Queues an ability bar flyout phase
* @param pokemon The pokemon who has the ability
* @param passive Whether the ability is a passive
* @param show Whether to show or hide the bar
*/
public queueAbilityDisplay(pokemon: Pokemon, passive: boolean, show: boolean): void {
this.unshiftPhase(show ? new ShowAbilityPhase(pokemon.getBattlerIndex(), passive) : new HideAbilityPhase());
this.clearPhaseQueueSplice();
}
/**
* Hides the ability bar if it is currently visible
*/
public hideAbilityBar(): void {
if (globalScene.abilityBar.isVisible()) {
this.unshiftPhase(new HideAbilityPhase());
}
}
/**
* Moves everything from nextCommandPhaseQueue to phaseQueue (keeping order)
*/
private populatePhaseQueue(): void {
if (this.nextCommandPhaseQueue.length) {
this.phaseQueue.push(...this.nextCommandPhaseQueue);
this.nextCommandPhaseQueue.splice(0, this.nextCommandPhaseQueue.length);
}
this.phaseQueue.push(new TurnInitPhase());
}
/**
* Dynamically create the named phase from the provided arguments
*
* @remarks
* Used to avoid importing each phase individually, allowing for dynamic creation of phases.
* @param phase - The name of the phase to create.
* @param args - The arguments to pass to the phase constructor.
* @returns The requested phase instance
*/
public create<T extends PhaseString>(phase: T, ...args: ConstructorParameters<PhaseConstructorMap[T]>): PhaseMap[T] {
const PhaseClass = PHASES[phase];
if (!PhaseClass) {
throw new Error(`Phase ${phase} does not exist in PhaseMap.`);
}
// @ts-expect-error: Typescript does not support narrowing the type of operands in generic methods (see https://stackoverflow.com/a/72891234)
return new PhaseClass(...args);
}
/**
* Create a new phase and immediately push it to the phase queue. Equivalent to calling {@linkcode create} followed by {@linkcode pushPhase}.
* @param phase - The name of the phase to create
* @param args - The arguments to pass to the phase constructor
*/
public pushNew<T extends PhaseString>(phase: T, ...args: ConstructorParameters<PhaseConstructorMap[T]>): void {
this.pushPhase(this.create(phase, ...args));
}
/**
* Create a new phase and immediately unshift it to the phase queue. Equivalent to calling {@linkcode create} followed by {@linkcode unshiftPhase}.
* @param phase - The name of the phase to create
* @param args - The arguments to pass to the phase constructor
*/
public unshiftNew<T extends PhaseString>(phase: T, ...args: ConstructorParameters<PhaseConstructorMap[T]>): void {
this.unshiftPhase(this.create(phase, ...args));
}
/**
* Create a new phase and immediately prepend it to an existing phase in the phase queue.
* Equivalent to calling {@linkcode create} followed by {@linkcode prependToPhase}.
* @param targetPhase - The phase to search for in phaseQueue
* @param phase - The name of the phase to create
* @param args - The arguments to pass to the phase constructor
* @returns `true` if a `targetPhase` was found to prepend to
*/
public prependNewToPhase<T extends PhaseString>(
targetPhase: PhaseString,
phase: T,
...args: ConstructorParameters<PhaseConstructorMap[T]>
): boolean {
return this.prependToPhase(this.create(phase, ...args), targetPhase);
}
/**
* Create a new phase and immediately append it to an existing phase the phase queue.
* Equivalent to calling {@linkcode create} followed by {@linkcode appendToPhase}.
* @param targetPhase - The phase to search for in phaseQueue
* @param phase - The name of the phase to create
* @param args - The arguments to pass to the phase constructor
* @returns `true` if a `targetPhase` was found to append to
*/
public appendNewToPhase<T extends PhaseString>(
targetPhase: PhaseString,
phase: T,
...args: ConstructorParameters<PhaseConstructorMap[T]>
): boolean {
return this.appendToPhase(this.create(phase, ...args), targetPhase);
}
}

View File

@ -1,9 +1,33 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { PhaseMap, PhaseString } from "./@types/phase-types";
export class Phase { export abstract class Phase {
start() {} start() {}
end() { end() {
globalScene.shiftPhase(); globalScene.phaseManager.shiftPhase();
}
/**
* The string name of the phase, used to identify the phase type for {@linkcode is}
*
* @privateremarks
*
* When implementing a phase, you must set the `phaseName` property to the name of the phase.
*/
public abstract readonly phaseName: PhaseString;
/**
* Check if the phase is of the given type without requiring `instanceof`.
*
* @param phase - The string name of the phase to check.
* @returns Whether this phase is of the provided type.
*
* @remarks
* This does not check for subclasses! It only checks if the phase is *exactly* the given type.
* This method exists to avoid circular import issues, as using `instanceof` would require importing each phase.
*/
is<K extends keyof PhaseMap>(phase: K): this is PhaseMap[K] {
return this.phaseName === phase;
} }
} }

View File

@ -9,6 +9,7 @@ import { Phase } from "#app/phase";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
export class AddEnemyBuffModifierPhase extends Phase { export class AddEnemyBuffModifierPhase extends Phase {
public readonly phaseName = "AddEnemyBuffModifierPhase";
start() { start() {
super.start(); super.start();

View File

@ -14,7 +14,6 @@ import type { EnemyPokemon } from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { PokemonPhase } from "#app/phases/pokemon-phase"; import { PokemonPhase } from "#app/phases/pokemon-phase";
import { VictoryPhase } from "#app/phases/victory-phase";
import { achvs } from "#app/system/achv"; import { achvs } from "#app/system/achv";
import type { PartyOption } from "#app/ui/party-ui-handler"; import type { PartyOption } from "#app/ui/party-ui-handler";
import { PartyUiMode } from "#app/ui/party-ui-handler"; import { PartyUiMode } from "#app/ui/party-ui-handler";
@ -27,6 +26,7 @@ import { globalScene } from "#app/global-scene";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
export class AttemptCapturePhase extends PokemonPhase { export class AttemptCapturePhase extends PokemonPhase {
public readonly phaseName = "AttemptCapturePhase";
private pokeballType: PokeballType; private pokeballType: PokeballType;
private pokeball: Phaser.GameObjects.Sprite; private pokeball: Phaser.GameObjects.Sprite;
private originalY: number; private originalY: number;
@ -256,7 +256,7 @@ export class AttemptCapturePhase extends PokemonPhase {
null, null,
() => { () => {
const end = () => { const end = () => {
globalScene.unshiftPhase(new VictoryPhase(this.battlerIndex)); globalScene.phaseManager.unshiftNew("VictoryPhase", this.battlerIndex);
globalScene.pokemonInfoContainer.hide(); globalScene.pokemonInfoContainer.hide();
this.removePb(); this.removePb();
this.end(); this.end();

View File

@ -10,13 +10,11 @@ import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import i18next from "i18next"; import i18next from "i18next";
import { NumberHolder } from "#app/utils/common"; import { NumberHolder } from "#app/utils/common";
import { BattleEndPhase } from "./battle-end-phase";
import { NewBattlePhase } from "./new-battle-phase";
import { PokemonPhase } from "./pokemon-phase"; import { PokemonPhase } from "./pokemon-phase";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { SelectBiomePhase } from "./select-biome-phase";
export class AttemptRunPhase extends PokemonPhase { export class AttemptRunPhase extends PokemonPhase {
public readonly phaseName = "AttemptRunPhase";
/** For testing purposes: this is to force the pokemon to fail and escape */ /** For testing purposes: this is to force the pokemon to fail and escape */
public forceFailEscape = false; public forceFailEscape = false;
@ -38,7 +36,7 @@ export class AttemptRunPhase extends PokemonPhase {
enemyField.forEach(enemyPokemon => applyPreLeaveFieldAbAttrs(PreLeaveFieldAbAttr, enemyPokemon)); enemyField.forEach(enemyPokemon => applyPreLeaveFieldAbAttrs(PreLeaveFieldAbAttr, enemyPokemon));
globalScene.playSound("se/flee"); globalScene.playSound("se/flee");
globalScene.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500); globalScene.phaseManager.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
globalScene.tweens.add({ globalScene.tweens.add({
targets: [globalScene.arenaEnemy, enemyField].flat(), targets: [globalScene.arenaEnemy, enemyField].flat(),
@ -59,16 +57,16 @@ export class AttemptRunPhase extends PokemonPhase {
enemyPokemon.trySetStatus(StatusEffect.FAINT); enemyPokemon.trySetStatus(StatusEffect.FAINT);
}); });
globalScene.pushPhase(new BattleEndPhase(false)); globalScene.phaseManager.pushNew("BattleEndPhase", false);
if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) { if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) {
globalScene.pushPhase(new SelectBiomePhase()); globalScene.phaseManager.pushNew("SelectBiomePhase");
} }
globalScene.pushPhase(new NewBattlePhase()); globalScene.phaseManager.pushNew("NewBattlePhase");
} else { } else {
playerPokemon.turnData.failedRunAway = true; playerPokemon.turnData.failedRunAway = true;
globalScene.queueMessage(i18next.t("battle:runAwayCannotEscape"), null, true, 500); globalScene.phaseManager.queueMessage(i18next.t("battle:runAwayCannotEscape"), null, true, 500);
} }
this.end(); this.end();

View File

@ -2,9 +2,9 @@ import { globalScene } from "#app/global-scene";
import { applyPostBattleAbAttrs, PostBattleAbAttr } from "#app/data/abilities/ability"; import { applyPostBattleAbAttrs, PostBattleAbAttr } from "#app/data/abilities/ability";
import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier"; import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier";
import { BattlePhase } from "./battle-phase"; import { BattlePhase } from "./battle-phase";
import { GameOverPhase } from "./game-over-phase";
export class BattleEndPhase extends BattlePhase { export class BattleEndPhase extends BattlePhase {
public readonly phaseName = "BattleEndPhase";
/** If true, will increment battles won */ /** If true, will increment battles won */
isVictory: boolean; isVictory: boolean;
@ -18,8 +18,8 @@ export class BattleEndPhase extends BattlePhase {
super.start(); super.start();
// cull any extra `BattleEnd` phases from the queue. // cull any extra `BattleEnd` phases from the queue.
globalScene.phaseQueue = globalScene.phaseQueue.filter(phase => { globalScene.phaseManager.phaseQueue = globalScene.phaseManager.phaseQueue.filter(phase => {
if (phase instanceof BattleEndPhase) { if (phase.is("BattleEndPhase")) {
this.isVictory ||= phase.isVictory; this.isVictory ||= phase.isVictory;
return false; return false;
} }
@ -27,8 +27,8 @@ export class BattleEndPhase extends BattlePhase {
}); });
// `phaseQueuePrepend` is private, so we have to use this inefficient loop. // `phaseQueuePrepend` is private, so we have to use this inefficient loop.
while ( while (
globalScene.tryRemoveUnshiftedPhase(phase => { globalScene.phaseManager.tryRemoveUnshiftedPhase(phase => {
if (phase instanceof BattleEndPhase) { if (phase.is("BattleEndPhase")) {
this.isVictory ||= phase.isVictory; this.isVictory ||= phase.isVictory;
return true; return true;
} }
@ -54,8 +54,8 @@ export class BattleEndPhase extends BattlePhase {
// Endless graceful end // Endless graceful end
if (globalScene.gameMode.isEndless && globalScene.currentBattle.waveIndex >= 5850) { if (globalScene.gameMode.isEndless && globalScene.currentBattle.waveIndex >= 5850) {
globalScene.clearPhaseQueue(); globalScene.phaseManager.clearPhaseQueue();
globalScene.unshiftPhase(new GameOverPhase(true)); globalScene.phaseManager.unshiftNew("GameOverPhase", true);
} }
for (const pokemon of globalScene.getField()) { for (const pokemon of globalScene.getField()) {

View File

@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene";
import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerSlot } from "#enums/trainer-slot";
import { Phase } from "#app/phase"; import { Phase } from "#app/phase";
export class BattlePhase extends Phase { export abstract class BattlePhase extends Phase {
showEnemyTrainer(trainerSlot: TrainerSlot = TrainerSlot.NONE): void { showEnemyTrainer(trainerSlot: TrainerSlot = TrainerSlot.NONE): void {
if (!globalScene.currentBattle.trainer) { if (!globalScene.currentBattle.trainer) {
console.warn("Enemy trainer is missing!"); console.warn("Enemy trainer is missing!");

View File

@ -11,7 +11,6 @@ import { BerryModifier } from "#app/modifier/modifier";
import i18next from "i18next"; import i18next from "i18next";
import { BooleanHolder } from "#app/utils/common"; import { BooleanHolder } from "#app/utils/common";
import { FieldPhase } from "./field-phase"; import { FieldPhase } from "./field-phase";
import { CommonAnimPhase } from "./common-anim-phase";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
@ -20,6 +19,7 @@ import type Pokemon from "#app/field/pokemon";
* Also triggers Cud Chew's "repeat berry use" effects * Also triggers Cud Chew's "repeat berry use" effects
*/ */
export class BerryPhase extends FieldPhase { export class BerryPhase extends FieldPhase {
public readonly phaseName = "BerryPhase";
start() { start() {
super.start(); super.start();
@ -49,7 +49,7 @@ export class BerryPhase extends FieldPhase {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
pokemon.getOpponents().forEach(opp => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled)); pokemon.getOpponents().forEach(opp => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled));
if (cancelled.value) { if (cancelled.value) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("abilityTriggers:preventBerryUse", { i18next.t("abilityTriggers:preventBerryUse", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), }),
@ -57,8 +57,11 @@ export class BerryPhase extends FieldPhase {
return; return;
} }
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM), "CommonAnimPhase",
pokemon.getBattlerIndex(),
pokemon.getBattlerIndex(),
CommonAnim.USE_ITEM,
); );
for (const berryModifier of globalScene.applyModifiers(BerryModifier, pokemon.isPlayer(), pokemon)) { for (const berryModifier of globalScene.applyModifiers(BerryModifier, pokemon.isPlayer(), pokemon)) {

View File

@ -1,9 +1,9 @@
import { PostTurnStatusEffectPhase } from "#app/phases/post-turn-status-effect-phase";
import { Phase } from "#app/phase"; import { Phase } from "#app/phase";
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#app/battle";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
export class CheckStatusEffectPhase extends Phase { export class CheckStatusEffectPhase extends Phase {
public readonly phaseName = "CheckStatusEffectPhase";
private order: BattlerIndex[]; private order: BattlerIndex[];
constructor(order: BattlerIndex[]) { constructor(order: BattlerIndex[]) {
super(); super();
@ -14,7 +14,7 @@ export class CheckStatusEffectPhase extends Phase {
const field = globalScene.getField(); const field = globalScene.getField();
for (const o of this.order) { for (const o of this.order) {
if (field[o].status?.isPostTurn()) { if (field[o].status?.isPostTurn()) {
globalScene.unshiftPhase(new PostTurnStatusEffectPhase(o)); globalScene.phaseManager.unshiftNew("PostTurnStatusEffectPhase", o);
} }
} }
this.end(); this.end();

View File

@ -5,11 +5,10 @@ import { getPokemonNameWithAffix } from "#app/messages";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import i18next from "i18next"; import i18next from "i18next";
import { BattlePhase } from "./battle-phase"; import { BattlePhase } from "./battle-phase";
import { SummonMissingPhase } from "./summon-missing-phase";
import { SwitchPhase } from "./switch-phase";
import { SwitchType } from "#enums/switch-type"; import { SwitchType } from "#enums/switch-type";
export class CheckSwitchPhase extends BattlePhase { export class CheckSwitchPhase extends BattlePhase {
public readonly phaseName = "CheckSwitchPhase";
protected fieldIndex: number; protected fieldIndex: number;
protected useName: boolean; protected useName: boolean;
@ -34,7 +33,7 @@ export class CheckSwitchPhase extends BattlePhase {
// ...if the checked Pokemon is somehow not on the field // ...if the checked Pokemon is somehow not on the field
if (globalScene.field.getAll().indexOf(pokemon) === -1) { if (globalScene.field.getAll().indexOf(pokemon) === -1) {
globalScene.unshiftPhase(new SummonMissingPhase(this.fieldIndex)); globalScene.phaseManager.unshiftNew("SummonMissingPhase", this.fieldIndex);
return super.end(); return super.end();
} }
@ -67,7 +66,7 @@ export class CheckSwitchPhase extends BattlePhase {
UiMode.CONFIRM, UiMode.CONFIRM,
() => { () => {
globalScene.ui.setMode(UiMode.MESSAGE); globalScene.ui.setMode(UiMode.MESSAGE);
globalScene.unshiftPhase(new SwitchPhase(SwitchType.INITIAL_SWITCH, this.fieldIndex, false, true)); globalScene.phaseManager.unshiftNew("SwitchPhase", SwitchType.INITIAL_SWITCH, this.fieldIndex, false, true);
this.end(); this.end();
}, },
() => { () => {

View File

@ -18,13 +18,13 @@ import { Command } from "#app/ui/command-ui-handler";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import i18next from "i18next"; import i18next from "i18next";
import { FieldPhase } from "./field-phase"; import { FieldPhase } from "./field-phase";
import { SelectTargetPhase } from "./select-target-phase";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { isNullOrUndefined } from "#app/utils/common"; import { isNullOrUndefined } from "#app/utils/common";
import { ArenaTagSide } from "#app/data/arena-tag"; import { ArenaTagSide } from "#app/data/arena-tag";
import { ArenaTagType } from "#app/enums/arena-tag-type"; import { ArenaTagType } from "#app/enums/arena-tag-type";
export class CommandPhase extends FieldPhase { export class CommandPhase extends FieldPhase {
public readonly phaseName = "CommandPhase";
protected fieldIndex: number; protected fieldIndex: number;
constructor(fieldIndex: number) { constructor(fieldIndex: number) {
@ -191,7 +191,7 @@ export class CommandPhase extends FieldPhase {
} }
console.log(moveTargets, getPokemonNameWithAffix(playerPokemon)); console.log(moveTargets, getPokemonNameWithAffix(playerPokemon));
if (moveTargets.targets.length > 1 && moveTargets.multiple) { if (moveTargets.targets.length > 1 && moveTargets.multiple) {
globalScene.unshiftPhase(new SelectTargetPhase(this.fieldIndex)); globalScene.phaseManager.unshiftNew("SelectTargetPhase", this.fieldIndex);
} }
if (turnCommand.move && (moveTargets.targets.length <= 1 || moveTargets.multiple)) { if (turnCommand.move && (moveTargets.targets.length <= 1 || moveTargets.multiple)) {
turnCommand.move.targets = moveTargets.targets; turnCommand.move.targets = moveTargets.targets;
@ -202,7 +202,7 @@ export class CommandPhase extends FieldPhase {
) { ) {
turnCommand.move.targets = playerPokemon.getMoveQueue()[0].targets; turnCommand.move.targets = playerPokemon.getMoveQueue()[0].targets;
} else { } else {
globalScene.unshiftPhase(new SelectTargetPhase(this.fieldIndex)); globalScene.phaseManager.unshiftNew("SelectTargetPhase", this.fieldIndex);
} }
globalScene.currentBattle.preTurnCommands[this.fieldIndex] = preTurnCommand; globalScene.currentBattle.preTurnCommands[this.fieldIndex] = preTurnCommand;
globalScene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; globalScene.currentBattle.turnCommands[this.fieldIndex] = turnCommand;
@ -456,8 +456,8 @@ export class CommandPhase extends FieldPhase {
cancel() { cancel() {
if (this.fieldIndex) { if (this.fieldIndex) {
globalScene.unshiftPhase(new CommandPhase(0)); globalScene.phaseManager.unshiftNew("CommandPhase", 0);
globalScene.unshiftPhase(new CommandPhase(1)); globalScene.phaseManager.unshiftNew("CommandPhase", 1);
this.end(); this.end();
} }
} }

View File

@ -5,6 +5,9 @@ import { CommonBattleAnim } from "#app/data/battle-anims";
import { PokemonPhase } from "./pokemon-phase"; import { PokemonPhase } from "./pokemon-phase";
export class CommonAnimPhase extends PokemonPhase { export class CommonAnimPhase extends PokemonPhase {
// PokemonHealPhase extends CommonAnimPhase, and to make typescript happy,
// we need to allow phaseName to be a union of the two
public readonly phaseName: "CommonAnimPhase" | "PokemonHealPhase" | "WeatherEffectPhase" = "CommonAnimPhase";
private anim: CommonAnim | null; private anim: CommonAnim | null;
private targetIndex?: BattlerIndex; private targetIndex?: BattlerIndex;
private playOnEmptyField: boolean; private playOnEmptyField: boolean;

View File

@ -6,6 +6,7 @@ import { fixedInt } from "#app/utils/common";
import { PokemonPhase } from "#app/phases/pokemon-phase"; import { PokemonPhase } from "#app/phases/pokemon-phase";
export class DamageAnimPhase extends PokemonPhase { export class DamageAnimPhase extends PokemonPhase {
public readonly phaseName = "DamageAnimPhase";
private amount: number; private amount: number;
private damageResult: DamageResult; private damageResult: DamageResult;
private critical: boolean; private critical: boolean;

View File

@ -20,6 +20,7 @@ import { doShinySparkleAnim } from "#app/field/anims";
* Class that represents egg hatching * Class that represents egg hatching
*/ */
export class EggHatchPhase extends Phase { export class EggHatchPhase extends Phase {
public readonly phaseName = "EggHatchPhase";
/** The egg that is hatching */ /** The egg that is hatching */
private egg: Egg; private egg: Egg;
/** The new EggHatchData for the egg/pokemon that hatches */ /** The new EggHatchData for the egg/pokemon that hatches */
@ -224,7 +225,7 @@ export class EggHatchPhase extends Phase {
} }
end() { end() {
if (globalScene.findPhase(p => p instanceof EggHatchPhase)) { if (globalScene.phaseManager.findPhase(p => p.is("EggHatchPhase"))) {
this.eggHatchHandler.clear(); this.eggHatchHandler.clear();
} else { } else {
globalScene.time.delayedCall(250, () => globalScene.setModifiersVisible(true)); globalScene.time.delayedCall(250, () => globalScene.setModifiersVisible(true));

View File

@ -4,11 +4,9 @@ import { EGG_SEED } from "#app/data/egg";
import { Phase } from "#app/phase"; import { Phase } from "#app/phase";
import i18next from "i18next"; import i18next from "i18next";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { EggHatchPhase } from "./egg-hatch-phase";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import { achvs } from "#app/system/achv"; import { achvs } from "#app/system/achv";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import { EggSummaryPhase } from "./egg-summary-phase";
import { EggHatchData } from "#app/data/egg-hatch-data"; import { EggHatchData } from "#app/data/egg-hatch-data";
/** /**
@ -16,6 +14,7 @@ import { EggHatchData } from "#app/data/egg-hatch-data";
* Also handles prompts for skipping animation, and calling the egg summary phase * Also handles prompts for skipping animation, and calling the egg summary phase
*/ */
export class EggLapsePhase extends Phase { export class EggLapsePhase extends Phase {
public readonly phaseName = "EggLapsePhase";
private eggHatchData: EggHatchData[] = []; private eggHatchData: EggHatchData[] = [];
private readonly minEggsToSkip: number = 2; private readonly minEggsToSkip: number = 2;
@ -61,12 +60,12 @@ export class EggLapsePhase extends Phase {
true, true,
); );
} else if (eggsToHatchCount >= this.minEggsToSkip && globalScene.eggSkipPreference === 2) { } else if (eggsToHatchCount >= this.minEggsToSkip && globalScene.eggSkipPreference === 2) {
globalScene.queueMessage(i18next.t("battle:eggHatching")); globalScene.phaseManager.queueMessage(i18next.t("battle:eggHatching"));
this.hatchEggsSkipped(eggsToHatch); this.hatchEggsSkipped(eggsToHatch);
this.showSummary(); this.showSummary();
} else { } else {
// regular hatches, no summary // regular hatches, no summary
globalScene.queueMessage(i18next.t("battle:eggHatching")); globalScene.phaseManager.queueMessage(i18next.t("battle:eggHatching"));
this.hatchEggsRegular(eggsToHatch); this.hatchEggsRegular(eggsToHatch);
this.end(); this.end();
} }
@ -82,7 +81,7 @@ export class EggLapsePhase extends Phase {
hatchEggsRegular(eggsToHatch: Egg[]) { hatchEggsRegular(eggsToHatch: Egg[]) {
let eggsToHatchCount: number = eggsToHatch.length; let eggsToHatchCount: number = eggsToHatch.length;
for (const egg of eggsToHatch) { for (const egg of eggsToHatch) {
globalScene.unshiftPhase(new EggHatchPhase(this, egg, eggsToHatchCount)); globalScene.phaseManager.unshiftNew("EggHatchPhase", this, egg, eggsToHatchCount);
eggsToHatchCount--; eggsToHatchCount--;
} }
} }
@ -98,7 +97,7 @@ export class EggLapsePhase extends Phase {
} }
showSummary() { showSummary() {
globalScene.unshiftPhase(new EggSummaryPhase(this.eggHatchData)); globalScene.phaseManager.unshiftNew("EggSummaryPhase", this.eggHatchData);
this.end(); this.end();
} }

View File

@ -9,6 +9,7 @@ import type { EggHatchData } from "#app/data/egg-hatch-data";
* Phase is handled mostly by the egg-hatch-scene-handler UI * Phase is handled mostly by the egg-hatch-scene-handler UI
*/ */
export class EggSummaryPhase extends Phase { export class EggSummaryPhase extends Phase {
public readonly phaseName = "EggSummaryPhase";
private eggHatchData: EggHatchData[]; private eggHatchData: EggHatchData[];
constructor(eggHatchData: EggHatchData[]) { constructor(eggHatchData: EggHatchData[]) {

View File

@ -23,15 +23,6 @@ import { BoostBugSpawnModifier, IvScannerModifier, TurnHeldItemTransferModifier
import { ModifierPoolType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { ModifierPoolType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { BattlePhase } from "#app/phases/battle-phase"; import { BattlePhase } from "#app/phases/battle-phase";
import { CheckSwitchPhase } from "#app/phases/check-switch-phase";
import { GameOverPhase } from "#app/phases/game-over-phase";
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
import { PostSummonPhase } from "#app/phases/post-summon-phase";
import { ReturnPhase } from "#app/phases/return-phase";
import { ScanIvsPhase } from "#app/phases/scan-ivs-phase";
import { ShinySparklePhase } from "#app/phases/shiny-sparkle-phase";
import { SummonPhase } from "#app/phases/summon-phase";
import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase";
import { achvs } from "#app/system/achv"; import { achvs } from "#app/system/achv";
import { handleTutorial, Tutorial } from "#app/tutorial"; import { handleTutorial, Tutorial } from "#app/tutorial";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
@ -47,6 +38,8 @@ import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mys
import { getNatureName } from "#app/data/nature"; import { getNatureName } from "#app/data/nature";
export class EncounterPhase extends BattlePhase { export class EncounterPhase extends BattlePhase {
// Union type is necessary as this is subclassed, and typescript will otherwise complain
public readonly phaseName: "EncounterPhase" | "NextEncounterPhase" | "NewBiomeEncounterPhase" = "EncounterPhase";
private loaded: boolean; private loaded: boolean;
constructor(loaded = false) { constructor(loaded = false) {
@ -66,7 +59,7 @@ export class EncounterPhase extends BattlePhase {
// Failsafe if players somehow skip floor 200 in classic mode // Failsafe if players somehow skip floor 200 in classic mode
if (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex > 200) { if (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex > 200) {
globalScene.unshiftPhase(new GameOverPhase()); globalScene.phaseManager.unshiftNew("GameOverPhase");
} }
const loadEnemyAssets: Promise<void>[] = []; const loadEnemyAssets: Promise<void>[] = [];
@ -436,9 +429,9 @@ export class EncounterPhase extends BattlePhase {
const doTrainerSummon = () => { const doTrainerSummon = () => {
this.hideEnemyTrainer(); this.hideEnemyTrainer();
const availablePartyMembers = globalScene.getEnemyParty().filter(p => !p.isFainted()).length; const availablePartyMembers = globalScene.getEnemyParty().filter(p => !p.isFainted()).length;
globalScene.unshiftPhase(new SummonPhase(0, false)); globalScene.phaseManager.unshiftNew("SummonPhase", 0, false);
if (globalScene.currentBattle.double && availablePartyMembers > 1) { if (globalScene.currentBattle.double && availablePartyMembers > 1) {
globalScene.unshiftPhase(new SummonPhase(1, false)); globalScene.phaseManager.unshiftNew("SummonPhase", 1, false);
} }
this.end(); this.end();
}; };
@ -494,7 +487,7 @@ export class EncounterPhase extends BattlePhase {
globalScene.ui.clearText(); globalScene.ui.clearText();
globalScene.ui.getMessageHandler().hideNameText(); globalScene.ui.getMessageHandler().hideNameText();
globalScene.unshiftPhase(new MysteryEncounterPhase()); globalScene.phaseManager.unshiftNew("MysteryEncounterPhase");
this.end(); this.end();
}; };
@ -552,7 +545,7 @@ export class EncounterPhase extends BattlePhase {
enemyField.forEach((enemyPokemon, e) => { enemyField.forEach((enemyPokemon, e) => {
if (enemyPokemon.isShiny(true)) { if (enemyPokemon.isShiny(true)) {
globalScene.unshiftPhase(new ShinySparklePhase(BattlerIndex.ENEMY + e)); globalScene.phaseManager.unshiftNew("ShinySparklePhase", BattlerIndex.ENEMY + e);
} }
/** This sets Eternatus' held item to be untransferrable, preventing it from being stolen */ /** This sets Eternatus' held item to be untransferrable, preventing it from being stolen */
if ( if (
@ -574,25 +567,31 @@ export class EncounterPhase extends BattlePhase {
if (![BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(globalScene.currentBattle.battleType)) { if (![BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(globalScene.currentBattle.battleType)) {
enemyField.map(p => enemyField.map(p =>
globalScene.pushConditionalPhase(new PostSummonPhase(p.getBattlerIndex()), () => { globalScene.phaseManager.pushConditionalPhase(
// if there is not a player party, we can't continue globalScene.phaseManager.create("PostSummonPhase", p.getBattlerIndex()),
if (!globalScene.getPlayerParty().length) { () => {
return false; // if there is not a player party, we can't continue
} if (!globalScene.getPlayerParty().length) {
// how many player pokemon are on the field ? return false;
const pokemonsOnFieldCount = globalScene.getPlayerParty().filter(p => p.isOnField()).length; }
// if it's a 2vs1, there will never be a 2nd pokemon on our field even // how many player pokemon are on the field ?
const requiredPokemonsOnField = Math.min(globalScene.getPlayerParty().filter(p => !p.isFainted()).length, 2); const pokemonsOnFieldCount = globalScene.getPlayerParty().filter(p => p.isOnField()).length;
// if it's a double, there should be 2, otherwise 1 // if it's a 2vs1, there will never be a 2nd pokemon on our field even
if (globalScene.currentBattle.double) { const requiredPokemonsOnField = Math.min(
return pokemonsOnFieldCount === requiredPokemonsOnField; globalScene.getPlayerParty().filter(p => !p.isFainted()).length,
} 2,
return pokemonsOnFieldCount === 1; );
}), // if it's a double, there should be 2, otherwise 1
if (globalScene.currentBattle.double) {
return pokemonsOnFieldCount === requiredPokemonsOnField;
}
return pokemonsOnFieldCount === 1;
},
),
); );
const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier); const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier);
if (ivScannerModifier) { if (ivScannerModifier) {
enemyField.map(p => globalScene.pushPhase(new ScanIvsPhase(p.getBattlerIndex()))); enemyField.map(p => globalScene.phaseManager.pushNew("ScanIvsPhase", p.getBattlerIndex()));
} }
} }
@ -600,21 +599,21 @@ export class EncounterPhase extends BattlePhase {
const availablePartyMembers = globalScene.getPokemonAllowedInBattle(); const availablePartyMembers = globalScene.getPokemonAllowedInBattle();
if (!availablePartyMembers[0].isOnField()) { if (!availablePartyMembers[0].isOnField()) {
globalScene.pushPhase(new SummonPhase(0)); globalScene.phaseManager.pushNew("SummonPhase", 0);
} }
if (globalScene.currentBattle.double) { if (globalScene.currentBattle.double) {
if (availablePartyMembers.length > 1) { if (availablePartyMembers.length > 1) {
globalScene.pushPhase(new ToggleDoublePositionPhase(true)); globalScene.phaseManager.pushNew("ToggleDoublePositionPhase", true);
if (!availablePartyMembers[1].isOnField()) { if (!availablePartyMembers[1].isOnField()) {
globalScene.pushPhase(new SummonPhase(1)); globalScene.phaseManager.pushNew("SummonPhase", 1);
} }
} }
} else { } else {
if (availablePartyMembers.length > 1 && availablePartyMembers[1].isOnField()) { if (availablePartyMembers.length > 1 && availablePartyMembers[1].isOnField()) {
globalScene.pushPhase(new ReturnPhase(1)); globalScene.phaseManager.pushNew("ReturnPhase", 1);
} }
globalScene.pushPhase(new ToggleDoublePositionPhase(false)); globalScene.phaseManager.pushNew("ToggleDoublePositionPhase", false);
} }
if ( if (
@ -623,9 +622,9 @@ export class EncounterPhase extends BattlePhase {
) { ) {
const minPartySize = globalScene.currentBattle.double ? 2 : 1; const minPartySize = globalScene.currentBattle.double ? 2 : 1;
if (availablePartyMembers.length > minPartySize) { if (availablePartyMembers.length > minPartySize) {
globalScene.pushPhase(new CheckSwitchPhase(0, globalScene.currentBattle.double)); globalScene.phaseManager.pushNew("CheckSwitchPhase", 0, globalScene.currentBattle.double);
if (globalScene.currentBattle.double) { if (globalScene.currentBattle.double) {
globalScene.pushPhase(new CheckSwitchPhase(1, globalScene.currentBattle.double)); globalScene.phaseManager.pushNew("CheckSwitchPhase", 1, globalScene.currentBattle.double);
} }
} }
} }

View File

@ -5,6 +5,7 @@ import { addTextObject, TextStyle } from "#app/ui/text";
import i18next from "i18next"; import i18next from "i18next";
export class EndCardPhase extends Phase { export class EndCardPhase extends Phase {
public readonly phaseName = "EndCardPhase";
public endCard: Phaser.GameObjects.Image; public endCard: Phaser.GameObjects.Image;
public text: Phaser.GameObjects.Text; public text: Phaser.GameObjects.Text;
start(): void { start(): void {

View File

@ -3,6 +3,7 @@ import { Phase } from "#app/phase";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
export class EndEvolutionPhase extends Phase { export class EndEvolutionPhase extends Phase {
public readonly phaseName = "EndEvolutionPhase";
start() { start() {
super.start(); super.start();

View File

@ -15,6 +15,7 @@ import { BattlerTagType } from "#enums/battler-tag-type";
* @see {@linkcode EnemyPokemon.getNextMove} * @see {@linkcode EnemyPokemon.getNextMove}
*/ */
export class EnemyCommandPhase extends FieldPhase { export class EnemyCommandPhase extends FieldPhase {
public readonly phaseName = "EnemyCommandPhase";
protected fieldIndex: number; protected fieldIndex: number;
protected skipTurn = false; protected skipTurn = false;

View File

@ -14,11 +14,12 @@ import { LearnMoveSituation } from "#app/field/pokemon";
import { getTypeRgb } from "#app/data/type"; import { getTypeRgb } from "#app/data/type";
import i18next from "i18next"; import i18next from "i18next";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { EndEvolutionPhase } from "#app/phases/end-evolution-phase";
import { EVOLVE_MOVE } from "#app/data/balance/pokemon-level-moves"; import { EVOLVE_MOVE } from "#app/data/balance/pokemon-level-moves";
export class EvolutionPhase extends Phase { export class EvolutionPhase extends Phase {
// FormChangePhase inherits from this, but EvolutionPhase is not abstract.
// We have to use the union here
public readonly phaseName: "EvolutionPhase" | "FormChangePhase" = "EvolutionPhase";
protected pokemon: PlayerPokemon; protected pokemon: PlayerPokemon;
protected lastLevel: number; protected lastLevel: number;
@ -259,7 +260,7 @@ export class EvolutionPhase extends Phase {
SoundFade.fadeOut(globalScene, this.evolutionBgm, 100); SoundFade.fadeOut(globalScene, this.evolutionBgm, 100);
globalScene.unshiftPhase(new EndEvolutionPhase()); globalScene.phaseManager.unshiftNew("EndEvolutionPhase");
globalScene.ui.showText( globalScene.ui.showText(
i18next.t("menu:stoppedEvolving", { i18next.t("menu:stoppedEvolving", {
@ -352,9 +353,13 @@ export class EvolutionPhase extends Phase {
.getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation) .getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation)
.filter(lm => lm[0] === EVOLVE_MOVE); .filter(lm => lm[0] === EVOLVE_MOVE);
for (const lm of levelMoves) { for (const lm of levelMoves) {
globalScene.unshiftPhase(new LearnMovePhase(globalScene.getPlayerParty().indexOf(this.pokemon), lm[1])); globalScene.phaseManager.unshiftNew(
"LearnMovePhase",
globalScene.getPlayerParty().indexOf(this.pokemon),
lm[1],
);
} }
globalScene.unshiftPhase(new EndEvolutionPhase()); globalScene.phaseManager.unshiftNew("EndEvolutionPhase");
globalScene.playSound("se/shine"); globalScene.playSound("se/shine");
this.doSpray(); this.doSpray();

View File

@ -4,9 +4,9 @@ import { ExpBoosterModifier } from "#app/modifier/modifier";
import i18next from "i18next"; import i18next from "i18next";
import { NumberHolder } from "#app/utils/common"; import { NumberHolder } from "#app/utils/common";
import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase";
import { LevelUpPhase } from "./level-up-phase";
export class ExpPhase extends PlayerPartyMemberPokemonPhase { export class ExpPhase extends PlayerPartyMemberPokemonPhase {
public readonly phaseName = "ExpPhase";
private expValue: number; private expValue: number;
constructor(partyMemberIndex: number, expValue: number) { constructor(partyMemberIndex: number, expValue: number) {
@ -33,7 +33,7 @@ export class ExpPhase extends PlayerPartyMemberPokemonPhase {
pokemon.addExp(exp.value); pokemon.addExp(exp.value);
const newLevel = pokemon.level; const newLevel = pokemon.level;
if (newLevel > lastLevel) { if (newLevel > lastLevel) {
globalScene.unshiftPhase(new LevelUpPhase(this.partyMemberIndex, lastLevel, newLevel)); globalScene.phaseManager.unshiftNew("LevelUpPhase", this.partyMemberIndex, lastLevel, newLevel);
} }
pokemon.updateInfo().then(() => this.end()); pokemon.updateInfo().then(() => this.end());
}, },

View File

@ -18,23 +18,19 @@ import { BattleSpec } from "#app/enums/battle-spec";
import { StatusEffect } from "#app/enums/status-effect"; import { StatusEffect } from "#app/enums/status-effect";
import type { EnemyPokemon } from "#app/field/pokemon"; import type { EnemyPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { HitResult, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { HitResult, PokemonMove } from "#app/field/pokemon";
import type { PlayerPokemon } from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { PokemonInstantReviveModifier } from "#app/modifier/modifier";
import { SwitchType } from "#enums/switch-type"; import { SwitchType } from "#enums/switch-type";
import i18next from "i18next"; import i18next from "i18next";
import { DamageAnimPhase } from "./damage-anim-phase";
import { GameOverPhase } from "./game-over-phase";
import { PokemonPhase } from "./pokemon-phase"; import { PokemonPhase } from "./pokemon-phase";
import { SwitchPhase } from "./switch-phase";
import { SwitchSummonPhase } from "./switch-summon-phase";
import { ToggleDoublePositionPhase } from "./toggle-double-position-phase";
import { VictoryPhase } from "./victory-phase";
import { isNullOrUndefined } from "#app/utils/common"; import { isNullOrUndefined } from "#app/utils/common";
import { FRIENDSHIP_LOSS_FROM_FAINT } from "#app/data/balance/starters"; import { FRIENDSHIP_LOSS_FROM_FAINT } from "#app/data/balance/starters";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
export class FaintPhase extends PokemonPhase { export class FaintPhase extends PokemonPhase {
public readonly phaseName = "FaintPhase";
/** /**
* Whether or not instant revive should be prevented * Whether or not instant revive should be prevented
*/ */
@ -78,10 +74,15 @@ export class FaintPhase extends PokemonPhase {
} }
} }
/** In case the current pokemon was just switched in, make sure it is counted as participating in the combat */ /**
* In case the current pokemon was just switched in, make sure it is counted as participating in the combat.
* For EXP_SHARE purposes, if the current pokemon faints as the combat ends and it was the ONLY player pokemon
* involved in combat, it needs to be counted as a participant so the other party pokemon can get their EXP,
* so the fainted pokemon has been included.
*/
for (const pokemon of globalScene.getPlayerField()) { for (const pokemon of globalScene.getPlayerField()) {
if (pokemon?.isActive(true) && pokemon.isPlayer()) { if (pokemon?.isActive() || pokemon?.isFainted()) {
globalScene.currentBattle.addParticipant(pokemon as PlayerPokemon); globalScene.currentBattle.addParticipant(pokemon);
} }
} }
@ -108,7 +109,7 @@ export class FaintPhase extends PokemonPhase {
}); });
} }
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("battle:fainted", { i18next.t("battle:fainted", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), }),
@ -159,7 +160,7 @@ export class FaintPhase extends PokemonPhase {
const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true)); const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true));
if (!legalPlayerPokemon.length) { if (!legalPlayerPokemon.length) {
/** If the player doesn't have any legal Pokemon, end the game */ /** If the player doesn't have any legal Pokemon, end the game */
globalScene.unshiftPhase(new GameOverPhase()); globalScene.phaseManager.unshiftNew("GameOverPhase");
} else if ( } else if (
globalScene.currentBattle.double && globalScene.currentBattle.double &&
legalPlayerPokemon.length === 1 && legalPlayerPokemon.length === 1 &&
@ -169,23 +170,23 @@ export class FaintPhase extends PokemonPhase {
* If the player has exactly one Pokemon in total at this point in a double battle, and that Pokemon * If the player has exactly one Pokemon in total at this point in a double battle, and that Pokemon
* is already on the field, unshift a phase that moves that Pokemon to center position. * is already on the field, unshift a phase that moves that Pokemon to center position.
*/ */
globalScene.unshiftPhase(new ToggleDoublePositionPhase(true)); globalScene.phaseManager.unshiftNew("ToggleDoublePositionPhase", true);
} else if (legalPlayerPartyPokemon.length > 0) { } else if (legalPlayerPartyPokemon.length > 0) {
/** /**
* If previous conditions weren't met, and the player has at least 1 legal Pokemon off the field, * If previous conditions weren't met, and the player has at least 1 legal Pokemon off the field,
* push a phase that prompts the player to summon a Pokemon from their party. * push a phase that prompts the player to summon a Pokemon from their party.
*/ */
globalScene.pushPhase(new SwitchPhase(SwitchType.SWITCH, this.fieldIndex, true, false)); globalScene.phaseManager.pushNew("SwitchPhase", SwitchType.SWITCH, this.fieldIndex, true, false);
} }
} else { } else {
globalScene.unshiftPhase(new VictoryPhase(this.battlerIndex)); globalScene.phaseManager.unshiftNew("VictoryPhase", this.battlerIndex);
if ([BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(globalScene.currentBattle.battleType)) { if ([BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(globalScene.currentBattle.battleType)) {
const hasReservePartyMember = !!globalScene const hasReservePartyMember = !!globalScene
.getEnemyParty() .getEnemyParty()
.filter(p => p.isActive() && !p.isOnField() && p.trainerSlot === (pokemon as EnemyPokemon).trainerSlot) .filter(p => p.isActive() && !p.isOnField() && p.trainerSlot === (pokemon as EnemyPokemon).trainerSlot)
.length; .length;
if (hasReservePartyMember) { if (hasReservePartyMember) {
globalScene.pushPhase(new SwitchSummonPhase(SwitchType.SWITCH, this.fieldIndex, -1, false, false)); globalScene.phaseManager.pushNew("SwitchSummonPhase", SwitchType.SWITCH, this.fieldIndex, -1, false, false);
} }
} }
} }
@ -197,7 +198,7 @@ export class FaintPhase extends PokemonPhase {
} }
pokemon.faintCry(() => { pokemon.faintCry(() => {
if (pokemon instanceof PlayerPokemon) { if (pokemon.isPlayer()) {
pokemon.addFriendship(-FRIENDSHIP_LOSS_FROM_FAINT); pokemon.addFriendship(-FRIENDSHIP_LOSS_FROM_FAINT);
} }
pokemon.hideInfo(); pokemon.hideInfo();
@ -240,7 +241,7 @@ export class FaintPhase extends PokemonPhase {
} else { } else {
// Final boss' HP threshold has been bypassed; cancel faint and force check for 2nd phase // Final boss' HP threshold has been bypassed; cancel faint and force check for 2nd phase
enemy.hp++; enemy.hp++;
globalScene.unshiftPhase(new DamageAnimPhase(enemy.getBattlerIndex(), 0, HitResult.INDIRECT)); globalScene.phaseManager.unshiftNew("DamageAnimPhase", enemy.getBattlerIndex(), 0, HitResult.INDIRECT);
this.end(); this.end();
} }
return true; return true;

View File

@ -7,12 +7,12 @@ import type { PlayerPokemon } from "../field/pokemon";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import type PartyUiHandler from "../ui/party-ui-handler"; import type PartyUiHandler from "../ui/party-ui-handler";
import { getPokemonNameWithAffix } from "../messages"; import { getPokemonNameWithAffix } from "../messages";
import { EndEvolutionPhase } from "./end-evolution-phase";
import { EvolutionPhase } from "./evolution-phase"; import { EvolutionPhase } from "./evolution-phase";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { SpeciesFormKey } from "#enums/species-form-key"; import { SpeciesFormKey } from "#enums/species-form-key";
export class FormChangePhase extends EvolutionPhase { export class FormChangePhase extends EvolutionPhase {
public readonly phaseName = "FormChangePhase";
private formChange: SpeciesFormChange; private formChange: SpeciesFormChange;
private modal: boolean; private modal: boolean;
@ -99,7 +99,7 @@ export class FormChangePhase extends EvolutionPhase {
globalScene.time.delayedCall(900, () => { globalScene.time.delayedCall(900, () => {
this.pokemon.changeForm(this.formChange).then(() => { this.pokemon.changeForm(this.formChange).then(() => {
if (!this.modal) { if (!this.modal) {
globalScene.unshiftPhase(new EndEvolutionPhase()); globalScene.phaseManager.unshiftNew("EndEvolutionPhase");
} }
globalScene.playSound("se/shine"); globalScene.playSound("se/shine");

View File

@ -4,6 +4,7 @@ import i18next from "i18next";
import { ModifierRewardPhase } from "./modifier-reward-phase"; import { ModifierRewardPhase } from "./modifier-reward-phase";
export class GameOverModifierRewardPhase extends ModifierRewardPhase { export class GameOverModifierRewardPhase extends ModifierRewardPhase {
public readonly phaseName = "GameOverModifierRewardPhase";
doReward(): Promise<void> { doReward(): Promise<void> {
return new Promise<void>(resolve => { return new Promise<void>(resolve => {
const newModifier = this.modifierType.newModifier(); const newModifier = this.modifierType.newModifier();

View File

@ -9,14 +9,7 @@ import { trainerConfigs } from "#app/data/trainers/trainer-config";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { BattlePhase } from "#app/phases/battle-phase"; import { BattlePhase } from "#app/phases/battle-phase";
import { CheckSwitchPhase } from "#app/phases/check-switch-phase"; import type { EndCardPhase } from "#app/phases/end-card-phase";
import { EncounterPhase } from "#app/phases/encounter-phase";
import { EndCardPhase } from "#app/phases/end-card-phase";
import { GameOverModifierRewardPhase } from "#app/phases/game-over-modifier-reward-phase";
import { PostGameOverPhase } from "#app/phases/post-game-over-phase";
import { RibbonModifierRewardPhase } from "#app/phases/ribbon-modifier-reward-phase";
import { SummonPhase } from "#app/phases/summon-phase";
import { UnlockPhase } from "#app/phases/unlock-phase";
import { achvs, ChallengeAchv } from "#app/system/achv"; import { achvs, ChallengeAchv } from "#app/system/achv";
import { Unlockables } from "#app/system/unlockables"; import { Unlockables } from "#app/system/unlockables";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
@ -31,9 +24,9 @@ import ChallengeData from "#app/system/challenge-data";
import TrainerData from "#app/system/trainer-data"; import TrainerData from "#app/system/trainer-data";
import ArenaData from "#app/system/arena-data"; import ArenaData from "#app/system/arena-data";
import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
import { MessagePhase } from "./message-phase";
export class GameOverPhase extends BattlePhase { export class GameOverPhase extends BattlePhase {
public readonly phaseName = "GameOverPhase";
private isVictory: boolean; private isVictory: boolean;
private firstRibbons: PokemonSpecies[] = []; private firstRibbons: PokemonSpecies[] = [];
@ -46,7 +39,7 @@ export class GameOverPhase extends BattlePhase {
start() { start() {
super.start(); super.start();
globalScene.hideAbilityBar(); globalScene.phaseManager.hideAbilityBar();
// Failsafe if players somehow skip floor 200 in classic mode // Failsafe if players somehow skip floor 200 in classic mode
if (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex > 200) { if (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex > 200) {
@ -83,23 +76,23 @@ export class GameOverPhase extends BattlePhase {
() => { () => {
globalScene.ui.fadeOut(1250).then(() => { globalScene.ui.fadeOut(1250).then(() => {
globalScene.reset(); globalScene.reset();
globalScene.clearPhaseQueue(); globalScene.phaseManager.clearPhaseQueue();
globalScene.gameData.loadSession(globalScene.sessionSlotId).then(() => { globalScene.gameData.loadSession(globalScene.sessionSlotId).then(() => {
globalScene.pushPhase(new EncounterPhase(true)); globalScene.phaseManager.pushNew("EncounterPhase", true);
const availablePartyMembers = globalScene.getPokemonAllowedInBattle().length; const availablePartyMembers = globalScene.getPokemonAllowedInBattle().length;
globalScene.pushPhase(new SummonPhase(0)); globalScene.phaseManager.pushNew("SummonPhase", 0);
if (globalScene.currentBattle.double && availablePartyMembers > 1) { if (globalScene.currentBattle.double && availablePartyMembers > 1) {
globalScene.pushPhase(new SummonPhase(1)); globalScene.phaseManager.pushNew("SummonPhase", 1);
} }
if ( if (
globalScene.currentBattle.waveIndex > 1 && globalScene.currentBattle.waveIndex > 1 &&
globalScene.currentBattle.battleType !== BattleType.TRAINER globalScene.currentBattle.battleType !== BattleType.TRAINER
) { ) {
globalScene.pushPhase(new CheckSwitchPhase(0, globalScene.currentBattle.double)); globalScene.phaseManager.pushNew("CheckSwitchPhase", 0, globalScene.currentBattle.double);
if (globalScene.currentBattle.double && availablePartyMembers > 1) { if (globalScene.currentBattle.double && availablePartyMembers > 1) {
globalScene.pushPhase(new CheckSwitchPhase(1, globalScene.currentBattle.double)); globalScene.phaseManager.pushNew("CheckSwitchPhase", 1, globalScene.currentBattle.double);
} }
} }
@ -147,7 +140,7 @@ export class GameOverPhase extends BattlePhase {
globalScene.ui.fadeOut(fadeDuration).then(() => { globalScene.ui.fadeOut(fadeDuration).then(() => {
activeBattlers.map(a => a.setVisible(false)); activeBattlers.map(a => a.setVisible(false));
globalScene.setFieldScale(1, true); globalScene.setFieldScale(1, true);
globalScene.clearPhaseQueue(); globalScene.phaseManager.clearPhaseQueue();
globalScene.ui.clearText(); globalScene.ui.clearText();
if (this.isVictory && globalScene.gameMode.isChallenge) { if (this.isVictory && globalScene.gameMode.isChallenge) {
@ -159,15 +152,15 @@ export class GameOverPhase extends BattlePhase {
this.handleUnlocks(); this.handleUnlocks();
for (const species of this.firstRibbons) { for (const species of this.firstRibbons) {
globalScene.unshiftPhase(new RibbonModifierRewardPhase(modifierTypes.VOUCHER_PLUS, species)); globalScene.phaseManager.unshiftNew("RibbonModifierRewardPhase", modifierTypes.VOUCHER_PLUS, species);
} }
if (!firstClear) { if (!firstClear) {
globalScene.unshiftPhase(new GameOverModifierRewardPhase(modifierTypes.VOUCHER_PREMIUM)); globalScene.phaseManager.unshiftNew("GameOverModifierRewardPhase", modifierTypes.VOUCHER_PREMIUM);
} }
} }
this.getRunHistoryEntry().then(runHistoryEntry => { this.getRunHistoryEntry().then(runHistoryEntry => {
globalScene.gameData.saveRunHistory(runHistoryEntry, this.isVictory); globalScene.gameData.saveRunHistory(runHistoryEntry, this.isVictory);
globalScene.pushPhase(new PostGameOverPhase(endCardPhase)); globalScene.phaseManager.pushNew("PostGameOverPhase", endCardPhase);
this.end(); this.end();
}); });
}; };
@ -196,8 +189,8 @@ export class GameOverPhase extends BattlePhase {
() => { () => {
globalScene.ui.fadeOut(500).then(() => { globalScene.ui.fadeOut(500).then(() => {
globalScene.charSprite.hide().then(() => { globalScene.charSprite.hide().then(() => {
const endCardPhase = new EndCardPhase(); const endCardPhase = globalScene.phaseManager.create("EndCardPhase");
globalScene.unshiftPhase(endCardPhase); globalScene.phaseManager.unshiftPhase(endCardPhase);
clear(endCardPhase); clear(endCardPhase);
}); });
}); });
@ -206,8 +199,8 @@ export class GameOverPhase extends BattlePhase {
}); });
}); });
} else { } else {
const endCardPhase = new EndCardPhase(); const endCardPhase = globalScene.phaseManager.create("EndCardPhase");
globalScene.unshiftPhase(endCardPhase); globalScene.phaseManager.unshiftPhase(endCardPhase);
clear(endCardPhase); clear(endCardPhase);
} }
} else { } else {
@ -229,9 +222,9 @@ export class GameOverPhase extends BattlePhase {
}) })
.then(success => doGameOver(!globalScene.gameMode.isDaily || !!success)) .then(success => doGameOver(!globalScene.gameMode.isDaily || !!success))
.catch(_err => { .catch(_err => {
globalScene.clearPhaseQueue(); globalScene.phaseManager.clearPhaseQueue();
globalScene.clearPhaseQueueSplice(); globalScene.phaseManager.clearPhaseQueueSplice();
globalScene.unshiftPhase(new MessagePhase(i18next.t("menu:serverCommunicationFailed"), 2500)); globalScene.phaseManager.unshiftNew("MessagePhase", i18next.t("menu:serverCommunicationFailed"), 2500);
// force the game to reload after 2 seconds. // force the game to reload after 2 seconds.
setTimeout(() => { setTimeout(() => {
window.location.reload(); window.location.reload();
@ -250,22 +243,22 @@ export class GameOverPhase extends BattlePhase {
handleUnlocks(): void { handleUnlocks(): void {
if (this.isVictory && globalScene.gameMode.isClassic) { if (this.isVictory && globalScene.gameMode.isClassic) {
if (!globalScene.gameData.unlocks[Unlockables.ENDLESS_MODE]) { if (!globalScene.gameData.unlocks[Unlockables.ENDLESS_MODE]) {
globalScene.unshiftPhase(new UnlockPhase(Unlockables.ENDLESS_MODE)); globalScene.phaseManager.unshiftNew("UnlockPhase", Unlockables.ENDLESS_MODE);
} }
if ( if (
globalScene.getPlayerParty().filter(p => p.fusionSpecies).length && globalScene.getPlayerParty().filter(p => p.fusionSpecies).length &&
!globalScene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE] !globalScene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]
) { ) {
globalScene.unshiftPhase(new UnlockPhase(Unlockables.SPLICED_ENDLESS_MODE)); globalScene.phaseManager.unshiftNew("UnlockPhase", Unlockables.SPLICED_ENDLESS_MODE);
} }
if (!globalScene.gameData.unlocks[Unlockables.MINI_BLACK_HOLE]) { if (!globalScene.gameData.unlocks[Unlockables.MINI_BLACK_HOLE]) {
globalScene.unshiftPhase(new UnlockPhase(Unlockables.MINI_BLACK_HOLE)); globalScene.phaseManager.unshiftNew("UnlockPhase", Unlockables.MINI_BLACK_HOLE);
} }
if ( if (
!globalScene.gameData.unlocks[Unlockables.EVIOLITE] && !globalScene.gameData.unlocks[Unlockables.EVIOLITE] &&
globalScene.getPlayerParty().some(p => p.getSpeciesForm(true).speciesId in pokemonEvolutions) globalScene.getPlayerParty().some(p => p.getSpeciesForm(true).speciesId in pokemonEvolutions)
) { ) {
globalScene.unshiftPhase(new UnlockPhase(Unlockables.EVIOLITE)); globalScene.phaseManager.unshiftNew("UnlockPhase", Unlockables.EVIOLITE);
} }
} }
} }

View File

@ -2,6 +2,7 @@ import { globalScene } from "#app/global-scene";
import { Phase } from "#app/phase"; import { Phase } from "#app/phase";
export class HideAbilityPhase extends Phase { export class HideAbilityPhase extends Phase {
public readonly phaseName = "HideAbilityPhase";
start() { start() {
super.start(); super.start();

View File

@ -2,6 +2,7 @@ import { globalScene } from "#app/global-scene";
import { BattlePhase } from "./battle-phase"; import { BattlePhase } from "./battle-phase";
export class HidePartyExpBarPhase extends BattlePhase { export class HidePartyExpBarPhase extends BattlePhase {
public readonly phaseName = "HidePartyExpBarPhase";
start() { start() {
super.start(); super.start();

View File

@ -12,7 +12,6 @@ import { UiMode } from "#enums/ui-mode";
import i18next from "i18next"; import i18next from "i18next";
import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase"; import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
export enum LearnMoveType { export enum LearnMoveType {
/** For learning a move via level-up, evolution, or other non-item-based event */ /** For learning a move via level-up, evolution, or other non-item-based event */
@ -24,6 +23,7 @@ export enum LearnMoveType {
} }
export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
public readonly phaseName = "LearnMovePhase";
private moveId: MoveId; private moveId: MoveId;
private messageMode: UiMode; private messageMode: UiMode;
private learnMoveType: LearnMoveType; private learnMoveType: LearnMoveType;
@ -195,7 +195,7 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
pokemon.usedTMs = []; pokemon.usedTMs = [];
} }
pokemon.usedTMs.push(this.moveId); pokemon.usedTMs.push(this.moveId);
globalScene.tryRemovePhase(phase => phase instanceof SelectModifierPhase); globalScene.phaseManager.tryRemovePhase(phase => phase.is("SelectModifierPhase"));
} else if (this.learnMoveType === LearnMoveType.MEMORY) { } else if (this.learnMoveType === LearnMoveType.MEMORY) {
if (this.cost !== -1) { if (this.cost !== -1) {
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
@ -205,7 +205,7 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
} }
globalScene.playSound("se/buy"); globalScene.playSound("se/buy");
} else { } else {
globalScene.tryRemovePhase(phase => phase instanceof SelectModifierPhase); globalScene.phaseManager.tryRemovePhase(phase => phase.is("SelectModifierPhase"));
} }
} }
pokemon.setMove(index, this.moveId); pokemon.setMove(index, this.moveId);

View File

@ -4,6 +4,7 @@ import i18next from "i18next";
import { FieldPhase } from "./field-phase"; import { FieldPhase } from "./field-phase";
export class LevelCapPhase extends FieldPhase { export class LevelCapPhase extends FieldPhase {
public readonly phaseName = "LevelCapPhase";
start(): void { start(): void {
super.start(); super.start();

View File

@ -2,14 +2,13 @@ import { globalScene } from "#app/global-scene";
import { ExpNotification } from "#app/enums/exp-notification"; import { ExpNotification } from "#app/enums/exp-notification";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { EvolutionPhase } from "#app/phases/evolution-phase";
import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase"; import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase";
import { LevelAchv } from "#app/system/achv"; import { LevelAchv } from "#app/system/achv";
import { NumberHolder } from "#app/utils/common"; import { NumberHolder } from "#app/utils/common";
import i18next from "i18next"; import i18next from "i18next";
export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { export class LevelUpPhase extends PlayerPartyMemberPokemonPhase {
public readonly phaseName = "LevelUpPhase";
protected lastLevel: number; protected lastLevel: number;
protected level: number; protected level: number;
protected pokemon: PlayerPokemon = this.getPlayerPokemon(); protected pokemon: PlayerPokemon = this.getPlayerPokemon();
@ -65,14 +64,14 @@ export class LevelUpPhase extends PlayerPartyMemberPokemonPhase {
// this feels like an unnecessary optimization // this feels like an unnecessary optimization
const levelMoves = this.getPokemon().getLevelMoves(this.lastLevel + 1); const levelMoves = this.getPokemon().getLevelMoves(this.lastLevel + 1);
for (const lm of levelMoves) { for (const lm of levelMoves) {
globalScene.unshiftPhase(new LearnMovePhase(this.partyMemberIndex, lm[1])); globalScene.phaseManager.unshiftNew("LearnMovePhase", this.partyMemberIndex, lm[1]);
} }
} }
if (!this.pokemon.pauseEvolutions) { if (!this.pokemon.pauseEvolutions) {
const evolution = this.pokemon.getEvolution(); const evolution = this.pokemon.getEvolution();
if (evolution) { if (evolution) {
this.pokemon.breakIllusion(); this.pokemon.breakIllusion();
globalScene.unshiftPhase(new EvolutionPhase(this.pokemon, evolution, this.lastLevel)); globalScene.phaseManager.unshiftNew("EvolutionPhase", this.pokemon, evolution, this.lastLevel);
} }
} }
return super.end(); return super.end();

View File

@ -8,6 +8,7 @@ import { Phase } from "#app/phase";
* isn't already loaded (e.g. for Metronome) * isn't already loaded (e.g. for Metronome)
*/ */
export class LoadMoveAnimPhase extends Phase { export class LoadMoveAnimPhase extends Phase {
public readonly phaseName = "LoadMoveAnimPhase";
constructor(protected moveId: MoveId) { constructor(protected moveId: MoveId) {
super(); super();
} }

View File

@ -7,10 +7,9 @@ import { UiMode } from "#enums/ui-mode";
import i18next, { t } from "i18next"; import i18next, { t } from "i18next";
import { sessionIdKey, executeIf } from "#app/utils/common"; import { sessionIdKey, executeIf } from "#app/utils/common";
import { getCookie, removeCookie } from "#app/utils/cookies"; import { getCookie, removeCookie } from "#app/utils/cookies";
import { SelectGenderPhase } from "./select-gender-phase";
import { UnavailablePhase } from "./unavailable-phase";
export class LoginPhase extends Phase { export class LoginPhase extends Phase {
public readonly phaseName = "LoginPhase";
private showText: boolean; private showText: boolean;
constructor(showText = true) { constructor(showText = true) {
@ -69,7 +68,7 @@ export class LoginPhase extends Phase {
}); });
}, },
() => { () => {
globalScene.unshiftPhase(new LoginPhase(false)); globalScene.phaseManager.unshiftNew("LoginPhase", false);
this.end(); this.end();
}, },
], ],
@ -93,7 +92,7 @@ export class LoginPhase extends Phase {
removeCookie(sessionIdKey); removeCookie(sessionIdKey);
globalScene.reset(true, true); globalScene.reset(true, true);
} else { } else {
globalScene.unshiftPhase(new UnavailablePhase()); globalScene.phaseManager.unshiftNew("UnavailablePhase");
super.end(); super.end();
} }
return null; return null;
@ -113,7 +112,7 @@ export class LoginPhase extends Phase {
globalScene.ui.setMode(UiMode.MESSAGE); globalScene.ui.setMode(UiMode.MESSAGE);
if (!globalScene.gameData.gender) { if (!globalScene.gameData.gender) {
globalScene.unshiftPhase(new SelectGenderPhase()); globalScene.phaseManager.unshiftNew("SelectGenderPhase");
} }
handleTutorial(Tutorial.Intro).then(() => super.end()); handleTutorial(Tutorial.Intro).then(() => super.end());

View File

@ -2,6 +2,7 @@ import { globalScene } from "#app/global-scene";
import { Phase } from "#app/phase"; import { Phase } from "#app/phase";
export class MessagePhase extends Phase { export class MessagePhase extends Phase {
public readonly phaseName = "MessagePhase";
private text: string; private text: string;
private callbackDelay?: number | null; private callbackDelay?: number | null;
private prompt?: boolean | null; private prompt?: boolean | null;
@ -43,8 +44,13 @@ export class MessagePhase extends Phase {
page0 = page0.split(repname[p]).join(pokename[p]); page0 = page0.split(repname[p]).join(pokename[p]);
page1 = page1.split(repname[p]).join(pokename[p]); page1 = page1.split(repname[p]).join(pokename[p]);
} }
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new MessagePhase(page1, this.callbackDelay, this.prompt, this.promptDelay, this.speaker), "MessagePhase",
page1,
this.callbackDelay,
this.prompt,
this.promptDelay,
this.speaker,
); );
this.text = page0.trim(); this.text = page0.trim();
} else { } else {

View File

@ -5,6 +5,10 @@ import i18next from "i18next";
import { BattlePhase } from "./battle-phase"; import { BattlePhase } from "./battle-phase";
export class ModifierRewardPhase extends BattlePhase { export class ModifierRewardPhase extends BattlePhase {
// RibbonModifierRewardPhase extends ModifierRewardPhase and to make typescript happy
// we need to use a union type here
public readonly phaseName: "ModifierRewardPhase" | "RibbonModifierRewardPhase" | "GameOverModifierRewardPhase" =
"ModifierRewardPhase";
protected modifierType: ModifierType; protected modifierType: ModifierType;
constructor(modifierTypeFunc: ModifierTypeFunc) { constructor(modifierTypeFunc: ModifierTypeFunc) {

View File

@ -6,6 +6,7 @@ import { NumberHolder } from "#app/utils/common";
import { BattlePhase } from "./battle-phase"; import { BattlePhase } from "./battle-phase";
export class MoneyRewardPhase extends BattlePhase { export class MoneyRewardPhase extends BattlePhase {
public readonly phaseName = "MoneyRewardPhase";
private moneyMultiplier: number; private moneyMultiplier: number;
constructor(moneyMultiplier: number) { constructor(moneyMultiplier: number) {

View File

@ -5,6 +5,8 @@ import { Phase } from "#app/phase";
* Plays the given {@linkcode MoveAnim} sequentially. * Plays the given {@linkcode MoveAnim} sequentially.
*/ */
export class MoveAnimPhase<Anim extends MoveAnim> extends Phase { export class MoveAnimPhase<Anim extends MoveAnim> extends Phase {
public readonly phaseName = "MoveAnimPhase";
constructor( constructor(
protected anim: Anim, protected anim: Anim,
protected onSubstitute = false, protected onSubstitute = false,

View File

@ -6,16 +6,15 @@ import type { PokemonMove } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { MoveResult } from "#app/field/pokemon"; import { MoveResult } from "#app/field/pokemon";
import { BooleanHolder } from "#app/utils/common"; import { BooleanHolder } from "#app/utils/common";
import { MovePhase } from "#app/phases/move-phase";
import { PokemonPhase } from "#app/phases/pokemon-phase"; import { PokemonPhase } from "#app/phases/pokemon-phase";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { MoveEndPhase } from "#app/phases/move-end-phase";
/** /**
* Phase for the "charging turn" of two-turn moves (e.g. Dig). * Phase for the "charging turn" of two-turn moves (e.g. Dig).
* @extends {@linkcode PokemonPhase} * @extends {@linkcode PokemonPhase}
*/ */
export class MoveChargePhase extends PokemonPhase { export class MoveChargePhase extends PokemonPhase {
public readonly phaseName = "MoveChargePhase";
/** The move instance that this phase applies */ /** The move instance that this phase applies */
public move: PokemonMove; public move: PokemonMove;
/** The field index targeted by the move (Charging moves assume single target) */ /** The field index targeted by the move (Charging moves assume single target) */
@ -62,9 +61,9 @@ export class MoveChargePhase extends PokemonPhase {
if (instantCharge.value) { if (instantCharge.value) {
// this MoveEndPhase will be duplicated by the queued MovePhase if not removed // this MoveEndPhase will be duplicated by the queued MovePhase if not removed
globalScene.tryRemovePhase(phase => phase instanceof MoveEndPhase && phase.getPokemon() === user); globalScene.phaseManager.tryRemovePhase(phase => phase.is("MoveEndPhase") && phase.getPokemon() === user);
// queue a new MovePhase for this move's attack phase // queue a new MovePhase for this move's attack phase
globalScene.unshiftPhase(new MovePhase(user, [this.targetIndex], this.move, false)); globalScene.phaseManager.unshiftNew("MovePhase", user, [this.targetIndex], this.move, false);
} else { } else {
user.getMoveQueue().push({ move: move.id, targets: [this.targetIndex] }); user.getMoveQueue().push({ move: move.id, targets: [this.targetIndex] });
} }

View File

@ -68,20 +68,16 @@ import { BattlerTagType } from "#enums/battler-tag-type";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import i18next from "i18next"; import i18next from "i18next";
import type { Phase } from "#app/phase"; import type { Phase } from "#app/phase";
import { ShowAbilityPhase } from "./show-ability-phase";
import { MovePhase } from "./move-phase";
import { MoveEndPhase } from "./move-end-phase";
import { HideAbilityPhase } from "#app/phases/hide-ability-phase";
import type { TypeDamageMultiplier } from "#app/data/type"; import type { TypeDamageMultiplier } from "#app/data/type";
import { HitCheckResult } from "#enums/hit-check-result"; import { HitCheckResult } from "#enums/hit-check-result";
import type Move from "#app/data/moves/move"; import type Move from "#app/data/moves/move";
import { isFieldTargeted } from "#app/data/moves/move-utils"; import { isFieldTargeted } from "#app/data/moves/move-utils";
import { FaintPhase } from "./faint-phase";
import { DamageAchv } from "#app/system/achv"; import { DamageAchv } from "#app/system/achv";
type HitCheckEntry = [HitCheckResult, TypeDamageMultiplier]; type HitCheckEntry = [HitCheckResult, TypeDamageMultiplier];
export class MoveEffectPhase extends PokemonPhase { export class MoveEffectPhase extends PokemonPhase {
public readonly phaseName = "MoveEffectPhase";
public move: Move; public move: Move;
private virtual = false; private virtual = false;
protected targets: BattlerIndex[]; protected targets: BattlerIndex[];
@ -190,13 +186,25 @@ export class MoveEffectPhase extends PokemonPhase {
// TODO: ability displays should be handled by the ability // TODO: ability displays should be handled by the ability
if (!target.getTag(BattlerTagType.MAGIC_COAT)) { if (!target.getTag(BattlerTagType.MAGIC_COAT)) {
this.queuedPhases.push( this.queuedPhases.push(
new ShowAbilityPhase(target.getBattlerIndex(), target.getPassiveAbility().hasAttr(ReflectStatusMoveAbAttr)), globalScene.phaseManager.create(
"ShowAbilityPhase",
target.getBattlerIndex(),
target.getPassiveAbility().hasAttr(ReflectStatusMoveAbAttr),
),
); );
this.queuedPhases.push(new HideAbilityPhase()); this.queuedPhases.push(globalScene.phaseManager.create("HideAbilityPhase"));
} }
this.queuedPhases.push( this.queuedPhases.push(
new MovePhase(target, newTargets, new PokemonMove(this.move.id, 0, 0, true), true, true, true), globalScene.phaseManager.create(
"MovePhase",
target,
newTargets,
new PokemonMove(this.move.id, 0, 0, true),
true,
true,
true,
),
); );
} }
@ -218,8 +226,9 @@ export class MoveEffectPhase extends PokemonPhase {
return; return;
} }
break; break;
// biome-ignore lint/suspicious/noFallthroughSwitchClause: The fallthrough is intentional
case HitCheckResult.NO_EFFECT: case HitCheckResult.NO_EFFECT:
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t(this.move.id === MoveId.SHEER_COLD ? "battle:hitResultImmune" : "battle:hitResultNoEffect", { i18next.t(this.move.id === MoveId.SHEER_COLD ? "battle:hitResultImmune" : "battle:hitResultNoEffect", {
pokemonName: getPokemonNameWithAffix(target), pokemonName: getPokemonNameWithAffix(target),
}), }),
@ -230,7 +239,7 @@ export class MoveEffectPhase extends PokemonPhase {
applyMoveAttrs(NoEffectAttr, user, target, this.move); applyMoveAttrs(NoEffectAttr, user, target, this.move);
break; break;
case HitCheckResult.MISS: case HitCheckResult.MISS:
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }), i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }),
); );
applyMoveAttrs(MissEffectAttr, user, target, this.move); applyMoveAttrs(MissEffectAttr, user, target, this.move);
@ -293,7 +302,8 @@ export class MoveEffectPhase extends PokemonPhase {
// If other effects were overriden, stop this phase before they can be applied // If other effects were overriden, stop this phase before they can be applied
if (overridden.value) { if (overridden.value) {
return this.end(); this.end();
return;
} }
// Lapse `MOVE_EFFECT` effects (i.e. semi-invulnerability) when applicable // Lapse `MOVE_EFFECT` effects (i.e. semi-invulnerability) when applicable
@ -381,7 +391,7 @@ export class MoveEffectPhase extends PokemonPhase {
} }
if (this.queuedPhases.length) { if (this.queuedPhases.length) {
globalScene.appendToPhase(this.queuedPhases, MoveEndPhase); globalScene.phaseManager.appendToPhase(this.queuedPhases, "MoveEndPhase");
} }
const moveType = user.getMoveType(this.move, true); const moveType = user.getMoveType(this.move, true);
if (this.move.category !== MoveCategory.STATUS && !user.stellarTypesBoosted.includes(moveType)) { if (this.move.category !== MoveCategory.STATUS && !user.stellarTypesBoosted.includes(moveType)) {
@ -407,14 +417,14 @@ export class MoveEffectPhase extends PokemonPhase {
*/ */
if (user) { if (user) {
if (user.turnData.hitsLeft && --user.turnData.hitsLeft >= 1 && this.getFirstTarget()?.isActive()) { if (user.turnData.hitsLeft && --user.turnData.hitsLeft >= 1 && this.getFirstTarget()?.isActive()) {
globalScene.unshiftPhase(this.getNewHitPhase()); globalScene.phaseManager.unshiftPhase(this.getNewHitPhase());
} else { } else {
// Queue message for number of hits made by multi-move // Queue message for number of hits made by multi-move
// If multi-hit attack only hits once, still want to render a message // If multi-hit attack only hits once, still want to render a message
const hitsTotal = user.turnData.hitCount - Math.max(user.turnData.hitsLeft, 0); const hitsTotal = user.turnData.hitCount - Math.max(user.turnData.hitsLeft, 0);
if (hitsTotal > 1 || (user.turnData.hitsLeft && user.turnData.hitsLeft > 0)) { if (hitsTotal > 1 || (user.turnData.hitsLeft && user.turnData.hitsLeft > 0)) {
// If there are multiple hits, or if there are hits of the multi-hit move left // If there are multiple hits, or if there are hits of the multi-hit move left
globalScene.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal })); globalScene.phaseManager.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal }));
} }
globalScene.applyModifiers(HitHealModifier, this.player, user); globalScene.applyModifiers(HitHealModifier, this.player, user);
this.getTargets().forEach(target => (target.turnData.moveEffectiveness = null)); this.getTargets().forEach(target => (target.turnData.moveEffectiveness = null));
@ -742,7 +752,7 @@ export class MoveEffectPhase extends PokemonPhase {
firstTarget?: boolean | null, firstTarget?: boolean | null,
selfTarget?: boolean, selfTarget?: boolean,
): void { ): void {
return applyFilteredMoveAttrs( applyFilteredMoveAttrs(
(attr: MoveAttr) => (attr: MoveAttr) =>
attr instanceof MoveEffectAttr && attr instanceof MoveEffectAttr &&
attr.trigger === triggerType && attr.trigger === triggerType &&
@ -855,7 +865,7 @@ export class MoveEffectPhase extends PokemonPhase {
}); });
if (isCritical) { if (isCritical) {
globalScene.queueMessage(i18next.t("battle:hitResultCriticalHit")); globalScene.phaseManager.queueMessage(i18next.t("battle:hitResultCriticalHit"));
} }
if (damage <= 0) { if (damage <= 0) {
@ -884,7 +894,7 @@ export class MoveEffectPhase extends PokemonPhase {
sourceBattlerIndex: user.getBattlerIndex(), sourceBattlerIndex: user.getBattlerIndex(),
}); });
if (user.isPlayer() && !target.isPlayer()) { if (user.isPlayer() && target.isEnemy()) {
globalScene.applyModifiers(DamageMoneyRewardModifier, true, user, new NumberHolder(damage)); globalScene.applyModifiers(DamageMoneyRewardModifier, true, user, new NumberHolder(damage));
} }
@ -898,9 +908,9 @@ export class MoveEffectPhase extends PokemonPhase {
*/ */
protected onFaintTarget(user: Pokemon, target: Pokemon): void { protected onFaintTarget(user: Pokemon, target: Pokemon): void {
// set splice index here, so future scene queues happen before FaintedPhase // set splice index here, so future scene queues happen before FaintedPhase
globalScene.setPhaseQueueSplice(); globalScene.phaseManager.setPhaseQueueSplice();
globalScene.unshiftPhase(new FaintPhase(target.getBattlerIndex(), false, user)); globalScene.phaseManager.unshiftNew("FaintPhase", target.getBattlerIndex(), false, user);
target.destroySubstitute(); target.destroySubstitute();
target.lapseTag(BattlerTagType.COMMANDED); target.lapseTag(BattlerTagType.COMMANDED);
@ -933,7 +943,7 @@ export class MoveEffectPhase extends PokemonPhase {
break; break;
} }
if (msg) { if (msg) {
globalScene.queueMessage(msg); globalScene.phaseManager.queueMessage(msg);
} }
} }

View File

@ -6,6 +6,7 @@ import { applyPostSummonAbAttrs, PostSummonRemoveEffectAbAttr } from "#app/data/
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
export class MoveEndPhase extends PokemonPhase { export class MoveEndPhase extends PokemonPhase {
public readonly phaseName = "MoveEndPhase";
private wasFollowUp: boolean; private wasFollowUp: boolean;
/** Targets from the preceding MovePhase */ /** Targets from the preceding MovePhase */

View File

@ -4,6 +4,7 @@ import type Pokemon from "#app/field/pokemon";
import { BattlePhase } from "./battle-phase"; import { BattlePhase } from "./battle-phase";
export class MoveHeaderPhase extends BattlePhase { export class MoveHeaderPhase extends BattlePhase {
public readonly phaseName = "MoveHeaderPhase";
public pokemon: Pokemon; public pokemon: Pokemon;
public move: PokemonMove; public move: PokemonMove;

View File

@ -39,10 +39,6 @@ import { MoveResult } from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { BattlePhase } from "#app/phases/battle-phase"; import { BattlePhase } from "#app/phases/battle-phase";
import { CommonAnimPhase } from "#app/phases/common-anim-phase";
import { MoveChargePhase } from "#app/phases/move-charge-phase";
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { MoveEndPhase } from "#app/phases/move-end-phase";
import { NumberHolder } from "#app/utils/common"; import { NumberHolder } from "#app/utils/common";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type";
@ -52,6 +48,7 @@ import { StatusEffect } from "#enums/status-effect";
import i18next from "i18next"; import i18next from "i18next";
export class MovePhase extends BattlePhase { export class MovePhase extends BattlePhase {
public readonly phaseName = "MovePhase";
protected _pokemon: Pokemon; protected _pokemon: Pokemon;
protected _move: PokemonMove; protected _move: PokemonMove;
protected _targets: BattlerIndex[]; protected _targets: BattlerIndex[];
@ -267,18 +264,17 @@ export class MovePhase extends BattlePhase {
if (activated) { if (activated) {
this.cancel(); this.cancel();
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
getStatusEffectActivationText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)), getStatusEffectActivationText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)),
); );
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new CommonAnimPhase( "CommonAnimPhase",
this.pokemon.getBattlerIndex(), this.pokemon.getBattlerIndex(),
undefined, undefined,
CommonAnim.POISON + (this.pokemon.status.effect - 1), CommonAnim.POISON + (this.pokemon.status.effect - 1),
),
); );
} else if (healed) { } else if (healed) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
getStatusEffectHealText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)), getStatusEffectHealText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)),
); );
this.pokemon.resetStatus(); this.pokemon.resetStatus();
@ -406,8 +402,13 @@ export class MovePhase extends BattlePhase {
if (success) { if (success) {
const move = this.move.getMove(); const move = this.move.getMove();
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, move); applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, move);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new MoveEffectPhase(this.pokemon.getBattlerIndex(), this.targets, move, this.reflected, this.move.virtual), "MoveEffectPhase",
this.pokemon.getBattlerIndex(),
this.targets,
move,
this.reflected,
this.move.virtual,
); );
} else { } else {
if ([MoveId.ROAR, MoveId.WHIRLWIND, MoveId.TRICK_OR_TREAT, MoveId.FORESTS_CURSE].includes(this.move.moveId)) { if ([MoveId.ROAR, MoveId.WHIRLWIND, MoveId.TRICK_OR_TREAT, MoveId.FORESTS_CURSE].includes(this.move.moveId)) {
@ -456,7 +457,12 @@ export class MovePhase extends BattlePhase {
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove()); applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove());
this.showMoveText(); this.showMoveText();
globalScene.unshiftPhase(new MoveChargePhase(this.pokemon.getBattlerIndex(), this.targets[0], this.move)); globalScene.phaseManager.unshiftNew(
"MoveChargePhase",
this.pokemon.getBattlerIndex(),
this.targets[0],
this.move,
);
} else { } else {
this.pokemon.pushMoveHistory({ this.pokemon.pushMoveHistory({
move: this.move.moveId, move: this.move.moveId,
@ -478,8 +484,11 @@ export class MovePhase extends BattlePhase {
* Queues a {@linkcode MoveEndPhase} and then ends the phase * Queues a {@linkcode MoveEndPhase} and then ends the phase
*/ */
public end(): void { public end(): void {
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new MoveEndPhase(this.pokemon.getBattlerIndex(), this.getActiveTargetPokemon(), this.followUp), "MoveEndPhase",
this.pokemon.getBattlerIndex(),
this.getActiveTargetPokemon(),
this.followUp,
); );
super.end(); super.end();
@ -544,12 +553,12 @@ export class MovePhase extends BattlePhase {
if (this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr)) { if (this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr)) {
redirectTarget.value = currentTarget; redirectTarget.value = currentTarget;
// TODO: Ability displays should be handled by the ability // TODO: Ability displays should be handled by the ability
globalScene.queueAbilityDisplay( globalScene.phaseManager.queueAbilityDisplay(
this.pokemon, this.pokemon,
this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr),
true, true,
); );
globalScene.queueAbilityDisplay( globalScene.phaseManager.queueAbilityDisplay(
this.pokemon, this.pokemon,
this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr),
false, false,
@ -648,7 +657,7 @@ export class MovePhase extends BattlePhase {
return; return;
} }
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t(this.reflected ? "battle:magicCoatActivated" : "battle:useMove", { i18next.t(this.reflected ? "battle:magicCoatActivated" : "battle:useMove", {
pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon),
moveName: this.move.getName(), moveName: this.move.getName(),
@ -659,6 +668,6 @@ export class MovePhase extends BattlePhase {
} }
public showFailedText(failedText: string = i18next.t("battle:attackFailed")): void { public showFailedText(failedText: string = i18next.t("battle:attackFailed")): void {
globalScene.queueMessage(failedText); globalScene.phaseManager.queueMessage(failedText);
} }
} }

View File

@ -3,16 +3,6 @@ import type { OptionPhaseCallback } from "#app/data/mystery-encounters/mystery-e
import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
import { SeenEncounterData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; import { SeenEncounterData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { CheckSwitchPhase } from "#app/phases/check-switch-phase";
import { GameOverPhase } from "#app/phases/game-over-phase";
import { NewBattlePhase } from "#app/phases/new-battle-phase";
import { PostTurnStatusEffectPhase } from "#app/phases/post-turn-status-effect-phase";
import { ReturnPhase } from "#app/phases/return-phase";
import { ScanIvsPhase } from "#app/phases/scan-ivs-phase";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
import { SummonPhase } from "#app/phases/summon-phase";
import { SwitchPhase } from "#app/phases/switch-phase";
import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase";
import { BattleSpec } from "#enums/battle-spec"; import { BattleSpec } from "#enums/battle-spec";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
@ -27,7 +17,6 @@ import { IvScannerModifier } from "../modifier/modifier";
import { Phase } from "../phase"; import { Phase } from "../phase";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import { isNullOrUndefined, randSeedItem } from "#app/utils/common"; import { isNullOrUndefined, randSeedItem } from "#app/utils/common";
import { SelectBiomePhase } from "./select-biome-phase";
/** /**
* Will handle (in order): * Will handle (in order):
@ -39,6 +28,7 @@ import { SelectBiomePhase } from "./select-biome-phase";
* - Queuing of the {@linkcode MysteryEncounterOptionSelectedPhase} * - Queuing of the {@linkcode MysteryEncounterOptionSelectedPhase}
*/ */
export class MysteryEncounterPhase extends Phase { export class MysteryEncounterPhase extends Phase {
public readonly phaseName = "MysteryEncounterPhase";
private readonly FIRST_DIALOGUE_PROMPT_DELAY = 300; private readonly FIRST_DIALOGUE_PROMPT_DELAY = 300;
optionSelectSettings?: OptionSelectSettings; optionSelectSettings?: OptionSelectSettings;
@ -58,8 +48,8 @@ export class MysteryEncounterPhase extends Phase {
super.start(); super.start();
// Clears out queued phases that are part of standard battle // Clears out queued phases that are part of standard battle
globalScene.clearPhaseQueue(); globalScene.phaseManager.clearPhaseQueue();
globalScene.clearPhaseQueueSplice(); globalScene.phaseManager.clearPhaseQueueSplice();
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
encounter.updateSeedOffset(); encounter.updateSeedOffset();
@ -124,7 +114,7 @@ export class MysteryEncounterPhase extends Phase {
*/ */
continueEncounter() { continueEncounter() {
const endDialogueAndContinueEncounter = () => { const endDialogueAndContinueEncounter = () => {
globalScene.pushPhase(new MysteryEncounterOptionSelectedPhase()); globalScene.phaseManager.pushNew("MysteryEncounterOptionSelectedPhase");
this.end(); this.end();
}; };
@ -180,6 +170,7 @@ export class MysteryEncounterPhase extends Phase {
* Any phase that is meant to follow this one MUST be queued via the onOptionSelect() logic of the selected option * Any phase that is meant to follow this one MUST be queued via the onOptionSelect() logic of the selected option
*/ */
export class MysteryEncounterOptionSelectedPhase extends Phase { export class MysteryEncounterOptionSelectedPhase extends Phase {
public readonly phaseName = "MysteryEncounterOptionSelectedPhase";
onOptionSelect: OptionPhaseCallback; onOptionSelect: OptionPhaseCallback;
constructor() { constructor() {
@ -221,6 +212,7 @@ export class MysteryEncounterOptionSelectedPhase extends Phase {
* See {@linkcode TurnEndPhase} for more details * See {@linkcode TurnEndPhase} for more details
*/ */
export class MysteryEncounterBattleStartCleanupPhase extends Phase { export class MysteryEncounterBattleStartCleanupPhase extends Phase {
public readonly phaseName = "MysteryEncounterBattleStartCleanupPhase";
/** /**
* Cleans up `TURN_END` tags, any {@linkcode PostTurnStatusEffectPhase}s, checks for Pokemon switches, then continues * Cleans up `TURN_END` tags, any {@linkcode PostTurnStatusEffectPhase}s, checks for Pokemon switches, then continues
*/ */
@ -245,8 +237,8 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase {
}); });
// Remove any status tick phases // Remove any status tick phases
while (globalScene.findPhase(p => p instanceof PostTurnStatusEffectPhase)) { while (globalScene.phaseManager.findPhase(p => p.is("PostTurnStatusEffectPhase"))) {
globalScene.tryRemovePhase(p => p instanceof PostTurnStatusEffectPhase); globalScene.phaseManager.tryRemovePhase(p => p.is("PostTurnStatusEffectPhase"));
} }
// The total number of Pokemon in the player's party that can legally fight // The total number of Pokemon in the player's party that can legally fight
@ -254,7 +246,7 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase {
// The total number of legal player Pokemon that aren't currently on the field // The total number of legal player Pokemon that aren't currently on the field
const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true)); const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true));
if (!legalPlayerPokemon.length) { if (!legalPlayerPokemon.length) {
globalScene.unshiftPhase(new GameOverPhase()); globalScene.phaseManager.unshiftNew("GameOverPhase");
return this.end(); return this.end();
} }
@ -263,13 +255,13 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase {
const playerField = globalScene.getPlayerField(); const playerField = globalScene.getPlayerField();
playerField.forEach((pokemon, i) => { playerField.forEach((pokemon, i) => {
if (!pokemon.isAllowedInBattle() && legalPlayerPartyPokemon.length > i) { if (!pokemon.isAllowedInBattle() && legalPlayerPartyPokemon.length > i) {
globalScene.unshiftPhase(new SwitchPhase(SwitchType.SWITCH, i, true, false)); globalScene.phaseManager.unshiftNew("SwitchPhase", SwitchType.SWITCH, i, true, false);
} }
}); });
// THEN, if is a double battle, and player only has 1 summoned pokemon, center pokemon on field // THEN, if is a double battle, and player only has 1 summoned pokemon, center pokemon on field
if (globalScene.currentBattle.double && legalPlayerPokemon.length === 1 && legalPlayerPartyPokemon.length === 0) { if (globalScene.currentBattle.double && legalPlayerPokemon.length === 1 && legalPlayerPartyPokemon.length === 0) {
globalScene.unshiftPhase(new ToggleDoublePositionPhase(true)); globalScene.phaseManager.unshiftNew("ToggleDoublePositionPhase", true);
} }
this.end(); this.end();
@ -284,6 +276,7 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase {
* - Queue the {@linkcode SummonPhase}s, {@linkcode PostSummonPhase}s, etc., required to initialize the phase queue for a battle * - Queue the {@linkcode SummonPhase}s, {@linkcode PostSummonPhase}s, etc., required to initialize the phase queue for a battle
*/ */
export class MysteryEncounterBattlePhase extends Phase { export class MysteryEncounterBattlePhase extends Phase {
public readonly phaseName = "MysteryEncounterBattlePhase";
disableSwitch: boolean; disableSwitch: boolean;
constructor(disableSwitch = false) { constructor(disableSwitch = false) {
@ -345,9 +338,9 @@ export class MysteryEncounterBattlePhase extends Phase {
globalScene.playBgm(); globalScene.playBgm();
} }
const availablePartyMembers = globalScene.getEnemyParty().filter(p => !p.isFainted()).length; const availablePartyMembers = globalScene.getEnemyParty().filter(p => !p.isFainted()).length;
globalScene.unshiftPhase(new SummonPhase(0, false)); globalScene.phaseManager.unshiftNew("SummonPhase", 0, false);
if (globalScene.currentBattle.double && availablePartyMembers > 1) { if (globalScene.currentBattle.double && availablePartyMembers > 1) {
globalScene.unshiftPhase(new SummonPhase(1, false)); globalScene.phaseManager.unshiftNew("SummonPhase", 1, false);
} }
if (!globalScene.currentBattle.mysteryEncounter?.hideBattleIntroMessage) { if (!globalScene.currentBattle.mysteryEncounter?.hideBattleIntroMessage) {
@ -365,9 +358,9 @@ export class MysteryEncounterBattlePhase extends Phase {
const doTrainerSummon = () => { const doTrainerSummon = () => {
this.hideEnemyTrainer(); this.hideEnemyTrainer();
const availablePartyMembers = globalScene.getEnemyParty().filter(p => !p.isFainted()).length; const availablePartyMembers = globalScene.getEnemyParty().filter(p => !p.isFainted()).length;
globalScene.unshiftPhase(new SummonPhase(0, false)); globalScene.phaseManager.unshiftNew("SummonPhase", 0, false);
if (globalScene.currentBattle.double && availablePartyMembers > 1) { if (globalScene.currentBattle.double && availablePartyMembers > 1) {
globalScene.unshiftPhase(new SummonPhase(1, false)); globalScene.phaseManager.unshiftNew("SummonPhase", 1, false);
} }
this.endBattleSetup(); this.endBattleSetup();
}; };
@ -423,37 +416,37 @@ export class MysteryEncounterBattlePhase extends Phase {
if (encounterMode !== MysteryEncounterMode.TRAINER_BATTLE) { if (encounterMode !== MysteryEncounterMode.TRAINER_BATTLE) {
const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier); const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier);
if (ivScannerModifier) { if (ivScannerModifier) {
enemyField.map(p => globalScene.pushPhase(new ScanIvsPhase(p.getBattlerIndex()))); enemyField.map(p => globalScene.phaseManager.pushNew("ScanIvsPhase", p.getBattlerIndex()));
} }
} }
const availablePartyMembers = globalScene.getPlayerParty().filter(p => p.isAllowedInBattle()); const availablePartyMembers = globalScene.getPlayerParty().filter(p => p.isAllowedInBattle());
if (!availablePartyMembers[0].isOnField()) { if (!availablePartyMembers[0].isOnField()) {
globalScene.pushPhase(new SummonPhase(0)); globalScene.phaseManager.pushNew("SummonPhase", 0);
} }
if (globalScene.currentBattle.double) { if (globalScene.currentBattle.double) {
if (availablePartyMembers.length > 1) { if (availablePartyMembers.length > 1) {
globalScene.pushPhase(new ToggleDoublePositionPhase(true)); globalScene.phaseManager.pushNew("ToggleDoublePositionPhase", true);
if (!availablePartyMembers[1].isOnField()) { if (!availablePartyMembers[1].isOnField()) {
globalScene.pushPhase(new SummonPhase(1)); globalScene.phaseManager.pushNew("SummonPhase", 1);
} }
} }
} else { } else {
if (availablePartyMembers.length > 1 && availablePartyMembers[1].isOnField()) { if (availablePartyMembers.length > 1 && availablePartyMembers[1].isOnField()) {
globalScene.getPlayerField().forEach(pokemon => pokemon.lapseTag(BattlerTagType.COMMANDED)); globalScene.getPlayerField().forEach(pokemon => pokemon.lapseTag(BattlerTagType.COMMANDED));
globalScene.pushPhase(new ReturnPhase(1)); globalScene.phaseManager.pushNew("ReturnPhase", 1);
} }
globalScene.pushPhase(new ToggleDoublePositionPhase(false)); globalScene.phaseManager.pushNew("ToggleDoublePositionPhase", false);
} }
if (encounterMode !== MysteryEncounterMode.TRAINER_BATTLE && !this.disableSwitch) { if (encounterMode !== MysteryEncounterMode.TRAINER_BATTLE && !this.disableSwitch) {
const minPartySize = globalScene.currentBattle.double ? 2 : 1; const minPartySize = globalScene.currentBattle.double ? 2 : 1;
if (availablePartyMembers.length > minPartySize) { if (availablePartyMembers.length > minPartySize) {
globalScene.pushPhase(new CheckSwitchPhase(0, globalScene.currentBattle.double)); globalScene.phaseManager.pushNew("CheckSwitchPhase", 0, globalScene.currentBattle.double);
if (globalScene.currentBattle.double) { if (globalScene.currentBattle.double) {
globalScene.pushPhase(new CheckSwitchPhase(1, globalScene.currentBattle.double)); globalScene.phaseManager.pushNew("CheckSwitchPhase", 1, globalScene.currentBattle.double);
} }
} }
} }
@ -513,6 +506,7 @@ export class MysteryEncounterBattlePhase extends Phase {
* - Queuing of the {@linkcode PostMysteryEncounterPhase} * - Queuing of the {@linkcode PostMysteryEncounterPhase}
*/ */
export class MysteryEncounterRewardsPhase extends Phase { export class MysteryEncounterRewardsPhase extends Phase {
public readonly phaseName = "MysteryEncounterRewardsPhase";
addHealPhase: boolean; addHealPhase: boolean;
constructor(addHealPhase = false) { constructor(addHealPhase = false) {
@ -558,16 +552,14 @@ export class MysteryEncounterRewardsPhase extends Phase {
if (encounter.doEncounterRewards) { if (encounter.doEncounterRewards) {
encounter.doEncounterRewards(); encounter.doEncounterRewards();
} else if (this.addHealPhase) { } else if (this.addHealPhase) {
globalScene.tryRemovePhase(p => p instanceof SelectModifierPhase); globalScene.phaseManager.tryRemovePhase(p => p.is("SelectModifierPhase"));
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew("SelectModifierPhase", 0, undefined, {
new SelectModifierPhase(0, undefined, { fillRemaining: false,
fillRemaining: false, rerollMultiplier: -1,
rerollMultiplier: -1, });
}),
);
} }
globalScene.pushPhase(new PostMysteryEncounterPhase()); globalScene.phaseManager.pushNew("PostMysteryEncounterPhase");
this.end(); this.end();
} }
} }
@ -580,6 +572,7 @@ export class MysteryEncounterRewardsPhase extends Phase {
* - Queuing of the next wave * - Queuing of the next wave
*/ */
export class PostMysteryEncounterPhase extends Phase { export class PostMysteryEncounterPhase extends Phase {
public readonly phaseName = "PostMysteryEncounterPhase";
private readonly FIRST_DIALOGUE_PROMPT_DELAY = 750; private readonly FIRST_DIALOGUE_PROMPT_DELAY = 750;
onPostOptionSelect?: OptionPhaseCallback; onPostOptionSelect?: OptionPhaseCallback;
@ -613,10 +606,10 @@ export class PostMysteryEncounterPhase extends Phase {
continueEncounter() { continueEncounter() {
const endPhase = () => { const endPhase = () => {
if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) { if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) {
globalScene.pushPhase(new SelectBiomePhase()); globalScene.phaseManager.pushNew("SelectBiomePhase");
} }
globalScene.pushPhase(new NewBattlePhase()); globalScene.phaseManager.pushNew("NewBattlePhase");
this.end(); this.end();
}; };

View File

@ -2,13 +2,16 @@ import { globalScene } from "#app/global-scene";
import { BattlePhase } from "./battle-phase"; import { BattlePhase } from "./battle-phase";
export class NewBattlePhase extends BattlePhase { export class NewBattlePhase extends BattlePhase {
public readonly phaseName = "NewBattlePhase";
start() { start() {
super.start(); super.start();
// cull any extra `NewBattle` phases from the queue. // cull any extra `NewBattle` phases from the queue.
globalScene.phaseQueue = globalScene.phaseQueue.filter(phase => !(phase instanceof NewBattlePhase)); globalScene.phaseManager.phaseQueue = globalScene.phaseManager.phaseQueue.filter(
phase => !phase.is("NewBattlePhase"),
);
// `phaseQueuePrepend` is private, so we have to use this inefficient loop. // `phaseQueuePrepend` is private, so we have to use this inefficient loop.
while (globalScene.tryRemoveUnshiftedPhase(phase => phase instanceof NewBattlePhase)) {} while (globalScene.phaseManager.tryRemoveUnshiftedPhase(phase => phase.is("NewBattlePhase"))) {}
globalScene.newBattle(); globalScene.newBattle();

View File

@ -4,6 +4,7 @@ import { getRandomWeatherType } from "#app/data/weather";
import { NextEncounterPhase } from "./next-encounter-phase"; import { NextEncounterPhase } from "./next-encounter-phase";
export class NewBiomeEncounterPhase extends NextEncounterPhase { export class NewBiomeEncounterPhase extends NextEncounterPhase {
public readonly phaseName = "NewBiomeEncounterPhase";
doEncounter(): void { doEncounter(): void {
globalScene.playBgm(undefined, true); globalScene.playBgm(undefined, true);

View File

@ -6,6 +6,7 @@ import { EncounterPhase } from "./encounter-phase";
* Handles generating, loading and preparing for it. * Handles generating, loading and preparing for it.
*/ */
export class NextEncounterPhase extends EncounterPhase { export class NextEncounterPhase extends EncounterPhase {
public readonly phaseName: "NextEncounterPhase" | "NewBiomeEncounterPhase" = "NextEncounterPhase";
start() { start() {
super.start(); super.start();
} }

Some files were not shown because too many files have changed in this diff Show More