From 193c5ffb0ca9b0669d687170ab175c6d4c617b56 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Thu, 5 Jun 2025 17:18:07 -0500 Subject: [PATCH 01/51] [UI/UX] Move Reload Required text to bottom of settings (#5928) * Move Reload Required text to bottom of settings * Remove unneeded reloadRequiredText field --- src/ui/settings/abstract-settings-ui-handler.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ui/settings/abstract-settings-ui-handler.ts b/src/ui/settings/abstract-settings-ui-handler.ts index 27ca95c25ac..1fb4b6d34dc 100644 --- a/src/ui/settings/abstract-settings-ui-handler.ts +++ b/src/ui/settings/abstract-settings-ui-handler.ts @@ -108,10 +108,12 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler { this.reloadSettings = this.settings.filter(s => s?.requireReload); + let anyReloadRequired = false; this.settings.forEach((setting, s) => { let settingName = setting.label; if (setting?.requireReload) { - settingName += ` (${i18next.t("settings:requireReload")})`; + settingName += "*"; + anyReloadRequired = true; } this.settingLabels[s] = addTextObject(8, 28 + s * 16, settingName, TextStyle.SETTINGS_LABEL); @@ -187,6 +189,14 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler { this.settingsContainer.add(iconAction); this.settingsContainer.add(iconCancel); this.settingsContainer.add(actionText); + // Only add the ReloadRequired text on pages that have settings that require a reload. + if (anyReloadRequired) { + const reloadRequired = addTextObject(0, 0, `*${i18next.t("settings:requireReload")}`, TextStyle.SETTINGS_LABEL) + .setOrigin(0, 0.15) + .setPositionRelative(actionsBg, 6, 0) + .setY(actionText.y); + this.settingsContainer.add(reloadRequired); + } this.settingsContainer.add(cancelText); this.settingsContainer.add(this.messageBoxContainer); From 0c54fc1be064a815b5bf6609208e4b2e4801d56e Mon Sep 17 00:00:00 2001 From: PrabbyDD <147005742+PrabbyDD@users.noreply.github.com> Date: Thu, 5 Jun 2025 15:22:27 -0700 Subject: [PATCH 02/51] [Bug] Fix no EXP awarded if all active pokemon faint (#4688) Party pokemon will now gain EXP from EXP Share even if all the active pokemon are fainted --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/phases/faint-phase.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index bf0adf77061..cd40026e55f 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -78,10 +78,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()) { - if (pokemon?.isActive(true) && pokemon.isPlayer()) { - globalScene.currentBattle.addParticipant(pokemon as PlayerPokemon); + if (pokemon?.isActive() || pokemon?.isFainted()) { + globalScene.currentBattle.addParticipant(pokemon); } } From e82e18250761a2d33af09c3af06487812c830378 Mon Sep 17 00:00:00 2001 From: Lugiad <2070109+Adri1@users.noreply.github.com> Date: Fri, 6 Jun 2025 18:33:13 +0200 Subject: [PATCH 03/51] [UI/UX] [Localization] Russian Gatcha Text Resize (#5929) Russian Gatch Text Resize Co-authored-by: Wlowscha <54003515+Wlowscha@users.noreply.github.com> --- src/ui/egg-gacha-ui-handler.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index 1bb7124d935..a9a978548a8 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -108,7 +108,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { let pokemonIconX = -20; let pokemonIconY = 6; - if (["de", "es-ES", "es-MX", "fr", "ko", "pt-BR"].includes(currentLanguage)) { + if (["de", "es-ES", "es-MX", "fr", "ko", "pt-BR", "ru"].includes(currentLanguage)) { gachaTextStyle = TextStyle.SMALLER_WINDOW_ALT; gachaX = 2; gachaY = 2; @@ -150,7 +150,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { gachaInfoContainer.add(pokemonIcon); break; case GachaType.MOVE: - if (["de", "es-ES", "fr", "pt-BR"].includes(currentLanguage)) { + if (["de", "es-ES", "fr", "pt-BR", "ru"].includes(currentLanguage)) { gachaUpLabel.setAlign("center"); gachaUpLabel.setY(0); } @@ -160,7 +160,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { gachaUpLabel.setOrigin(0.5, 0); break; case GachaType.SHINY: - if (["de", "fr", "ko"].includes(currentLanguage)) { + if (["de", "fr", "ko", "ru"].includes(currentLanguage)) { gachaUpLabel.setAlign("center"); gachaUpLabel.setY(0); } From 178de207f1885769e2c9a8440343d5ea98fb1d61 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Fri, 6 Jun 2025 14:34:38 -0500 Subject: [PATCH 04/51] [Refactor] Cleanup egg list ui handler (#5890) Cleanup egg list ui handler Co-authored-by: Wlowscha <54003515+Wlowscha@users.noreply.github.com> --- src/ui/egg-list-ui-handler.ts | 93 +++++++++++++++-------------------- 1 file changed, 39 insertions(+), 54 deletions(-) diff --git a/src/ui/egg-list-ui-handler.ts b/src/ui/egg-list-ui-handler.ts index 9f41feea8ab..9a1b1f51e25 100644 --- a/src/ui/egg-list-ui-handler.ts +++ b/src/ui/egg-list-ui-handler.ts @@ -35,81 +35,70 @@ export default class EggListUiHandler extends MessageUiHandler { setup() { const ui = this.getUi(); - this.eggListContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); - this.eggListContainer.setVisible(false); + this.eggListContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6).setVisible(false); ui.add(this.eggListContainer); - const bgColor = globalScene.add.rectangle( - 0, - 0, - globalScene.game.canvas.width / 6, - globalScene.game.canvas.height / 6, - 0x006860, - ); - bgColor.setOrigin(0, 0); - this.eggListContainer.add(bgColor); + const bgColor = globalScene.add + .rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0x006860) + .setOrigin(0); - const eggListBg = globalScene.add.image(0, 0, "egg_list_bg"); - eggListBg.setOrigin(0, 0); - this.eggListContainer.add(eggListBg); - - this.eggListContainer.add(addWindow(1, 85, 106, 22)); - this.eggListContainer.add(addWindow(1, 102, 106, 50, true)); - this.eggListContainer.add(addWindow(1, 147, 106, 32, true)); - this.eggListContainer.add(addWindow(107, 1, 212, 178)); + const eggListBg = globalScene.add.image(0, 0, "egg_list_bg").setOrigin(0); this.iconAnimHandler = new PokemonIconAnimHandler(); this.iconAnimHandler.setup(); - this.eggNameText = addTextObject(8, 68, "", TextStyle.SUMMARY); - this.eggNameText.setOrigin(0, 0); - this.eggListContainer.add(this.eggNameText); + this.eggNameText = addTextObject(8, 68, "", TextStyle.SUMMARY).setOrigin(0); this.eggDateText = addTextObject(8, 91, "", TextStyle.TOOLTIP_CONTENT); - this.eggListContainer.add(this.eggDateText); - this.eggHatchWavesText = addTextObject(8, 108, "", TextStyle.TOOLTIP_CONTENT); - this.eggHatchWavesText.setWordWrapWidth(540); - this.eggListContainer.add(this.eggHatchWavesText); + this.eggHatchWavesText = addTextObject(8, 108, "", TextStyle.TOOLTIP_CONTENT).setWordWrapWidth(540); - this.eggGachaInfoText = addTextObject(8, 152, "", TextStyle.TOOLTIP_CONTENT); - this.eggGachaInfoText.setWordWrapWidth(540); - this.eggListContainer.add(this.eggGachaInfoText); + this.eggGachaInfoText = addTextObject(8, 152, "", TextStyle.TOOLTIP_CONTENT).setWordWrapWidth(540); this.eggListIconContainer = globalScene.add.container(113, 5); - this.eggListContainer.add(this.eggListIconContainer); - this.cursorObj = globalScene.add.image(0, 0, "select_cursor"); - this.cursorObj.setOrigin(0, 0); - this.eggListContainer.add(this.cursorObj); + this.cursorObj = globalScene.add.image(0, 0, "select_cursor").setOrigin(0); this.eggSprite = globalScene.add.sprite(54, 37, "egg"); - this.eggListContainer.add(this.eggSprite); const scrollBar = new ScrollBar(310, 5, 4, 170, this.ROWS); - this.eggListContainer.add(scrollBar); this.scrollGridHandler = new ScrollableGridUiHandler(this, this.ROWS, this.COLUMNS) .withScrollBar(scrollBar) .withUpdateGridCallBack(() => this.updateEggIcons()) .withUpdateSingleElementCallback((i: number) => this.setEggDetails(i)); - this.eggListMessageBoxContainer = globalScene.add.container(0, globalScene.game.canvas.height / 6); - this.eggListMessageBoxContainer.setVisible(false); - this.eggListContainer.add(this.eggListMessageBoxContainer); + this.eggListMessageBoxContainer = globalScene.add + .container(0, globalScene.game.canvas.height / 6) + .setVisible(false); - const eggListMessageBox = addWindow(1, -1, 318, 28); - eggListMessageBox.setOrigin(0, 1); + const eggListMessageBox = addWindow(1, -1, 318, 28).setOrigin(0, 1); this.eggListMessageBoxContainer.add(eggListMessageBox); - this.message = addTextObject(8, -8, "", TextStyle.WINDOW, { maxLines: 1 }); - this.message.setOrigin(0, 1); - this.eggListMessageBoxContainer.add(this.message); + // Message isn't used, but is expected to exist as this subclasses MessageUiHandler + this.message = addTextObject(8, -8, "", TextStyle.WINDOW, { maxLines: 1 }).setActive(false).setVisible(false); this.cursor = -1; + + this.eggListContainer.add([ + bgColor, + eggListBg, + addWindow(1, 85, 106, 22), + addWindow(1, 102, 106, 50, true), + addWindow(1, 147, 106, 32, true), + addWindow(107, 1, 212, 178), + this.eggNameText, + this.eggDateText, + this.eggHatchWavesText, + this.eggGachaInfoText, + this.eggListIconContainer, + this.cursorObj, + this.eggSprite, + scrollBar, + ]); } - show(args: any[]): boolean { + override show(args: any[]): boolean { super.show(args); this.initEggIcons(); @@ -134,9 +123,10 @@ export default class EggListUiHandler extends MessageUiHandler { for (let i = 0; i < Math.min(this.ROWS * this.COLUMNS, globalScene.gameData.eggs.length); i++) { const x = (i % this.COLUMNS) * 18; const y = Math.floor(i / this.COLUMNS) * 18; - const icon = globalScene.add.sprite(x - 2, y + 2, "egg_icons"); - icon.setScale(0.5); - icon.setOrigin(0, 0); + const icon = globalScene.add + .sprite(x - 2, y + 2, "egg_icons") + .setScale(0.5) + .setOrigin(0); this.eggListIconContainer.add(icon); this.eggIcons.push(icon); } @@ -148,15 +138,13 @@ export default class EggListUiHandler extends MessageUiHandler { private updateEggIcons() { const indexOffset = this.scrollGridHandler.getItemOffset(); const eggsToShow = Math.min(this.eggIcons.length, globalScene.gameData.eggs.length - indexOffset); - this.eggIcons.forEach((icon, i) => { if (i !== this.cursor) { this.iconAnimHandler.addOrUpdate(icon, PokemonIconAnimMode.NONE); } if (i < eggsToShow) { const egg = globalScene.gameData.eggs[i + indexOffset]; - icon.setFrame(egg.getKey()); - icon.setVisible(true); + icon.setFrame(egg.getKey()).setVisible(true); } else { icon.setVisible(false); } @@ -187,7 +175,6 @@ export default class EggListUiHandler extends MessageUiHandler { const ui = this.getUi(); let success = false; - const error = false; if (button === Button.CANCEL) { ui.revertMode(); @@ -198,11 +185,9 @@ export default class EggListUiHandler extends MessageUiHandler { if (success) { ui.playSelect(); - } else if (error) { - ui.playError(); } - return success || error; + return success; } setCursor(cursor: number): boolean { From 2a769e2733f7b05a297e62fd7bd2866af5b43b3a Mon Sep 17 00:00:00 2001 From: ShinigamiHolo <128856544+ShinigamiHolo@users.noreply.github.com> Date: Fri, 6 Jun 2025 22:45:12 +0300 Subject: [PATCH 05/51] [UI/UX] Adding more space in Egg Gacha for another languge (#5934) --- src/ui/egg-gacha-ui-handler.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index a9a978548a8..eb3c6c75d97 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -206,7 +206,17 @@ export default class EggGachaUiHandler extends MessageUiHandler { this.eggGachaOptionsContainer = globalScene.add.container(globalScene.game.canvas.width / 6, 148); this.eggGachaContainer.add(this.eggGachaOptionsContainer); - this.eggGachaOptionSelectBg = addWindow(0, 0, 96, 16 + 576 * this.scale); + // Increase egg box width on certain languages + let eggGachaOptionSelectWidth = 0; + switch (i18next.resolvedLanguage) { + case "ru": + eggGachaOptionSelectWidth = 100; + break; + default: + eggGachaOptionSelectWidth = 96; + } + + this.eggGachaOptionSelectBg = addWindow(0, 0, eggGachaOptionSelectWidth, 16 + 576 * this.scale); this.eggGachaOptionSelectBg.setOrigin(1, 1); this.eggGachaOptionsContainer.add(this.eggGachaOptionSelectBg); From 9021e4b41e67636e523811aa8266d59351904152 Mon Sep 17 00:00:00 2001 From: Lugiad <2070109+Adri1@users.noreply.github.com> Date: Fri, 6 Jun 2025 21:52:35 +0200 Subject: [PATCH 06/51] [i18n] More Controller settings localization (#5920) --- src/system/settings/settings-gamepad.ts | 9 +++++---- src/ui/settings/gamepad-binding-ui-handler.ts | 5 +++-- src/ui/settings/keyboard-binding-ui-handler.ts | 3 ++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/system/settings/settings-gamepad.ts b/src/system/settings/settings-gamepad.ts index 12add905096..d39d5cf5a41 100644 --- a/src/system/settings/settings-gamepad.ts +++ b/src/system/settings/settings-gamepad.ts @@ -4,6 +4,7 @@ import { truncateString } from "../../utils/common"; import { Button } from "#enums/buttons"; import { SettingKeyboard } from "#app/system/settings/settings-keyboard"; import { globalScene } from "#app/global-scene"; +import i18next from "i18next"; export enum SettingGamepad { Controller = "CONTROLLER", @@ -27,11 +28,11 @@ export enum SettingGamepad { Button_Submit = "BUTTON_SUBMIT", } -const pressAction = "Press action to assign"; +const pressAction = i18next.t("settings:pressActionToAssign"); export const settingGamepadOptions = { - [SettingGamepad.Controller]: ["Default", "Change"], - [SettingGamepad.Gamepad_Support]: ["Auto", "Disabled"], + [SettingGamepad.Controller]: [i18next.t("settings:controllerDefault"), i18next.t("settings:controllerChange")], + [SettingGamepad.Gamepad_Support]: [i18next.t("settings:gamepadSupportAuto"), i18next.t("settings:gamepadSupportDisabled")], [SettingGamepad.Button_Up]: [`KEY ${Button.UP.toString()}`, pressAction], [SettingGamepad.Button_Down]: [`KEY ${Button.DOWN.toString()}`, pressAction], [SettingGamepad.Button_Left]: [`KEY ${Button.LEFT.toString()}`, pressAction], @@ -140,7 +141,7 @@ export function setSettingGamepad(setting: SettingGamepad, value: number): boole handler: () => changeGamepadHandler(g), })), { - label: "Cancel", + label: i18next.t("settings:cancelContollerChoice"), handler: cancelHandler, }, ], diff --git a/src/ui/settings/gamepad-binding-ui-handler.ts b/src/ui/settings/gamepad-binding-ui-handler.ts index 0f226ddcafa..9ddb54131d5 100644 --- a/src/ui/settings/gamepad-binding-ui-handler.ts +++ b/src/ui/settings/gamepad-binding-ui-handler.ts @@ -4,6 +4,7 @@ import { Device } from "#enums/devices"; import { getIconWithSettingName, getKeyWithKeycode } from "#app/configs/inputs/configHandler"; import { addTextObject, TextStyle } from "#app/ui/text"; import { globalScene } from "#app/global-scene"; +import i18next from "i18next"; export default class GamepadBindingUiHandler extends AbstractBindingUiHandler { constructor(mode: UiMode | null = null) { @@ -19,7 +20,7 @@ export default class GamepadBindingUiHandler extends AbstractBindingUiHandler { this.newButtonIcon.setOrigin(0.5); this.newButtonIcon.setVisible(false); - this.swapText = addTextObject(0, 0, "will swap with", TextStyle.WINDOW); + this.swapText = addTextObject(0, 0, i18next.t("settings:willSwapWith"), TextStyle.WINDOW); this.swapText.setOrigin(0.5); this.swapText.setPositionRelative( this.optionSelectBg, @@ -33,7 +34,7 @@ export default class GamepadBindingUiHandler extends AbstractBindingUiHandler { this.targetButtonIcon.setOrigin(0.5); this.targetButtonIcon.setVisible(false); - this.actionLabel = addTextObject(0, 0, "Confirm swap", TextStyle.SETTINGS_LABEL); + this.actionLabel = addTextObject(0, 0, i18next.t("settings:confirmSwap"), TextStyle.SETTINGS_LABEL); this.actionLabel.setOrigin(0, 0.5); this.actionLabel.setPositionRelative(this.actionBg, this.actionBg.width - 75, this.actionBg.height / 2); this.actionsContainer.add(this.actionLabel); diff --git a/src/ui/settings/keyboard-binding-ui-handler.ts b/src/ui/settings/keyboard-binding-ui-handler.ts index c05a31ca91e..183b8269e78 100644 --- a/src/ui/settings/keyboard-binding-ui-handler.ts +++ b/src/ui/settings/keyboard-binding-ui-handler.ts @@ -4,6 +4,7 @@ import { getKeyWithKeycode } from "#app/configs/inputs/configHandler"; import { Device } from "#enums/devices"; import { addTextObject, TextStyle } from "#app/ui/text"; import { globalScene } from "#app/global-scene"; +import i18next from "i18next"; export default class KeyboardBindingUiHandler extends AbstractBindingUiHandler { constructor(mode: UiMode | null = null) { @@ -21,7 +22,7 @@ export default class KeyboardBindingUiHandler extends AbstractBindingUiHandler { this.newButtonIcon.setOrigin(0.5); this.newButtonIcon.setVisible(false); - this.actionLabel = addTextObject(0, 0, "Assign button", TextStyle.SETTINGS_LABEL); + this.actionLabel = addTextObject(0, 0, i18next.t("settings:assignButton"), TextStyle.SETTINGS_LABEL); this.actionLabel.setOrigin(0, 0.5); this.actionLabel.setPositionRelative(this.actionBg, this.actionBg.width - 80, this.actionBg.height / 2); this.actionsContainer.add(this.actionLabel); From 1fc42b32312d8069deb9da1f3e461c14601495b1 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Fri, 6 Jun 2025 16:08:23 -0500 Subject: [PATCH 07/51] [Misc] Add `phase#is` method to help reduce circular imports (#5868) * Move phase types out of phase interceptor * Create isXPhase method and add properties to each phase * Replace instanceof phase with isXPhase * Fix missing union types for phaseName * Update doc comment in phase.ts * Fix incomplete comment in encounter-phase * Make phaseName as public and fix more uses * Move phaseName property declaration before constructor in move anim phase Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Rename isXPhase to is --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/@types/phase-types.ts | 286 ++++++++++++++++++ src/battle-scene.ts | 11 +- src/data/abilities/ability.ts | 4 +- src/data/arena-tag.ts | 2 +- src/data/battler-tags.ts | 20 +- src/data/moves/move.ts | 14 +- .../utils/encounter-phase-utils.ts | 2 +- src/field/pokemon.ts | 5 +- src/phase.ts | 26 +- src/phases/add-enemy-buff-modifier-phase.ts | 1 + src/phases/attempt-capture-phase.ts | 1 + src/phases/attempt-run-phase.ts | 1 + src/phases/battle-end-phase.ts | 5 +- src/phases/battle-phase.ts | 2 +- src/phases/berry-phase.ts | 1 + src/phases/check-status-effect-phase.ts | 1 + src/phases/check-switch-phase.ts | 1 + src/phases/command-phase.ts | 1 + src/phases/common-anim-phase.ts | 3 + src/phases/damage-anim-phase.ts | 1 + src/phases/egg-hatch-phase.ts | 3 +- src/phases/egg-lapse-phase.ts | 1 + src/phases/egg-summary-phase.ts | 1 + src/phases/encounter-phase.ts | 2 + src/phases/end-card-phase.ts | 1 + src/phases/end-evolution-phase.ts | 1 + src/phases/enemy-command-phase.ts | 1 + src/phases/evolution-phase.ts | 3 + src/phases/exp-phase.ts | 1 + src/phases/faint-phase.ts | 1 + src/phases/form-change-phase.ts | 1 + src/phases/game-over-modifier-reward-phase.ts | 1 + src/phases/game-over-phase.ts | 1 + src/phases/hide-ability-phase.ts | 1 + src/phases/hide-party-exp-bar-phase.ts | 1 + src/phases/learn-move-phase.ts | 6 +- src/phases/level-cap-phase.ts | 1 + src/phases/level-up-phase.ts | 1 + src/phases/load-move-anim-phase.ts | 1 + src/phases/login-phase.ts | 1 + src/phases/message-phase.ts | 1 + src/phases/modifier-reward-phase.ts | 4 + src/phases/money-reward-phase.ts | 1 + src/phases/move-anim-phase.ts | 2 + src/phases/move-charge-phase.ts | 4 +- src/phases/move-effect-phase.ts | 1 + src/phases/move-end-phase.ts | 1 + src/phases/move-header-phase.ts | 1 + src/phases/move-phase.ts | 1 + src/phases/mystery-encounter-phases.ts | 13 +- src/phases/new-battle-phase.ts | 5 +- src/phases/new-biome-encounter-phase.ts | 1 + src/phases/next-encounter-phase.ts | 1 + src/phases/obtain-status-effect-phase.ts | 1 + src/phases/party-exp-phase.ts | 1 + src/phases/party-heal-phase.ts | 1 + src/phases/pokemon-anim-phase.ts | 1 + src/phases/pokemon-heal-phase.ts | 1 + src/phases/pokemon-transform-phase.ts | 1 + src/phases/post-game-over-phase.ts | 1 + src/phases/post-summon-phase.ts | 1 + src/phases/post-turn-status-effect-phase.ts | 1 + src/phases/quiet-form-change-phase.ts | 5 +- src/phases/reload-session-phase.ts | 1 + src/phases/reset-status-phase.ts | 1 + src/phases/return-phase.ts | 1 + src/phases/revival-blessing-phase.ts | 1 + src/phases/ribbon-modifier-reward-phase.ts | 1 + src/phases/scan-ivs-phase.ts | 1 + src/phases/select-biome-phase.ts | 1 + src/phases/select-challenge-phase.ts | 1 + src/phases/select-gender-phase.ts | 1 + src/phases/select-modifier-phase.ts | 1 + src/phases/select-starter-phase.ts | 1 + src/phases/select-target-phase.ts | 1 + src/phases/shiny-sparkle-phase.ts | 1 + src/phases/show-ability-phase.ts | 1 + src/phases/show-party-exp-bar-phase.ts | 1 + src/phases/show-trainer-phase.ts | 1 + src/phases/stat-stage-change-phase.ts | 9 +- src/phases/summon-missing-phase.ts | 1 + src/phases/summon-phase.ts | 2 + src/phases/switch-biome-phase.ts | 1 + src/phases/switch-phase.ts | 4 +- src/phases/switch-summon-phase.ts | 1 + src/phases/tera-phase.ts | 1 + src/phases/title-phase.ts | 1 + src/phases/toggle-double-position-phase.ts | 1 + src/phases/trainer-victory-phase.ts | 1 + src/phases/turn-end-phase.ts | 1 + src/phases/turn-init-phase.ts | 1 + src/phases/turn-start-phase.ts | 1 + src/phases/unavailable-phase.ts | 1 + src/phases/unlock-phase.ts | 1 + src/phases/victory-phase.ts | 1 + src/phases/weather-effect-phase.ts | 1 + src/ui/command-ui-handler.ts | 4 +- src/ui/egg-hatch-scene-handler.ts | 3 +- src/ui/egg-summary-ui-handler.ts | 3 +- src/ui/menu-ui-handler.ts | 3 +- src/ui/party-ui-handler.ts | 5 +- src/ui/pokedex-page-ui-handler.ts | 2 +- test/testUtils/phaseInterceptor.ts | 122 +------- 103 files changed, 468 insertions(+), 187 deletions(-) create mode 100644 src/@types/phase-types.ts diff --git a/src/@types/phase-types.ts b/src/@types/phase-types.ts new file mode 100644 index 00000000000..596d9b15723 --- /dev/null +++ b/src/@types/phase-types.ts @@ -0,0 +1,286 @@ +import type { MoveAnim } from "#app/data/battle-anims"; +import type { AddEnemyBuffModifierPhase } from "#app/phases/add-enemy-buff-modifier-phase"; +import type { AttemptCapturePhase } from "#app/phases/attempt-capture-phase"; +import type { AttemptRunPhase } from "#app/phases/attempt-run-phase"; +import type { BattleEndPhase } from "#app/phases/battle-end-phase"; +import type { BerryPhase } from "#app/phases/berry-phase"; +import type { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase"; +import type { CheckSwitchPhase } from "#app/phases/check-switch-phase"; +import type { CommandPhase } from "#app/phases/command-phase"; +import type { CommonAnimPhase } from "#app/phases/common-anim-phase"; +import type { DamageAnimPhase } from "#app/phases/damage-anim-phase"; +import type { EggHatchPhase } from "#app/phases/egg-hatch-phase"; +import type { EggLapsePhase } from "#app/phases/egg-lapse-phase"; +import type { EggSummaryPhase } from "#app/phases/egg-summary-phase"; +import type { EncounterPhase } from "#app/phases/encounter-phase"; +import type { EndCardPhase } from "#app/phases/end-card-phase"; +import type { EndEvolutionPhase } from "#app/phases/end-evolution-phase"; +import type { EnemyCommandPhase } from "#app/phases/enemy-command-phase"; +import type { EvolutionPhase } from "#app/phases/evolution-phase"; +import type { ExpPhase } from "#app/phases/exp-phase"; +import type { FaintPhase } from "#app/phases/faint-phase"; +import type { FormChangePhase } from "#app/phases/form-change-phase"; +import type { GameOverModifierRewardPhase } from "#app/phases/game-over-modifier-reward-phase"; +import type { GameOverPhase } from "#app/phases/game-over-phase"; +import type { HideAbilityPhase } from "#app/phases/hide-ability-phase"; +import type { HidePartyExpBarPhase } from "#app/phases/hide-party-exp-bar-phase"; +import type { LearnMovePhase } from "#app/phases/learn-move-phase"; +import type { LevelCapPhase } from "#app/phases/level-cap-phase"; +import type { LevelUpPhase } from "#app/phases/level-up-phase"; +import type { LoadMoveAnimPhase } from "#app/phases/load-move-anim-phase"; +import type { LoginPhase } from "#app/phases/login-phase"; +import type { MessagePhase } from "#app/phases/message-phase"; +import type { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; +import type { MoneyRewardPhase } from "#app/phases/money-reward-phase"; +import type { MoveAnimPhase } from "#app/phases/move-anim-phase"; +import type { MoveChargePhase } from "#app/phases/move-charge-phase"; +import type { MoveEffectPhase } from "#app/phases/move-effect-phase"; +import type { MoveEndPhase } from "#app/phases/move-end-phase"; +import type { MoveHeaderPhase } from "#app/phases/move-header-phase"; +import type { MovePhase } from "#app/phases/move-phase"; +import type { + MysteryEncounterPhase, + MysteryEncounterOptionSelectedPhase, + MysteryEncounterBattlePhase, + MysteryEncounterRewardsPhase, + PostMysteryEncounterPhase, + MysteryEncounterBattleStartCleanupPhase, +} from "#app/phases/mystery-encounter-phases"; +import type { NewBattlePhase } from "#app/phases/new-battle-phase"; +import type { NewBiomeEncounterPhase } from "#app/phases/new-biome-encounter-phase"; +import type { NextEncounterPhase } from "#app/phases/next-encounter-phase"; +import type { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase"; +import type { PartyExpPhase } from "#app/phases/party-exp-phase"; +import type { PartyHealPhase } from "#app/phases/party-heal-phase"; +import type { PokemonAnimPhase } from "#app/phases/pokemon-anim-phase"; +import type { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; +import type { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase"; +import type { PostGameOverPhase } from "#app/phases/post-game-over-phase"; +import type { PostSummonPhase } from "#app/phases/post-summon-phase"; +import type { PostTurnStatusEffectPhase } from "#app/phases/post-turn-status-effect-phase"; +import type { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; +import type { ReloadSessionPhase } from "#app/phases/reload-session-phase"; +import type { ResetStatusPhase } from "#app/phases/reset-status-phase"; +import type { ReturnPhase } from "#app/phases/return-phase"; +import type { RevivalBlessingPhase } from "#app/phases/revival-blessing-phase"; +import type { RibbonModifierRewardPhase } from "#app/phases/ribbon-modifier-reward-phase"; +import type { ScanIvsPhase } from "#app/phases/scan-ivs-phase"; +import type { SelectBiomePhase } from "#app/phases/select-biome-phase"; +import type { SelectChallengePhase } from "#app/phases/select-challenge-phase"; +import type { SelectGenderPhase } from "#app/phases/select-gender-phase"; +import type { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import type { SelectStarterPhase } from "#app/phases/select-starter-phase"; +import type { SelectTargetPhase } from "#app/phases/select-target-phase"; +import type { ShinySparklePhase } from "#app/phases/shiny-sparkle-phase"; +import type { ShowAbilityPhase } from "#app/phases/show-ability-phase"; +import type { ShowPartyExpBarPhase } from "#app/phases/show-party-exp-bar-phase"; +import type { ShowTrainerPhase } from "#app/phases/show-trainer-phase"; +import type { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; +import type { SummonMissingPhase } from "#app/phases/summon-missing-phase"; +import type { SummonPhase } from "#app/phases/summon-phase"; +import type { SwitchBiomePhase } from "#app/phases/switch-biome-phase"; +import type { SwitchPhase } from "#app/phases/switch-phase"; +import type { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; +import type { TeraPhase } from "#app/phases/tera-phase"; +import type { TitlePhase } from "#app/phases/title-phase"; +import type { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase"; +import type { TrainerVictoryPhase } from "#app/phases/trainer-victory-phase"; +import type { TurnEndPhase } from "#app/phases/turn-end-phase"; +import type { TurnInitPhase } from "#app/phases/turn-init-phase"; +import type { TurnStartPhase } from "#app/phases/turn-start-phase"; +import type { UnavailablePhase } from "#app/phases/unavailable-phase"; +import type { UnlockPhase } from "#app/phases/unlock-phase"; +import type { VictoryPhase } from "#app/phases/victory-phase"; +import type { WeatherEffectPhase } from "#app/phases/weather-effect-phase"; + +export type PhaseClass = + | typeof AddEnemyBuffModifierPhase + | typeof AttemptCapturePhase + | typeof AttemptRunPhase + | typeof BattleEndPhase + | typeof BerryPhase + | typeof CheckStatusEffectPhase + | typeof CheckSwitchPhase + | typeof CommandPhase + | typeof CommonAnimPhase + | typeof DamageAnimPhase + | typeof EggHatchPhase + | typeof EggLapsePhase + | typeof EggSummaryPhase + | typeof EncounterPhase + | typeof EndCardPhase + | typeof EndEvolutionPhase + | typeof EnemyCommandPhase + | typeof EvolutionPhase + | typeof FormChangePhase + | typeof ExpPhase + | typeof FaintPhase + | typeof FormChangePhase + | typeof GameOverPhase + | typeof GameOverModifierRewardPhase + | typeof HideAbilityPhase + | typeof HidePartyExpBarPhase + | typeof LearnMovePhase + | typeof LevelUpPhase + | typeof LevelCapPhase + | typeof LoadMoveAnimPhase + | typeof LoginPhase + | typeof MessagePhase + | typeof ModifierRewardPhase + | typeof MoneyRewardPhase + | typeof MoveAnimPhase + | typeof MoveChargePhase + | typeof MoveEffectPhase + | typeof MoveEndPhase + | typeof MoveHeaderPhase + | typeof MovePhase + | typeof MysteryEncounterPhase + | typeof MysteryEncounterOptionSelectedPhase + | typeof MysteryEncounterBattlePhase + | typeof MysteryEncounterRewardsPhase + | typeof MysteryEncounterBattleStartCleanupPhase + | typeof MysteryEncounterRewardsPhase + | typeof PostMysteryEncounterPhase + | typeof NewBattlePhase + | typeof NewBiomeEncounterPhase + | typeof NextEncounterPhase + | typeof ObtainStatusEffectPhase + | typeof PartyExpPhase + | typeof PartyHealPhase + | typeof PokemonAnimPhase + | typeof PokemonHealPhase + | typeof PokemonTransformPhase + | typeof PostGameOverPhase + | typeof PostSummonPhase + | typeof PostTurnStatusEffectPhase + | typeof QuietFormChangePhase + | typeof ReloadSessionPhase + | typeof ResetStatusPhase + | typeof ReturnPhase + | typeof RevivalBlessingPhase + | typeof RibbonModifierRewardPhase + | typeof ScanIvsPhase + | typeof SelectBiomePhase + | typeof SelectChallengePhase + | typeof SelectGenderPhase + | typeof SelectModifierPhase + | typeof SelectStarterPhase + | typeof SelectTargetPhase + | typeof ShinySparklePhase + | typeof ShowAbilityPhase + | typeof ShowTrainerPhase + | typeof ShowPartyExpBarPhase + | typeof StatStageChangePhase + | typeof SummonMissingPhase + | typeof SummonPhase + | typeof SwitchBiomePhase + | typeof SwitchPhase + | typeof SwitchSummonPhase + | typeof TeraPhase + | typeof TitlePhase + | typeof ToggleDoublePositionPhase + | typeof TrainerVictoryPhase + | typeof TurnEndPhase + | typeof TurnInitPhase + | typeof TurnStartPhase + | typeof UnavailablePhase + | typeof UnlockPhase + | typeof VictoryPhase + | typeof WeatherEffectPhase; + +/** Typescript map used to map a string phase to the actual phase type */ +export type PhaseMap = { + AddEnemyBuffModifierPhase: AddEnemyBuffModifierPhase; + AttemptCapturePhase: AttemptCapturePhase; + AttemptRunPhase: AttemptRunPhase; + BattleEndPhase: BattleEndPhase; + BerryPhase: BerryPhase; + CheckStatusEffectPhase: CheckStatusEffectPhase; + CheckSwitchPhase: CheckSwitchPhase; + CommandPhase: CommandPhase; + CommonAnimPhase: CommonAnimPhase; + DamageAnimPhase: DamageAnimPhase; + EggHatchPhase: EggHatchPhase; + EggLapsePhase: EggLapsePhase; + EggSummaryPhase: EggSummaryPhase; + EncounterPhase: EncounterPhase; + EndCardPhase: EndCardPhase; + EndEvolutionPhase: EndEvolutionPhase; + EnemyCommandPhase: EnemyCommandPhase; + EvolutionPhase: EvolutionPhase; + ExpPhase: ExpPhase; + FaintPhase: FaintPhase; + FormChangePhase: FormChangePhase; + GameOverPhase: GameOverPhase; + GameOverModifierRewardPhase: GameOverModifierRewardPhase; + HideAbilityPhase: HideAbilityPhase; + HidePartyExpBarPhase: HidePartyExpBarPhase; + LearnMovePhase: LearnMovePhase; + LevelCapPhase: LevelCapPhase; + LevelUpPhase: LevelUpPhase; + LoadMoveAnimPhase: LoadMoveAnimPhase; + LoginPhase: LoginPhase; + MessagePhase: MessagePhase; + ModifierRewardPhase: ModifierRewardPhase; + MoneyRewardPhase: MoneyRewardPhase; + MoveAnimPhase: MoveAnimPhase; + MoveChargePhase: MoveChargePhase; + MoveEffectPhase: MoveEffectPhase; + MoveEndPhase: MoveEndPhase; + MoveHeaderPhase: MoveHeaderPhase; + MovePhase: MovePhase; + MysteryEncounterPhase: MysteryEncounterPhase; + MysteryEncounterOptionSelectedPhase: MysteryEncounterOptionSelectedPhase; + MysteryEncounterBattlePhase: MysteryEncounterBattlePhase; + MysteryEncounterBattleStartCleanupPhase: MysteryEncounterBattleStartCleanupPhase; + MysteryEncounterRewardsPhase: MysteryEncounterRewardsPhase; + PostMysteryEncounterPhase: PostMysteryEncounterPhase; + NewBattlePhase: NewBattlePhase; + NewBiomeEncounterPhase: NewBiomeEncounterPhase; + NextEncounterPhase: NextEncounterPhase; + ObtainStatusEffectPhase: ObtainStatusEffectPhase; + PartyExpPhase: PartyExpPhase; + PartyHealPhase: PartyHealPhase; + PokemonAnimPhase: PokemonAnimPhase; + PokemonHealPhase: PokemonHealPhase; + PokemonTransformPhase: PokemonTransformPhase; + PostGameOverPhase: PostGameOverPhase; + PostSummonPhase: PostSummonPhase; + PostTurnStatusEffectPhase: PostTurnStatusEffectPhase; + QuietFormChangePhase: QuietFormChangePhase; + ReloadSessionPhase: ReloadSessionPhase; + ResetStatusPhase: ResetStatusPhase; + ReturnPhase: ReturnPhase; + RevivalBlessingPhase: RevivalBlessingPhase; + RibbonModifierRewardPhase: RibbonModifierRewardPhase; + ScanIvsPhase: ScanIvsPhase; + SelectBiomePhase: SelectBiomePhase; + SelectChallengePhase: SelectChallengePhase; + SelectGenderPhase: SelectGenderPhase; + SelectModifierPhase: SelectModifierPhase; + SelectStarterPhase: SelectStarterPhase; + SelectTargetPhase: SelectTargetPhase; + ShinySparklePhase: ShinySparklePhase; + ShowAbilityPhase: ShowAbilityPhase; + ShowPartyExpBarPhase: ShowPartyExpBarPhase; + ShowTrainerPhase: ShowTrainerPhase; + StatStageChangePhase: StatStageChangePhase; + SummonMissingPhase: SummonMissingPhase; + SummonPhase: SummonPhase; + SwitchBiomePhase: SwitchBiomePhase; + SwitchPhase: SwitchPhase; + SwitchSummonPhase: SwitchSummonPhase; + TeraPhase: TeraPhase; + TitlePhase: TitlePhase; + ToggleDoublePositionPhase: ToggleDoublePositionPhase; + TrainerVictoryPhase: TrainerVictoryPhase; + TurnEndPhase: TurnEndPhase; + TurnInitPhase: TurnInitPhase; + TurnStartPhase: TurnStartPhase; + UnavailablePhase: UnavailablePhase; + UnlockPhase: UnlockPhase; + VictoryPhase: VictoryPhase; + WeatherEffectPhase: WeatherEffectPhase; +}; + +export type PhaseString = keyof PhaseMap; diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 34d26b3975c..f2f952ea301 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -145,7 +145,7 @@ import { LoadingScene } from "#app/loading-scene"; import { LevelCapPhase } from "#app/phases/level-cap-phase"; import { LoginPhase } from "#app/phases/login-phase"; import { MessagePhase } from "#app/phases/message-phase"; -import { MovePhase } from "#app/phases/move-phase"; +import type { 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"; @@ -153,7 +153,6 @@ 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"; @@ -901,7 +900,7 @@ export default class BattleScene extends SceneBase { do { targetingMovePhase = this.findPhase( mp => - mp instanceof MovePhase && + mp.is("MovePhase") && mp.targets.length === 1 && mp.targets[0] === removedPokemon.getBattlerIndex() && mp.pokemon.isPlayer() !== allyPokemon.isPlayer(), @@ -1450,7 +1449,7 @@ export default class BattleScene extends SceneBase { } if (lastBattle?.double && !newDouble) { - this.tryRemovePhase(p => p instanceof SwitchPhase); + this.tryRemovePhase((p: Phase) => p.is("SwitchPhase")); for (const p of this.getPlayerField()) { p.lapseTag(BattlerTagType.COMMANDED); } @@ -1588,9 +1587,7 @@ export default class BattleScene extends SceneBase { return 0; } - const isEggPhase: boolean = ["EggLapsePhase", "EggHatchPhase"].includes( - this.getCurrentPhase()?.constructor.name ?? "", - ); + const isEggPhase: boolean = ["EggLapsePhase", "EggHatchPhase"].includes(this.getCurrentPhase()?.phaseName ?? ""); if ( // Give trainers with specialty types an appropriately-typed form for Wormadam, Rotom, Arceus, Oricorio, Silvally, or Paldean Tauros. diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 8f5f267f7ef..1bd71df32e0 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -2828,7 +2828,7 @@ export class CommanderAbAttr extends AbAttr { // Apply boosts from this effect to the ally Dondozo pokemon.getAlly()?.addTag(BattlerTagType.COMMANDED, 0, MoveId.NONE, pokemon.id); // Cancel the source Pokemon's next move (if a move is queued) - globalScene.tryRemovePhase((phase) => phase instanceof MovePhase && phase.pokemon === pokemon); + globalScene.tryRemovePhase((phase) => phase.is("MovePhase") && phase.pokemon === pokemon); } } } @@ -6897,7 +6897,7 @@ export function initAbilities() { .ignorable(), new Ability(AbilityId.ANALYTIC, 5) .attr(MovePowerBoostAbAttr, (user, target, move) => { - const movePhase = globalScene.findPhase((phase) => phase instanceof MovePhase && phase.pokemon.id !== user?.id); + const movePhase = globalScene.findPhase((phase) => phase.is("MovePhase") && phase.pokemon.id !== user?.id); return isNullOrUndefined(movePhase); }, 1.3), new Ability(AbilityId.ILLUSION, 5) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 590319a01c0..0254aab37e1 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -383,7 +383,7 @@ const QuickGuardConditionFunc: ProtectConditionFunc = (_arena, moveId) => { const move = allMoves[moveId]; const effectPhase = globalScene.getCurrentPhase(); - if (effectPhase instanceof MoveEffectPhase) { + if (effectPhase?.is("MoveEffectPhase")) { const attacker = effectPhase.getUserPokemon(); if (attacker) { return move.getPriority(attacker) > 0; diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 4f263fc152b..c047f424591 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -28,7 +28,7 @@ import type Pokemon from "#app/field/pokemon"; import { HitResult, MoveResult } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { CommonAnimPhase } from "#app/phases/common-anim-phase"; -import { MoveEffectPhase } from "#app/phases/move-effect-phase"; +import type { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MovePhase } from "#app/phases/move-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import type { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase"; @@ -553,9 +553,9 @@ export class ShellTrapTag extends BattlerTag { // Trap should only be triggered by opponent's Physical moves if (phaseData?.move.category === MoveCategory.PHYSICAL && pokemon.isOpponent(phaseData.attacker)) { const shellTrapPhaseIndex = globalScene.phaseQueue.findIndex( - phase => phase instanceof MovePhase && phase.pokemon === pokemon, + phase => phase.is("MovePhase") && phase.pokemon === pokemon, ); - const firstMovePhaseIndex = globalScene.phaseQueue.findIndex(phase => phase instanceof MovePhase); + const firstMovePhaseIndex = globalScene.phaseQueue.findIndex(phase => phase.is("MovePhase")); // Only shift MovePhase timing if it's not already next up if (shellTrapPhaseIndex !== -1 && shellTrapPhaseIndex !== firstMovePhaseIndex) { @@ -1027,7 +1027,7 @@ export class PowderTag extends BattlerTag { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.PRE_MOVE) { const movePhase = globalScene.getCurrentPhase(); - if (movePhase instanceof MovePhase) { + if (movePhase?.is("MovePhase")) { const move = movePhase.move.getMove(); const weather = globalScene.arena.weather; if ( @@ -1183,13 +1183,13 @@ export class EncoreTag extends MoveRestrictionBattlerTag { }), ); - const movePhase = globalScene.findPhase(m => m instanceof MovePhase && m.pokemon === pokemon); + const movePhase = globalScene.findPhase(m => m.is("MovePhase") && m.pokemon === pokemon); if (movePhase) { const movesetMove = pokemon.getMoveset().find(m => m.moveId === this.moveId); if (movesetMove) { const lastMove = pokemon.getLastXMoves(1)[0]; globalScene.tryReplacePhase( - m => m instanceof MovePhase && m.pokemon === pokemon, + m => m.is("MovePhase") && m.pokemon === pokemon, new MovePhase(pokemon, lastMove.targets ?? [], movesetMove), ); } @@ -1624,7 +1624,7 @@ export class ProtectedTag extends BattlerTag { // Stop multi-hit moves early const effectPhase = globalScene.getCurrentPhase(); - if (effectPhase instanceof MoveEffectPhase) { + if (effectPhase?.is("MoveEffectPhase")) { effectPhase.stopMultiHit(pokemon); } return true; @@ -2646,7 +2646,7 @@ export class GulpMissileTag extends BattlerTag { } const moveEffectPhase = globalScene.getCurrentPhase(); - if (moveEffectPhase instanceof MoveEffectPhase) { + if (moveEffectPhase?.is("MoveEffectPhase")) { const attacker = moveEffectPhase.getUserPokemon(); if (!attacker) { @@ -3004,7 +3004,7 @@ export class SubstituteTag extends BattlerTag { /** If the Substitute redirects damage, queue a message to indicate it. */ onHit(pokemon: Pokemon): void { const moveEffectPhase = globalScene.getCurrentPhase(); - if (moveEffectPhase instanceof MoveEffectPhase) { + if (moveEffectPhase?.is("MoveEffectPhase")) { const attacker = moveEffectPhase.getUserPokemon(); if (!attacker) { return; @@ -3693,7 +3693,7 @@ export function loadBattlerTag(source: BattlerTag | any): BattlerTag { */ function getMoveEffectPhaseData(_pokemon: Pokemon): { phase: MoveEffectPhase; attacker: Pokemon; move: Move } | null { const phase = globalScene.getCurrentPhase(); - if (phase instanceof MoveEffectPhase) { + if (phase?.is("MoveEffectPhase")) { return { phase: phase, attacker: phase.getPokemon(), diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 7e9f99e28c1..91cc6350daa 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -3109,7 +3109,7 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr { const overridden = args[0] as BooleanHolder; - const allyMovePhase = globalScene.findPhase((phase) => phase instanceof MovePhase && phase.pokemon.isPlayer() === user.isPlayer()); + const allyMovePhase = globalScene.findPhase((phase) => phase.is("MovePhase") && phase.pokemon.isPlayer() === user.isPlayer()); if (allyMovePhase) { const allyMove = allyMovePhase.move.getMove(); if (allyMove !== move && allyMove.hasAttr(AwaitCombinedPledgeAttr)) { @@ -3123,7 +3123,7 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr { // Move the ally's MovePhase (if needed) so that the ally moves next const allyMovePhaseIndex = globalScene.phaseQueue.indexOf(allyMovePhase); - const firstMovePhaseIndex = globalScene.phaseQueue.findIndex((phase) => phase instanceof MovePhase); + const firstMovePhaseIndex = globalScene.phaseQueue.findIndex((phase) => phase.is("MovePhase")); if (allyMovePhaseIndex !== firstMovePhaseIndex) { globalScene.prependToPhase(globalScene.phaseQueue.splice(allyMovePhaseIndex, 1)[0], MovePhase); } @@ -4477,7 +4477,7 @@ export class CueNextRoundAttr extends MoveEffectAttr { override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean { const nextRoundPhase = globalScene.findPhase(phase => - phase instanceof MovePhase && phase.move.moveId === MoveId.ROUND + phase.is("MovePhase") && phase.move.moveId === MoveId.ROUND ); if (!nextRoundPhase) { @@ -4486,7 +4486,7 @@ export class CueNextRoundAttr extends MoveEffectAttr { // Update the phase queue so that the next Pokemon using Round moves next const nextRoundIndex = globalScene.phaseQueue.indexOf(nextRoundPhase); - const nextMoveIndex = globalScene.phaseQueue.findIndex(phase => phase instanceof MovePhase); + const nextMoveIndex = globalScene.phaseQueue.findIndex(phase => phase.is("MovePhase")); if (nextRoundIndex !== nextMoveIndex) { globalScene.prependToPhase(globalScene.phaseQueue.splice(nextRoundIndex, 1)[0], MovePhase); } @@ -6177,7 +6177,7 @@ export class RevivalBlessingAttr extends MoveEffectAttr { // Handle cases where revived pokemon needs to get switched in on same turn if (allyPokemon.isFainted() || allyPokemon === pokemon) { // 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.tryRemovePhase((phase: SwitchSummonPhase) => phase.is("SwitchSummonPhase") && phase.getPokemon() === pokemon); // 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) globalScene.findPhase((phase: MovePhase) => phase.pokemon === pokemon)?.cancel(); @@ -7896,7 +7896,7 @@ export class ForceLastAttr extends MoveEffectAttr { // Either the end of the turn or in front of another, slower move which has also been forced last const prependPhase = globalScene.findPhase((phase) => [ 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) { globalScene.phaseQueue.splice( @@ -7942,7 +7942,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 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.phaseQueue.find(phase => phase.is("MovePhase")) !== undefined; const failIfLastInPartyCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => { const party: Pokemon[] = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 060de9c3a9e..c9eaa2e6968 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -769,7 +769,7 @@ export function setEncounterRewards( if (customShopRewards) { globalScene.unshiftPhase(new SelectModifierPhase(0, undefined, customShopRewards)); } else { - globalScene.tryRemovePhase(p => p instanceof SelectModifierPhase); + globalScene.tryRemovePhase(p => p.is("MysteryEncounterRewardsPhase")); } if (eggRewards) { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index cd8563cfb30..d06553f2227 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -232,7 +232,6 @@ 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"; @@ -1300,7 +1299,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // 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) { + if (currentPhase?.is("MoveEffectPhase") && currentPhase.getPokemon() === this) { return false; } return true; @@ -4775,7 +4774,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ if (effect === StatusEffect.SLEEP || effect === StatusEffect.FREEZE) { const currentPhase = globalScene.getCurrentPhase(); - if (currentPhase instanceof MoveEffectPhase && currentPhase.getUserPokemon() === this) { + if (currentPhase?.is("MoveEffectPhase") && currentPhase.getUserPokemon() === this) { this.turnData.hitCount = 1; this.turnData.hitsLeft = 1; } diff --git a/src/phase.ts b/src/phase.ts index 20cc7cc4063..9e2468ebdff 100644 --- a/src/phase.ts +++ b/src/phase.ts @@ -1,9 +1,33 @@ import { globalScene } from "#app/global-scene"; +import type { PhaseMap, PhaseString } from "./@types/phase-types"; -export class Phase { +export abstract class Phase { start() {} end() { globalScene.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(phase: K): this is PhaseMap[K] { + return this.phaseName === phase; + } } diff --git a/src/phases/add-enemy-buff-modifier-phase.ts b/src/phases/add-enemy-buff-modifier-phase.ts index 16ed78e6d0d..28eaf0dc4df 100644 --- a/src/phases/add-enemy-buff-modifier-phase.ts +++ b/src/phases/add-enemy-buff-modifier-phase.ts @@ -9,6 +9,7 @@ import { Phase } from "#app/phase"; import { globalScene } from "#app/global-scene"; export class AddEnemyBuffModifierPhase extends Phase { + public readonly phaseName = "AddEnemyBuffModifierPhase"; start() { super.start(); diff --git a/src/phases/attempt-capture-phase.ts b/src/phases/attempt-capture-phase.ts index 8592cd98508..6c2f5f4dd76 100644 --- a/src/phases/attempt-capture-phase.ts +++ b/src/phases/attempt-capture-phase.ts @@ -27,6 +27,7 @@ import { globalScene } from "#app/global-scene"; import { Gender } from "#app/data/gender"; export class AttemptCapturePhase extends PokemonPhase { + public readonly phaseName = "AttemptCapturePhase"; private pokeballType: PokeballType; private pokeball: Phaser.GameObjects.Sprite; private originalY: number; diff --git a/src/phases/attempt-run-phase.ts b/src/phases/attempt-run-phase.ts index 15c521c01fc..ced81567c43 100644 --- a/src/phases/attempt-run-phase.ts +++ b/src/phases/attempt-run-phase.ts @@ -17,6 +17,7 @@ import { globalScene } from "#app/global-scene"; import { SelectBiomePhase } from "./select-biome-phase"; export class AttemptRunPhase extends PokemonPhase { + public readonly phaseName = "AttemptRunPhase"; /** For testing purposes: this is to force the pokemon to fail and escape */ public forceFailEscape = false; diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index b4bb28fe55e..96f6d02b1fc 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -5,6 +5,7 @@ import { BattlePhase } from "./battle-phase"; import { GameOverPhase } from "./game-over-phase"; export class BattleEndPhase extends BattlePhase { + public readonly phaseName = "BattleEndPhase"; /** If true, will increment battles won */ isVictory: boolean; @@ -19,7 +20,7 @@ export class BattleEndPhase extends BattlePhase { // cull any extra `BattleEnd` phases from the queue. globalScene.phaseQueue = globalScene.phaseQueue.filter(phase => { - if (phase instanceof BattleEndPhase) { + if (phase.is("BattleEndPhase")) { this.isVictory ||= phase.isVictory; return false; } @@ -28,7 +29,7 @@ export class BattleEndPhase extends BattlePhase { // `phaseQueuePrepend` is private, so we have to use this inefficient loop. while ( globalScene.tryRemoveUnshiftedPhase(phase => { - if (phase instanceof BattleEndPhase) { + if (phase.is("BattleEndPhase")) { this.isVictory ||= phase.isVictory; return true; } diff --git a/src/phases/battle-phase.ts b/src/phases/battle-phase.ts index d70b3909639..7cefd0369d9 100644 --- a/src/phases/battle-phase.ts +++ b/src/phases/battle-phase.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import { TrainerSlot } from "#enums/trainer-slot"; import { Phase } from "#app/phase"; -export class BattlePhase extends Phase { +export abstract class BattlePhase extends Phase { showEnemyTrainer(trainerSlot: TrainerSlot = TrainerSlot.NONE): void { if (!globalScene.currentBattle.trainer) { console.warn("Enemy trainer is missing!"); diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index 989f19c944f..8d66d498039 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -20,6 +20,7 @@ import type Pokemon from "#app/field/pokemon"; * Also triggers Cud Chew's "repeat berry use" effects */ export class BerryPhase extends FieldPhase { + public readonly phaseName = "BerryPhase"; start() { super.start(); diff --git a/src/phases/check-status-effect-phase.ts b/src/phases/check-status-effect-phase.ts index f59dfea9f02..0c5884bbac7 100644 --- a/src/phases/check-status-effect-phase.ts +++ b/src/phases/check-status-effect-phase.ts @@ -4,6 +4,7 @@ import type { BattlerIndex } from "#app/battle"; import { globalScene } from "#app/global-scene"; export class CheckStatusEffectPhase extends Phase { + public readonly phaseName = "CheckStatusEffectPhase"; private order: BattlerIndex[]; constructor(order: BattlerIndex[]) { super(); diff --git a/src/phases/check-switch-phase.ts b/src/phases/check-switch-phase.ts index 9d73411fd37..0506cd36c46 100644 --- a/src/phases/check-switch-phase.ts +++ b/src/phases/check-switch-phase.ts @@ -10,6 +10,7 @@ import { SwitchPhase } from "./switch-phase"; import { SwitchType } from "#enums/switch-type"; export class CheckSwitchPhase extends BattlePhase { + public readonly phaseName = "CheckSwitchPhase"; protected fieldIndex: number; protected useName: boolean; diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index 3f18ea95777..209c1eefc85 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -25,6 +25,7 @@ import { ArenaTagSide } from "#app/data/arena-tag"; import { ArenaTagType } from "#app/enums/arena-tag-type"; export class CommandPhase extends FieldPhase { + public readonly phaseName = "CommandPhase"; protected fieldIndex: number; constructor(fieldIndex: number) { diff --git a/src/phases/common-anim-phase.ts b/src/phases/common-anim-phase.ts index 5be5e112389..4a27db3a651 100644 --- a/src/phases/common-anim-phase.ts +++ b/src/phases/common-anim-phase.ts @@ -5,6 +5,9 @@ import { CommonBattleAnim } from "#app/data/battle-anims"; import { PokemonPhase } from "./pokemon-phase"; 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 targetIndex?: BattlerIndex; private playOnEmptyField: boolean; diff --git a/src/phases/damage-anim-phase.ts b/src/phases/damage-anim-phase.ts index b9581573f2e..85cb26e0a09 100644 --- a/src/phases/damage-anim-phase.ts +++ b/src/phases/damage-anim-phase.ts @@ -6,6 +6,7 @@ import { fixedInt } from "#app/utils/common"; import { PokemonPhase } from "#app/phases/pokemon-phase"; export class DamageAnimPhase extends PokemonPhase { + public readonly phaseName = "DamageAnimPhase"; private amount: number; private damageResult: DamageResult; private critical: boolean; diff --git a/src/phases/egg-hatch-phase.ts b/src/phases/egg-hatch-phase.ts index 69bcf741383..dfcdc05f9a2 100644 --- a/src/phases/egg-hatch-phase.ts +++ b/src/phases/egg-hatch-phase.ts @@ -20,6 +20,7 @@ import { doShinySparkleAnim } from "#app/field/anims"; * Class that represents egg hatching */ export class EggHatchPhase extends Phase { + public readonly phaseName = "EggHatchPhase"; /** The egg that is hatching */ private egg: Egg; /** The new EggHatchData for the egg/pokemon that hatches */ @@ -224,7 +225,7 @@ export class EggHatchPhase extends Phase { } end() { - if (globalScene.findPhase(p => p instanceof EggHatchPhase)) { + if (globalScene.findPhase(p => p.is("EggHatchPhase"))) { this.eggHatchHandler.clear(); } else { globalScene.time.delayedCall(250, () => globalScene.setModifiersVisible(true)); diff --git a/src/phases/egg-lapse-phase.ts b/src/phases/egg-lapse-phase.ts index 4632e264c1d..182d6f304d6 100644 --- a/src/phases/egg-lapse-phase.ts +++ b/src/phases/egg-lapse-phase.ts @@ -16,6 +16,7 @@ import { EggHatchData } from "#app/data/egg-hatch-data"; * Also handles prompts for skipping animation, and calling the egg summary phase */ export class EggLapsePhase extends Phase { + public readonly phaseName = "EggLapsePhase"; private eggHatchData: EggHatchData[] = []; private readonly minEggsToSkip: number = 2; diff --git a/src/phases/egg-summary-phase.ts b/src/phases/egg-summary-phase.ts index d16cafa7611..cc7857426bc 100644 --- a/src/phases/egg-summary-phase.ts +++ b/src/phases/egg-summary-phase.ts @@ -9,6 +9,7 @@ import type { EggHatchData } from "#app/data/egg-hatch-data"; * Phase is handled mostly by the egg-hatch-scene-handler UI */ export class EggSummaryPhase extends Phase { + public readonly phaseName = "EggSummaryPhase"; private eggHatchData: EggHatchData[]; constructor(eggHatchData: EggHatchData[]) { diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index c7308fc5a64..df84f8f8ff4 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -47,6 +47,8 @@ import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mys import { getNatureName } from "#app/data/nature"; 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; constructor(loaded = false) { diff --git a/src/phases/end-card-phase.ts b/src/phases/end-card-phase.ts index 41775248b67..bb64969514f 100644 --- a/src/phases/end-card-phase.ts +++ b/src/phases/end-card-phase.ts @@ -5,6 +5,7 @@ import { addTextObject, TextStyle } from "#app/ui/text"; import i18next from "i18next"; export class EndCardPhase extends Phase { + public readonly phaseName = "EndCardPhase"; public endCard: Phaser.GameObjects.Image; public text: Phaser.GameObjects.Text; start(): void { diff --git a/src/phases/end-evolution-phase.ts b/src/phases/end-evolution-phase.ts index 579920dde90..cfc0d89fc31 100644 --- a/src/phases/end-evolution-phase.ts +++ b/src/phases/end-evolution-phase.ts @@ -3,6 +3,7 @@ import { Phase } from "#app/phase"; import { UiMode } from "#enums/ui-mode"; export class EndEvolutionPhase extends Phase { + public readonly phaseName = "EndEvolutionPhase"; start() { super.start(); diff --git a/src/phases/enemy-command-phase.ts b/src/phases/enemy-command-phase.ts index 2a1719f9002..a81fc4d2107 100644 --- a/src/phases/enemy-command-phase.ts +++ b/src/phases/enemy-command-phase.ts @@ -15,6 +15,7 @@ import { BattlerTagType } from "#enums/battler-tag-type"; * @see {@linkcode EnemyPokemon.getNextMove} */ export class EnemyCommandPhase extends FieldPhase { + public readonly phaseName = "EnemyCommandPhase"; protected fieldIndex: number; protected skipTurn = false; diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index 8fc8a8be031..5e635f4cd82 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -19,6 +19,9 @@ import { EndEvolutionPhase } from "#app/phases/end-evolution-phase"; import { EVOLVE_MOVE } from "#app/data/balance/pokemon-level-moves"; 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 lastLevel: number; diff --git a/src/phases/exp-phase.ts b/src/phases/exp-phase.ts index 8841a90d5b1..14d7d7578f8 100644 --- a/src/phases/exp-phase.ts +++ b/src/phases/exp-phase.ts @@ -7,6 +7,7 @@ import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-pha import { LevelUpPhase } from "./level-up-phase"; export class ExpPhase extends PlayerPartyMemberPokemonPhase { + public readonly phaseName = "ExpPhase"; private expValue: number; constructor(partyMemberIndex: number, expValue: number) { diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index cd40026e55f..7332d6b9462 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -35,6 +35,7 @@ import { FRIENDSHIP_LOSS_FROM_FAINT } from "#app/data/balance/starters"; import { BattlerTagType } from "#enums/battler-tag-type"; export class FaintPhase extends PokemonPhase { + public readonly phaseName = "FaintPhase"; /** * Whether or not instant revive should be prevented */ diff --git a/src/phases/form-change-phase.ts b/src/phases/form-change-phase.ts index 5517fb0f402..f5e428c6d3d 100644 --- a/src/phases/form-change-phase.ts +++ b/src/phases/form-change-phase.ts @@ -13,6 +13,7 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { SpeciesFormKey } from "#enums/species-form-key"; export class FormChangePhase extends EvolutionPhase { + public readonly phaseName = "FormChangePhase"; private formChange: SpeciesFormChange; private modal: boolean; diff --git a/src/phases/game-over-modifier-reward-phase.ts b/src/phases/game-over-modifier-reward-phase.ts index ab6f6554c99..13c8f48abad 100644 --- a/src/phases/game-over-modifier-reward-phase.ts +++ b/src/phases/game-over-modifier-reward-phase.ts @@ -4,6 +4,7 @@ import i18next from "i18next"; import { ModifierRewardPhase } from "./modifier-reward-phase"; export class GameOverModifierRewardPhase extends ModifierRewardPhase { + public readonly phaseName = "GameOverModifierRewardPhase"; doReward(): Promise { return new Promise(resolve => { const newModifier = this.modifierType.newModifier(); diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts index 3a3305fd45e..1d03739d610 100644 --- a/src/phases/game-over-phase.ts +++ b/src/phases/game-over-phase.ts @@ -34,6 +34,7 @@ import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { MessagePhase } from "./message-phase"; export class GameOverPhase extends BattlePhase { + public readonly phaseName = "GameOverPhase"; private isVictory: boolean; private firstRibbons: PokemonSpecies[] = []; diff --git a/src/phases/hide-ability-phase.ts b/src/phases/hide-ability-phase.ts index 142bb4b251d..b0a12da24b1 100644 --- a/src/phases/hide-ability-phase.ts +++ b/src/phases/hide-ability-phase.ts @@ -2,6 +2,7 @@ import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; export class HideAbilityPhase extends Phase { + public readonly phaseName = "HideAbilityPhase"; start() { super.start(); diff --git a/src/phases/hide-party-exp-bar-phase.ts b/src/phases/hide-party-exp-bar-phase.ts index 52cfd1f71d6..9ee08280cd4 100644 --- a/src/phases/hide-party-exp-bar-phase.ts +++ b/src/phases/hide-party-exp-bar-phase.ts @@ -2,6 +2,7 @@ import { globalScene } from "#app/global-scene"; import { BattlePhase } from "./battle-phase"; export class HidePartyExpBarPhase extends BattlePhase { + public readonly phaseName = "HidePartyExpBarPhase"; start() { super.start(); diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts index 65679a7ade7..d455ed35591 100644 --- a/src/phases/learn-move-phase.ts +++ b/src/phases/learn-move-phase.ts @@ -12,7 +12,6 @@ import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase"; import type Pokemon from "#app/field/pokemon"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; export enum LearnMoveType { /** 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 { + public readonly phaseName = "LearnMovePhase"; private moveId: MoveId; private messageMode: UiMode; private learnMoveType: LearnMoveType; @@ -195,7 +195,7 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { pokemon.usedTMs = []; } pokemon.usedTMs.push(this.moveId); - globalScene.tryRemovePhase(phase => phase instanceof SelectModifierPhase); + globalScene.tryRemovePhase(phase => phase.is("SelectModifierPhase")); } else if (this.learnMoveType === LearnMoveType.MEMORY) { if (this.cost !== -1) { if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { @@ -205,7 +205,7 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { } globalScene.playSound("se/buy"); } else { - globalScene.tryRemovePhase(phase => phase instanceof SelectModifierPhase); + globalScene.tryRemovePhase(phase => phase.is("SelectModifierPhase")); } } pokemon.setMove(index, this.moveId); diff --git a/src/phases/level-cap-phase.ts b/src/phases/level-cap-phase.ts index 6f3fa6fdb39..12d4d64e8e2 100644 --- a/src/phases/level-cap-phase.ts +++ b/src/phases/level-cap-phase.ts @@ -4,6 +4,7 @@ import i18next from "i18next"; import { FieldPhase } from "./field-phase"; export class LevelCapPhase extends FieldPhase { + public readonly phaseName = "LevelCapPhase"; start(): void { super.start(); diff --git a/src/phases/level-up-phase.ts b/src/phases/level-up-phase.ts index 8c4f4f58095..7cf86a313df 100644 --- a/src/phases/level-up-phase.ts +++ b/src/phases/level-up-phase.ts @@ -10,6 +10,7 @@ import { NumberHolder } from "#app/utils/common"; import i18next from "i18next"; export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { + public readonly phaseName = "LevelUpPhase"; protected lastLevel: number; protected level: number; protected pokemon: PlayerPokemon = this.getPlayerPokemon(); diff --git a/src/phases/load-move-anim-phase.ts b/src/phases/load-move-anim-phase.ts index c0b6cd58c54..c9b78797407 100644 --- a/src/phases/load-move-anim-phase.ts +++ b/src/phases/load-move-anim-phase.ts @@ -8,6 +8,7 @@ import { Phase } from "#app/phase"; * isn't already loaded (e.g. for Metronome) */ export class LoadMoveAnimPhase extends Phase { + public readonly phaseName = "LoadMoveAnimPhase"; constructor(protected moveId: MoveId) { super(); } diff --git a/src/phases/login-phase.ts b/src/phases/login-phase.ts index 673b94b1148..ec12b5ddaa4 100644 --- a/src/phases/login-phase.ts +++ b/src/phases/login-phase.ts @@ -11,6 +11,7 @@ import { SelectGenderPhase } from "./select-gender-phase"; import { UnavailablePhase } from "./unavailable-phase"; export class LoginPhase extends Phase { + public readonly phaseName = "LoginPhase"; private showText: boolean; constructor(showText = true) { diff --git a/src/phases/message-phase.ts b/src/phases/message-phase.ts index b277d67de82..335258abe5c 100644 --- a/src/phases/message-phase.ts +++ b/src/phases/message-phase.ts @@ -2,6 +2,7 @@ import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; export class MessagePhase extends Phase { + public readonly phaseName = "MessagePhase"; private text: string; private callbackDelay?: number | null; private prompt?: boolean | null; diff --git a/src/phases/modifier-reward-phase.ts b/src/phases/modifier-reward-phase.ts index c94c4deb819..83bd8704f59 100644 --- a/src/phases/modifier-reward-phase.ts +++ b/src/phases/modifier-reward-phase.ts @@ -5,6 +5,10 @@ import i18next from "i18next"; import { BattlePhase } from "./battle-phase"; 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; constructor(modifierTypeFunc: ModifierTypeFunc) { diff --git a/src/phases/money-reward-phase.ts b/src/phases/money-reward-phase.ts index 708bb3a2fa8..52cb9ecb3ff 100644 --- a/src/phases/money-reward-phase.ts +++ b/src/phases/money-reward-phase.ts @@ -6,6 +6,7 @@ import { NumberHolder } from "#app/utils/common"; import { BattlePhase } from "./battle-phase"; export class MoneyRewardPhase extends BattlePhase { + public readonly phaseName = "MoneyRewardPhase"; private moneyMultiplier: number; constructor(moneyMultiplier: number) { diff --git a/src/phases/move-anim-phase.ts b/src/phases/move-anim-phase.ts index 830e72cb8be..383841a0146 100644 --- a/src/phases/move-anim-phase.ts +++ b/src/phases/move-anim-phase.ts @@ -5,6 +5,8 @@ import { Phase } from "#app/phase"; * Plays the given {@linkcode MoveAnim} sequentially. */ export class MoveAnimPhase extends Phase { + public readonly phaseName = "MoveAnimPhase"; + constructor( protected anim: Anim, protected onSubstitute = false, diff --git a/src/phases/move-charge-phase.ts b/src/phases/move-charge-phase.ts index ea43f1ddb88..789651623fa 100644 --- a/src/phases/move-charge-phase.ts +++ b/src/phases/move-charge-phase.ts @@ -9,13 +9,13 @@ import { BooleanHolder } from "#app/utils/common"; import { MovePhase } from "#app/phases/move-phase"; import { PokemonPhase } from "#app/phases/pokemon-phase"; 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). * @extends {@linkcode PokemonPhase} */ export class MoveChargePhase extends PokemonPhase { + public readonly phaseName = "MoveChargePhase"; /** The move instance that this phase applies */ public move: PokemonMove; /** The field index targeted by the move (Charging moves assume single target) */ @@ -62,7 +62,7 @@ export class MoveChargePhase extends PokemonPhase { if (instantCharge.value) { // this MoveEndPhase will be duplicated by the queued MovePhase if not removed - globalScene.tryRemovePhase(phase => phase instanceof MoveEndPhase && phase.getPokemon() === user); + globalScene.tryRemovePhase(phase => phase.is("MoveEndPhase") && phase.getPokemon() === user); // queue a new MovePhase for this move's attack phase globalScene.unshiftPhase(new MovePhase(user, [this.targetIndex], this.move, false)); } else { diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 636f85f0f82..3160d848624 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -82,6 +82,7 @@ import { DamageAchv } from "#app/system/achv"; type HitCheckEntry = [HitCheckResult, TypeDamageMultiplier]; export class MoveEffectPhase extends PokemonPhase { + public readonly phaseName = "MoveEffectPhase"; public move: Move; private virtual = false; protected targets: BattlerIndex[]; diff --git a/src/phases/move-end-phase.ts b/src/phases/move-end-phase.ts index 037596dca59..6642b97773b 100644 --- a/src/phases/move-end-phase.ts +++ b/src/phases/move-end-phase.ts @@ -6,6 +6,7 @@ import { applyPostSummonAbAttrs, PostSummonRemoveEffectAbAttr } from "#app/data/ import type Pokemon from "#app/field/pokemon"; export class MoveEndPhase extends PokemonPhase { + public readonly phaseName = "MoveEndPhase"; private wasFollowUp: boolean; /** Targets from the preceding MovePhase */ diff --git a/src/phases/move-header-phase.ts b/src/phases/move-header-phase.ts index c320df462d1..50100e827d6 100644 --- a/src/phases/move-header-phase.ts +++ b/src/phases/move-header-phase.ts @@ -4,6 +4,7 @@ import type Pokemon from "#app/field/pokemon"; import { BattlePhase } from "./battle-phase"; export class MoveHeaderPhase extends BattlePhase { + public readonly phaseName = "MoveHeaderPhase"; public pokemon: Pokemon; public move: PokemonMove; diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 1ccf5b7957e..300c27e01fc 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -52,6 +52,7 @@ import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; export class MovePhase extends BattlePhase { + public readonly phaseName = "MovePhase"; protected _pokemon: Pokemon; protected _move: PokemonMove; protected _targets: BattlerIndex[]; diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index fd0c4ef7949..5365ab3da32 100644 --- a/src/phases/mystery-encounter-phases.ts +++ b/src/phases/mystery-encounter-phases.ts @@ -6,7 +6,6 @@ import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-d 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"; @@ -39,6 +38,7 @@ import { SelectBiomePhase } from "./select-biome-phase"; * - Queuing of the {@linkcode MysteryEncounterOptionSelectedPhase} */ export class MysteryEncounterPhase extends Phase { + public readonly phaseName = "MysteryEncounterPhase"; private readonly FIRST_DIALOGUE_PROMPT_DELAY = 300; optionSelectSettings?: OptionSelectSettings; @@ -180,6 +180,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 */ export class MysteryEncounterOptionSelectedPhase extends Phase { + public readonly phaseName = "MysteryEncounterOptionSelectedPhase"; onOptionSelect: OptionPhaseCallback; constructor() { @@ -221,6 +222,7 @@ export class MysteryEncounterOptionSelectedPhase extends Phase { * See {@linkcode TurnEndPhase} for more details */ export class MysteryEncounterBattleStartCleanupPhase extends Phase { + public readonly phaseName = "MysteryEncounterBattleStartCleanupPhase"; /** * Cleans up `TURN_END` tags, any {@linkcode PostTurnStatusEffectPhase}s, checks for Pokemon switches, then continues */ @@ -245,8 +247,8 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase { }); // Remove any status tick phases - while (globalScene.findPhase(p => p instanceof PostTurnStatusEffectPhase)) { - globalScene.tryRemovePhase(p => p instanceof PostTurnStatusEffectPhase); + while (globalScene.findPhase(p => p.is("PostTurnStatusEffectPhase"))) { + globalScene.tryRemovePhase(p => p.is("PostTurnStatusEffectPhase")); } // The total number of Pokemon in the player's party that can legally fight @@ -284,6 +286,7 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase { * - Queue the {@linkcode SummonPhase}s, {@linkcode PostSummonPhase}s, etc., required to initialize the phase queue for a battle */ export class MysteryEncounterBattlePhase extends Phase { + public readonly phaseName = "MysteryEncounterBattlePhase"; disableSwitch: boolean; constructor(disableSwitch = false) { @@ -513,6 +516,7 @@ export class MysteryEncounterBattlePhase extends Phase { * - Queuing of the {@linkcode PostMysteryEncounterPhase} */ export class MysteryEncounterRewardsPhase extends Phase { + public readonly phaseName = "MysteryEncounterRewardsPhase"; addHealPhase: boolean; constructor(addHealPhase = false) { @@ -558,7 +562,7 @@ export class MysteryEncounterRewardsPhase extends Phase { if (encounter.doEncounterRewards) { encounter.doEncounterRewards(); } else if (this.addHealPhase) { - globalScene.tryRemovePhase(p => p instanceof SelectModifierPhase); + globalScene.tryRemovePhase(p => p.is("SelectModifierPhase")); globalScene.unshiftPhase( new SelectModifierPhase(0, undefined, { fillRemaining: false, @@ -580,6 +584,7 @@ export class MysteryEncounterRewardsPhase extends Phase { * - Queuing of the next wave */ export class PostMysteryEncounterPhase extends Phase { + public readonly phaseName = "PostMysteryEncounterPhase"; private readonly FIRST_DIALOGUE_PROMPT_DELAY = 750; onPostOptionSelect?: OptionPhaseCallback; diff --git a/src/phases/new-battle-phase.ts b/src/phases/new-battle-phase.ts index 09b8ab1d335..c4cfc72fb53 100644 --- a/src/phases/new-battle-phase.ts +++ b/src/phases/new-battle-phase.ts @@ -2,13 +2,14 @@ import { globalScene } from "#app/global-scene"; import { BattlePhase } from "./battle-phase"; export class NewBattlePhase extends BattlePhase { + public readonly phaseName = "NewBattlePhase"; start() { super.start(); // cull any extra `NewBattle` phases from the queue. - globalScene.phaseQueue = globalScene.phaseQueue.filter(phase => !(phase instanceof NewBattlePhase)); + globalScene.phaseQueue = globalScene.phaseQueue.filter(phase => !phase.is("NewBattlePhase")); // `phaseQueuePrepend` is private, so we have to use this inefficient loop. - while (globalScene.tryRemoveUnshiftedPhase(phase => phase instanceof NewBattlePhase)) {} + while (globalScene.tryRemoveUnshiftedPhase(phase => phase.is("NewBattlePhase"))) {} globalScene.newBattle(); diff --git a/src/phases/new-biome-encounter-phase.ts b/src/phases/new-biome-encounter-phase.ts index ef027bfd77a..29ba67cb797 100644 --- a/src/phases/new-biome-encounter-phase.ts +++ b/src/phases/new-biome-encounter-phase.ts @@ -4,6 +4,7 @@ import { getRandomWeatherType } from "#app/data/weather"; import { NextEncounterPhase } from "./next-encounter-phase"; export class NewBiomeEncounterPhase extends NextEncounterPhase { + public readonly phaseName = "NewBiomeEncounterPhase"; doEncounter(): void { globalScene.playBgm(undefined, true); diff --git a/src/phases/next-encounter-phase.ts b/src/phases/next-encounter-phase.ts index 30b4004363c..c31b4b5bbc3 100644 --- a/src/phases/next-encounter-phase.ts +++ b/src/phases/next-encounter-phase.ts @@ -6,6 +6,7 @@ import { EncounterPhase } from "./encounter-phase"; * Handles generating, loading and preparing for it. */ export class NextEncounterPhase extends EncounterPhase { + public readonly phaseName: "NextEncounterPhase" | "NewBiomeEncounterPhase" = "NextEncounterPhase"; start() { super.start(); } diff --git a/src/phases/obtain-status-effect-phase.ts b/src/phases/obtain-status-effect-phase.ts index 47cae2dcbf6..820db910681 100644 --- a/src/phases/obtain-status-effect-phase.ts +++ b/src/phases/obtain-status-effect-phase.ts @@ -11,6 +11,7 @@ import { applyPostSetStatusAbAttrs, PostSetStatusAbAttr } from "#app/data/abilit import { isNullOrUndefined } from "#app/utils/common"; export class ObtainStatusEffectPhase extends PokemonPhase { + public readonly phaseName = "ObtainStatusEffectPhase"; private statusEffect?: StatusEffect; private turnsRemaining?: number; private sourceText?: string | null; diff --git a/src/phases/party-exp-phase.ts b/src/phases/party-exp-phase.ts index 8fd9e1cf0f6..30fc97d9105 100644 --- a/src/phases/party-exp-phase.ts +++ b/src/phases/party-exp-phase.ts @@ -6,6 +6,7 @@ import { Phase } from "#app/phase"; * Intended to be used as a more 1-off phase to provide exp to the party (such as during MEs), rather than cleanup a battle entirely */ export class PartyExpPhase extends Phase { + public readonly phaseName = "PartyExpPhase"; expValue: number; useWaveIndexMultiplier?: boolean; pokemonParticipantIds?: Set; diff --git a/src/phases/party-heal-phase.ts b/src/phases/party-heal-phase.ts index 4a9f8a0c888..765c7dbad8e 100644 --- a/src/phases/party-heal-phase.ts +++ b/src/phases/party-heal-phase.ts @@ -3,6 +3,7 @@ import { fixedInt } from "#app/utils/common"; import { BattlePhase } from "./battle-phase"; export class PartyHealPhase extends BattlePhase { + public readonly phaseName = "PartyHealPhase"; private resumeBgm: boolean; constructor(resumeBgm: boolean) { diff --git a/src/phases/pokemon-anim-phase.ts b/src/phases/pokemon-anim-phase.ts index e9f0097459a..b1a21446996 100644 --- a/src/phases/pokemon-anim-phase.ts +++ b/src/phases/pokemon-anim-phase.ts @@ -7,6 +7,7 @@ import { PokemonAnimType } from "#enums/pokemon-anim-type"; import { SpeciesId } from "#enums/species-id"; export class PokemonAnimPhase extends BattlePhase { + public readonly phaseName = "PokemonAnimPhase"; /** The type of animation to play in this phase */ protected key: PokemonAnimType; /** The Pokemon to which this animation applies */ diff --git a/src/phases/pokemon-heal-phase.ts b/src/phases/pokemon-heal-phase.ts index 7cb013251f6..60bbb17c30a 100644 --- a/src/phases/pokemon-heal-phase.ts +++ b/src/phases/pokemon-heal-phase.ts @@ -14,6 +14,7 @@ import { BattlerTagType } from "#app/enums/battler-tag-type"; import type { HealBlockTag } from "#app/data/battler-tags"; export class PokemonHealPhase extends CommonAnimPhase { + public readonly phaseName = "PokemonHealPhase"; private hpHealed: number; private message: string | null; private showFullHpMessage: boolean; diff --git a/src/phases/pokemon-transform-phase.ts b/src/phases/pokemon-transform-phase.ts index 23a9a983bae..c0f3b048003 100644 --- a/src/phases/pokemon-transform-phase.ts +++ b/src/phases/pokemon-transform-phase.ts @@ -13,6 +13,7 @@ import i18next from "i18next"; * Used for Transform (move) and Imposter (ability) */ export class PokemonTransformPhase extends PokemonPhase { + public readonly phaseName = "PokemonTransformPhase"; protected targetIndex: BattlerIndex; private playSound: boolean; diff --git a/src/phases/post-game-over-phase.ts b/src/phases/post-game-over-phase.ts index 753251e992f..f985419da7a 100644 --- a/src/phases/post-game-over-phase.ts +++ b/src/phases/post-game-over-phase.ts @@ -4,6 +4,7 @@ import type { EndCardPhase } from "./end-card-phase"; import { TitlePhase } from "./title-phase"; export class PostGameOverPhase extends Phase { + public readonly phaseName = "PostGameOverPhase"; private endCardPhase?: EndCardPhase; constructor(endCardPhase?: EndCardPhase) { diff --git a/src/phases/post-summon-phase.ts b/src/phases/post-summon-phase.ts index 446d45bb2fa..a7faf614292 100644 --- a/src/phases/post-summon-phase.ts +++ b/src/phases/post-summon-phase.ts @@ -7,6 +7,7 @@ import { MysteryEncounterPostSummonTag } from "#app/data/battler-tags"; import { BattlerTagType } from "#enums/battler-tag-type"; export class PostSummonPhase extends PokemonPhase { + public readonly phaseName = "PostSummonPhase"; start() { super.start(); diff --git a/src/phases/post-turn-status-effect-phase.ts b/src/phases/post-turn-status-effect-phase.ts index 9b530d48196..47a84059745 100644 --- a/src/phases/post-turn-status-effect-phase.ts +++ b/src/phases/post-turn-status-effect-phase.ts @@ -17,6 +17,7 @@ import { BooleanHolder, NumberHolder } from "#app/utils/common"; import { PokemonPhase } from "./pokemon-phase"; export class PostTurnStatusEffectPhase extends PokemonPhase { + public readonly phaseName = "PostTurnStatusEffectPhase"; // biome-ignore lint/complexity/noUselessConstructor: Not unnecessary as it makes battlerIndex required constructor(battlerIndex: BattlerIndex) { super(battlerIndex); diff --git a/src/phases/quiet-form-change-phase.ts b/src/phases/quiet-form-change-phase.ts index 76411f62f77..9f6b5cb3361 100644 --- a/src/phases/quiet-form-change-phase.ts +++ b/src/phases/quiet-form-change-phase.ts @@ -9,7 +9,7 @@ import type Pokemon from "#app/field/pokemon"; import { EnemyPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { BattlePhase } from "./battle-phase"; -import { MovePhase } from "./move-phase"; +import type { MovePhase } from "./move-phase"; import { PokemonHealPhase } from "./pokemon-heal-phase"; import { applyAbAttrs, @@ -19,6 +19,7 @@ import { } from "#app/data/abilities/ability"; export class QuietFormChangePhase extends BattlePhase { + public readonly phaseName = "QuietFormChangePhase"; protected pokemon: Pokemon; protected formChange: SpeciesFormChange; @@ -168,7 +169,7 @@ export class QuietFormChangePhase extends BattlePhase { this.pokemon.initBattleInfo(); this.pokemon.cry(); - const movePhase = globalScene.findPhase(p => p instanceof MovePhase && p.pokemon === this.pokemon) as MovePhase; + const movePhase = globalScene.findPhase(p => p.is("MovePhase") && p.pokemon === this.pokemon) as MovePhase; if (movePhase) { movePhase.cancel(); } diff --git a/src/phases/reload-session-phase.ts b/src/phases/reload-session-phase.ts index 8cd5f67b43a..ac9337753c4 100644 --- a/src/phases/reload-session-phase.ts +++ b/src/phases/reload-session-phase.ts @@ -4,6 +4,7 @@ import { UiMode } from "#enums/ui-mode"; import { fixedInt } from "#app/utils/common"; export class ReloadSessionPhase extends Phase { + public readonly phaseName = "ReloadSessionPhase"; private systemDataStr?: string; constructor(systemDataStr?: string) { diff --git a/src/phases/reset-status-phase.ts b/src/phases/reset-status-phase.ts index 19bfc3027e2..779f375d7e2 100644 --- a/src/phases/reset-status-phase.ts +++ b/src/phases/reset-status-phase.ts @@ -7,6 +7,7 @@ import { BattlePhase } from "#app/phases/battle-phase"; * This is necessary to perform in a phase primarly to ensure that the status icon disappears at the correct time in the battle */ export class ResetStatusPhase extends BattlePhase { + public readonly phaseName = "ResetStatusPhase"; private readonly pokemon: Pokemon; private readonly affectConfusion: boolean; private readonly reloadAssets: boolean; diff --git a/src/phases/return-phase.ts b/src/phases/return-phase.ts index 6dee982a4f0..6365256d40a 100644 --- a/src/phases/return-phase.ts +++ b/src/phases/return-phase.ts @@ -4,6 +4,7 @@ import { SwitchType } from "#enums/switch-type"; import { SwitchSummonPhase } from "./switch-summon-phase"; export class ReturnPhase extends SwitchSummonPhase { + public readonly phaseName = "ReturnPhase"; constructor(fieldIndex: number) { super(SwitchType.SWITCH, fieldIndex, -1, true); } diff --git a/src/phases/revival-blessing-phase.ts b/src/phases/revival-blessing-phase.ts index 428acaf9ed4..3f70c93dd7a 100644 --- a/src/phases/revival-blessing-phase.ts +++ b/src/phases/revival-blessing-phase.ts @@ -15,6 +15,7 @@ import type { PlayerPokemon } from "#app/field/pokemon"; * when used by one of the player's Pokemon. */ export class RevivalBlessingPhase extends BattlePhase { + public readonly phaseName = "RevivalBlessingPhase"; constructor(protected user: PlayerPokemon) { super(); } diff --git a/src/phases/ribbon-modifier-reward-phase.ts b/src/phases/ribbon-modifier-reward-phase.ts index 21114ab3de9..949f7af0302 100644 --- a/src/phases/ribbon-modifier-reward-phase.ts +++ b/src/phases/ribbon-modifier-reward-phase.ts @@ -6,6 +6,7 @@ import i18next from "i18next"; import { ModifierRewardPhase } from "./modifier-reward-phase"; export class RibbonModifierRewardPhase extends ModifierRewardPhase { + public readonly phaseName = "RibbonModifierRewardPhase"; private species: PokemonSpecies; constructor(modifierTypeFunc: ModifierTypeFunc, species: PokemonSpecies) { diff --git a/src/phases/scan-ivs-phase.ts b/src/phases/scan-ivs-phase.ts index d79a32bd47e..df68a2d1cab 100644 --- a/src/phases/scan-ivs-phase.ts +++ b/src/phases/scan-ivs-phase.ts @@ -8,6 +8,7 @@ import i18next from "i18next"; import { PokemonPhase } from "./pokemon-phase"; export class ScanIvsPhase extends PokemonPhase { + public readonly phaseName = "ScanIvsPhase"; // biome-ignore lint/complexity/noUselessConstructor: This changes `battlerIndex` to be required constructor(battlerIndex: BattlerIndex) { super(battlerIndex); diff --git a/src/phases/select-biome-phase.ts b/src/phases/select-biome-phase.ts index a7736b16811..ef6b39e8b8f 100644 --- a/src/phases/select-biome-phase.ts +++ b/src/phases/select-biome-phase.ts @@ -10,6 +10,7 @@ import { PartyHealPhase } from "./party-heal-phase"; import { SwitchBiomePhase } from "./switch-biome-phase"; export class SelectBiomePhase extends BattlePhase { + public readonly phaseName = "SelectBiomePhase"; start() { super.start(); diff --git a/src/phases/select-challenge-phase.ts b/src/phases/select-challenge-phase.ts index 76ac8a60c4f..dcf72d1b441 100644 --- a/src/phases/select-challenge-phase.ts +++ b/src/phases/select-challenge-phase.ts @@ -3,6 +3,7 @@ import { Phase } from "#app/phase"; import { UiMode } from "#enums/ui-mode"; export class SelectChallengePhase extends Phase { + public readonly phaseName = "SelectChallengePhase"; start() { super.start(); diff --git a/src/phases/select-gender-phase.ts b/src/phases/select-gender-phase.ts index a1171c1a5db..ad8515e312e 100644 --- a/src/phases/select-gender-phase.ts +++ b/src/phases/select-gender-phase.ts @@ -6,6 +6,7 @@ import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; export class SelectGenderPhase extends Phase { + public readonly phaseName = "SelectGenderPhase"; start(): void { super.start(); diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 5f11441333b..6e429d9ad7f 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -32,6 +32,7 @@ import type { CustomModifierSettings } from "#app/modifier/modifier-type"; import { isNullOrUndefined, NumberHolder } from "#app/utils/common"; export class SelectModifierPhase extends BattlePhase { + public readonly phaseName = "SelectModifierPhase"; private rerollCount: number; private modifierTiers?: ModifierTier[]; private customModifierSettings?: CustomModifierSettings; diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index 6d333f4001c..d25c2dd7211 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -15,6 +15,7 @@ import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import { isNullOrUndefined } from "#app/utils/common"; export class SelectStarterPhase extends Phase { + public readonly phaseName = "SelectStarterPhase"; start() { super.start(); diff --git a/src/phases/select-target-phase.ts b/src/phases/select-target-phase.ts index f8a8ecfbf18..46ba378a56c 100644 --- a/src/phases/select-target-phase.ts +++ b/src/phases/select-target-phase.ts @@ -8,6 +8,7 @@ import i18next from "#app/plugins/i18n"; import { allMoves } from "#app/data/data-lists"; export class SelectTargetPhase extends PokemonPhase { + public readonly phaseName = "SelectTargetPhase"; // biome-ignore lint/complexity/noUselessConstructor: This makes `fieldIndex` required constructor(fieldIndex: number) { super(fieldIndex); diff --git a/src/phases/shiny-sparkle-phase.ts b/src/phases/shiny-sparkle-phase.ts index 87a7db29cf6..93d7dd67209 100644 --- a/src/phases/shiny-sparkle-phase.ts +++ b/src/phases/shiny-sparkle-phase.ts @@ -3,6 +3,7 @@ import type { BattlerIndex } from "#app/battle"; import { PokemonPhase } from "./pokemon-phase"; export class ShinySparklePhase extends PokemonPhase { + public readonly phaseName = "ShinySparklePhase"; // biome-ignore lint/complexity/noUselessConstructor: This makes `battlerIndex` required constructor(battlerIndex: BattlerIndex) { super(battlerIndex); diff --git a/src/phases/show-ability-phase.ts b/src/phases/show-ability-phase.ts index d6193ac3946..81aa69537e5 100644 --- a/src/phases/show-ability-phase.ts +++ b/src/phases/show-ability-phase.ts @@ -5,6 +5,7 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { HideAbilityPhase } from "#app/phases/hide-ability-phase"; export class ShowAbilityPhase extends PokemonPhase { + public readonly phaseName = "ShowAbilityPhase"; private passive: boolean; private pokemonName: string; private abilityName: string; diff --git a/src/phases/show-party-exp-bar-phase.ts b/src/phases/show-party-exp-bar-phase.ts index 89bec6d8fdd..6b4236f0868 100644 --- a/src/phases/show-party-exp-bar-phase.ts +++ b/src/phases/show-party-exp-bar-phase.ts @@ -8,6 +8,7 @@ import { LevelUpPhase } from "./level-up-phase"; import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { + public readonly phaseName = "ShowPartyExpBarPhase"; private expValue: number; constructor(partyMemberIndex: number, expValue: number) { diff --git a/src/phases/show-trainer-phase.ts b/src/phases/show-trainer-phase.ts index b6c1e345c70..bae6ecd839c 100644 --- a/src/phases/show-trainer-phase.ts +++ b/src/phases/show-trainer-phase.ts @@ -3,6 +3,7 @@ import { PlayerGender } from "#app/enums/player-gender"; import { BattlePhase } from "./battle-phase"; export class ShowTrainerPhase extends BattlePhase { + public readonly phaseName = "ShowTrainerPhase"; start() { super.start(); diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index 6731e45025c..baa93c63099 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -31,6 +31,7 @@ export type StatStageChangeCallback = ( ) => void; export class StatStageChangePhase extends PokemonPhase { + public readonly phaseName = "StatStageChangePhase"; private stats: BattleStat[]; private selfTarget: boolean; private stages: number; @@ -235,9 +236,9 @@ export class StatStageChangePhase extends PokemonPhase { // Look for any other stat change phases; if this is the last one, do White Herb check const existingPhase = globalScene.findPhase( - p => p instanceof StatStageChangePhase && p.battlerIndex === this.battlerIndex, + p => p.is("StatStageChangePhase") && p.battlerIndex === this.battlerIndex, ); - if (!(existingPhase instanceof StatStageChangePhase)) { + if (!existingPhase?.is("StatStageChangePhase")) { // Apply White Herb if needed const whiteHerb = globalScene.applyModifier( ResetNegativeStatStageModifier, @@ -316,7 +317,7 @@ export class StatStageChangePhase extends PokemonPhase { while ( (existingPhase = globalScene.findPhase( p => - p instanceof StatStageChangePhase && + p.is("StatStageChangePhase") && p.battlerIndex === this.battlerIndex && p.stats.length === 1 && p.stats[0] === this.stats[0] && @@ -335,7 +336,7 @@ export class StatStageChangePhase extends PokemonPhase { while ( (existingPhase = globalScene.findPhase( p => - p instanceof StatStageChangePhase && + p.is("StatStageChangePhase") && p.battlerIndex === this.battlerIndex && p.selfTarget === this.selfTarget && accEva.some(s => p.stats.includes(s)) === isAccEva && diff --git a/src/phases/summon-missing-phase.ts b/src/phases/summon-missing-phase.ts index a692455ce47..ce3e982055e 100644 --- a/src/phases/summon-missing-phase.ts +++ b/src/phases/summon-missing-phase.ts @@ -4,6 +4,7 @@ import { SummonPhase } from "./summon-phase"; import { globalScene } from "#app/global-scene"; export class SummonMissingPhase extends SummonPhase { + public readonly phaseName = "SummonMissingPhase"; preSummon(): void { globalScene.ui.showText( i18next.t("battle:sendOutPokemon", { diff --git a/src/phases/summon-phase.ts b/src/phases/summon-phase.ts index c217583f163..2cd7b122bb3 100644 --- a/src/phases/summon-phase.ts +++ b/src/phases/summon-phase.ts @@ -17,6 +17,8 @@ import { applyPreSummonAbAttrs, PreSummonAbAttr } from "#app/data/abilities/abil import { globalScene } from "#app/global-scene"; export class SummonPhase extends PartyMemberPokemonPhase { + // The union type is needed to keep typescript happy as these phases extend from SummonPhase + public readonly phaseName: "SummonPhase" | "SummonMissingPhase" | "SwitchSummonPhase" | "ReturnPhase" = "SummonPhase"; private loaded: boolean; constructor(fieldIndex: number, player = true, loaded = false) { diff --git a/src/phases/switch-biome-phase.ts b/src/phases/switch-biome-phase.ts index 69a6c97cd9a..f84f1d517b4 100644 --- a/src/phases/switch-biome-phase.ts +++ b/src/phases/switch-biome-phase.ts @@ -4,6 +4,7 @@ import { getBiomeKey } from "#app/field/arena"; import { BattlePhase } from "./battle-phase"; export class SwitchBiomePhase extends BattlePhase { + public readonly phaseName = "SwitchBiomePhase"; private nextBiome: BiomeId; constructor(nextBiome: BiomeId) { diff --git a/src/phases/switch-phase.ts b/src/phases/switch-phase.ts index c056b186021..6017aa0fa70 100644 --- a/src/phases/switch-phase.ts +++ b/src/phases/switch-phase.ts @@ -3,7 +3,6 @@ import PartyUiHandler, { PartyOption, PartyUiMode } from "#app/ui/party-ui-handl import { UiMode } from "#enums/ui-mode"; import { SwitchType } from "#enums/switch-type"; import { BattlePhase } from "./battle-phase"; -import { PostSummonPhase } from "./post-summon-phase"; import { SwitchSummonPhase } from "./switch-summon-phase"; /** @@ -11,6 +10,7 @@ import { SwitchSummonPhase } from "./switch-summon-phase"; * for the player (if a switch would be valid for the current battle state). */ export class SwitchPhase extends BattlePhase { + public readonly phaseName = "SwitchPhase"; protected readonly fieldIndex: number; private readonly switchType: SwitchType; private readonly isModal: boolean; @@ -76,7 +76,7 @@ export class SwitchPhase extends BattlePhase { if (slotIndex >= globalScene.currentBattle.getBattlerCount() && slotIndex < 6) { // Remove any pre-existing PostSummonPhase under the same field index. // Pre-existing PostSummonPhases may occur when this phase is invoked during a prompt to switch at the start of a wave. - globalScene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex); + globalScene.tryRemovePhase(p => p.is("PostSummonPhase") && p.player && p.fieldIndex === this.fieldIndex); const switchType = option === PartyOption.PASS_BATON ? SwitchType.BATON_PASS : this.switchType; globalScene.unshiftPhase(new SwitchSummonPhase(switchType, fieldIndex, slotIndex, this.doReturn)); } diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index 6bdbb66be14..d81ca6029c5 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -22,6 +22,7 @@ import { SubstituteTag } from "#app/data/battler-tags"; import { SwitchType } from "#enums/switch-type"; export class SwitchSummonPhase extends SummonPhase { + public readonly phaseName: "SwitchSummonPhase" | "ReturnPhase" = "SwitchSummonPhase"; private readonly switchType: SwitchType; private readonly slotIndex: number; private readonly doReturn: boolean; diff --git a/src/phases/tera-phase.ts b/src/phases/tera-phase.ts index c9320daf12f..5e4ea2fe54e 100644 --- a/src/phases/tera-phase.ts +++ b/src/phases/tera-phase.ts @@ -9,6 +9,7 @@ import { SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms"; import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims"; export class TeraPhase extends BattlePhase { + public readonly phaseName = "TeraPhase"; public pokemon: Pokemon; constructor(pokemon: Pokemon) { diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 56057c23372..aa9ae49ca8b 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -29,6 +29,7 @@ import { globalScene } from "#app/global-scene"; import Overrides from "#app/overrides"; export class TitlePhase extends Phase { + public readonly phaseName = "TitlePhase"; private loaded = false; private lastSessionData: SessionSaveData; public gameMode: GameModes; diff --git a/src/phases/toggle-double-position-phase.ts b/src/phases/toggle-double-position-phase.ts index 37f47d5cf95..a6b8705f580 100644 --- a/src/phases/toggle-double-position-phase.ts +++ b/src/phases/toggle-double-position-phase.ts @@ -3,6 +3,7 @@ import { FieldPosition } from "#app/field/pokemon"; import { BattlePhase } from "./battle-phase"; export class ToggleDoublePositionPhase extends BattlePhase { + public readonly phaseName = "ToggleDoublePositionPhase"; private double: boolean; constructor(double: boolean) { diff --git a/src/phases/trainer-victory-phase.ts b/src/phases/trainer-victory-phase.ts index daf5c38e57b..bd035248530 100644 --- a/src/phases/trainer-victory-phase.ts +++ b/src/phases/trainer-victory-phase.ts @@ -14,6 +14,7 @@ import { achvs } from "#app/system/achv"; import { timedEventManager } from "#app/global-event-manager"; export class TrainerVictoryPhase extends BattlePhase { + public readonly phaseName = "TrainerVictoryPhase"; start() { globalScene.disableMenu = true; diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index 756c497802b..4d486c4bbd6 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -18,6 +18,7 @@ import { PokemonHealPhase } from "./pokemon-heal-phase"; import { globalScene } from "#app/global-scene"; export class TurnEndPhase extends FieldPhase { + public readonly phaseName = "TurnEndPhase"; start() { super.start(); diff --git a/src/phases/turn-init-phase.ts b/src/phases/turn-init-phase.ts index 0c110024af7..7f94acd3b32 100644 --- a/src/phases/turn-init-phase.ts +++ b/src/phases/turn-init-phase.ts @@ -15,6 +15,7 @@ import { TurnStartPhase } from "./turn-start-phase"; import { globalScene } from "#app/global-scene"; export class TurnInitPhase extends FieldPhase { + public readonly phaseName = "TurnInitPhase"; start() { super.start(); diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index a02d869af10..2d009b30bf3 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -25,6 +25,7 @@ import { globalScene } from "#app/global-scene"; import { TeraPhase } from "./tera-phase"; export class TurnStartPhase extends FieldPhase { + public readonly phaseName = "TurnStartPhase"; /** * This orders the active Pokemon on the field by speed into an BattlerIndex array and returns that array. * It also checks for Trick Room and reverses the array if it is present. diff --git a/src/phases/unavailable-phase.ts b/src/phases/unavailable-phase.ts index e5f1d899191..4c4333ceb90 100644 --- a/src/phases/unavailable-phase.ts +++ b/src/phases/unavailable-phase.ts @@ -4,6 +4,7 @@ import { UiMode } from "#enums/ui-mode"; import { LoginPhase } from "./login-phase"; export class UnavailablePhase extends Phase { + public readonly phaseName = "UnavailablePhase"; start(): void { globalScene.ui.setMode(UiMode.UNAVAILABLE, () => { globalScene.unshiftPhase(new LoginPhase(true)); diff --git a/src/phases/unlock-phase.ts b/src/phases/unlock-phase.ts index 7a69fc207bb..839ac31dc5d 100644 --- a/src/phases/unlock-phase.ts +++ b/src/phases/unlock-phase.ts @@ -6,6 +6,7 @@ import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; export class UnlockPhase extends Phase { + public readonly phaseName = "UnlockPhase"; private unlockable: Unlockables; constructor(unlockable: Unlockables) { diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts index 1204877fec2..2d21f8abc08 100644 --- a/src/phases/victory-phase.ts +++ b/src/phases/victory-phase.ts @@ -18,6 +18,7 @@ import { timedEventManager } from "#app/global-event-manager"; import { SelectBiomePhase } from "./select-biome-phase"; export class VictoryPhase extends PokemonPhase { + public readonly phaseName = "VictoryPhase"; /** If true, indicates that the phase is intended for EXP purposes only, and not to continue a battle to next phase */ isExpOnly: boolean; diff --git a/src/phases/weather-effect-phase.ts b/src/phases/weather-effect-phase.ts index d89c78e96c7..cd91b89771c 100644 --- a/src/phases/weather-effect-phase.ts +++ b/src/phases/weather-effect-phase.ts @@ -19,6 +19,7 @@ import { BooleanHolder, toDmgValue } from "#app/utils/common"; import { CommonAnimPhase } from "./common-anim-phase"; export class WeatherEffectPhase extends CommonAnimPhase { + public readonly phaseName = "WeatherEffectPhase"; public weather: Weather | null; constructor() { diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts index fbfd4d2623b..0d67fdea624 100644 --- a/src/ui/command-ui-handler.ts +++ b/src/ui/command-ui-handler.ts @@ -5,7 +5,7 @@ import UiHandler from "./ui-handler"; import i18next from "i18next"; import { Button } from "#enums/buttons"; import { getPokemonNameWithAffix } from "#app/messages"; -import { CommandPhase } from "#app/phases/command-phase"; +import type { CommandPhase } from "#app/phases/command-phase"; import { globalScene } from "#app/global-scene"; import { TerastallizeAccessModifier } from "#app/modifier/modifier"; import { PokemonType } from "#enums/pokemon-type"; @@ -75,7 +75,7 @@ export default class CommandUiHandler extends UiHandler { let commandPhase: CommandPhase; const currentPhase = globalScene.getCurrentPhase(); - if (currentPhase instanceof CommandPhase) { + if (currentPhase?.is("CommandPhase")) { commandPhase = currentPhase; } else { commandPhase = globalScene.getStandbyPhase() as CommandPhase; diff --git a/src/ui/egg-hatch-scene-handler.ts b/src/ui/egg-hatch-scene-handler.ts index 76e2c54f4b6..b2863a213f8 100644 --- a/src/ui/egg-hatch-scene-handler.ts +++ b/src/ui/egg-hatch-scene-handler.ts @@ -1,7 +1,6 @@ import { UiMode } from "#enums/ui-mode"; import UiHandler from "./ui-handler"; import { Button } from "#enums/buttons"; -import { EggHatchPhase } from "#app/phases/egg-hatch-phase"; import { globalScene } from "#app/global-scene"; export default class EggHatchSceneHandler extends UiHandler { @@ -46,7 +45,7 @@ export default class EggHatchSceneHandler extends UiHandler { processInput(button: Button): boolean { if (button === Button.ACTION || button === Button.CANCEL) { const phase = globalScene.getCurrentPhase(); - if (phase instanceof EggHatchPhase && phase.trySkip()) { + if (phase?.is("EggHatchPhase") && phase.trySkip()) { return true; } } diff --git a/src/ui/egg-summary-ui-handler.ts b/src/ui/egg-summary-ui-handler.ts index ddc536fe1ad..ed2506ba0c1 100644 --- a/src/ui/egg-summary-ui-handler.ts +++ b/src/ui/egg-summary-ui-handler.ts @@ -4,7 +4,6 @@ import MessageUiHandler from "./message-ui-handler"; import { getEggTierForSpecies } from "../data/egg"; import { Button } from "#enums/buttons"; import PokemonHatchInfoContainer from "./pokemon-hatch-info-container"; -import { EggSummaryPhase } from "#app/phases/egg-summary-phase"; import type { EggHatchData } from "#app/data/egg-hatch-data"; import ScrollableGridUiHandler from "./scrollable-grid-handler"; import { HatchedPokemonContainer } from "./hatched-pokemon-container"; @@ -223,7 +222,7 @@ export default class EggSummaryUiHandler extends MessageUiHandler { if (button === Button.CANCEL) { if (!this.blockExit) { const phase = globalScene.getCurrentPhase(); - if (phase instanceof EggSummaryPhase) { + if (phase?.is("EggSummaryPhase")) { phase.end(); } success = true; diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index cc684111617..b6c3a9d7b8e 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -15,7 +15,6 @@ import { Button } from "#enums/buttons"; import { GameDataType } from "#enums/game-data-type"; import BgmBar from "#app/ui/bgm-bar"; import type AwaitableUiHandler from "./awaitable-ui-handler"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { AdminMode, getAdminModeName } from "./admin-ui-handler"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; @@ -126,7 +125,7 @@ export default class MenuUiHandler extends MessageUiHandler { const ui = this.getUi(); this.excludedMenus = () => [ { - condition: globalScene.getCurrentPhase() instanceof SelectModifierPhase, + condition: !!globalScene.getCurrentPhase()?.is("SelectModifierPhase"), options: [MenuOptions.EGG_GACHA], }, { condition: bypassLogin, options: [MenuOptions.LOG_OUT] }, diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 452ffcf5192..e26ecb531e9 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -29,7 +29,6 @@ import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { getPokemonNameWithAffix } from "#app/messages"; import type { CommandPhase } from "#app/phases/command-phase"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { globalScene } from "#app/global-scene"; const defaultMessage = i18next.t("partyUiHandler:choosePokemon"); @@ -751,7 +750,7 @@ export default class PartyUiHandler extends MessageUiHandler { // TODO: This risks hitting the other options (.MOVE_i and ALL) so does it? Do we need an extra check? if ( option >= PartyOption.FORM_CHANGE_ITEM && - globalScene.getCurrentPhase() instanceof SelectModifierPhase && + globalScene.getCurrentPhase()?.is("SelectModifierPhase") && this.partyUiMode === PartyUiMode.CHECK ) { const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); @@ -1338,7 +1337,7 @@ export default class PartyUiHandler extends MessageUiHandler { this.addCommonOptions(pokemon); break; case PartyUiMode.CHECK: - if (globalScene.getCurrentPhase() instanceof SelectModifierPhase) { + if (globalScene.getCurrentPhase()?.is("SelectModifierPhase")) { const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); for (let i = 0; i < formChangeItemModifiers.length; i++) { this.options.push(PartyOption.FORM_CHANGE_ITEM + i); diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts index 263842bd4f9..00e166f075d 100644 --- a/src/ui/pokedex-page-ui-handler.ts +++ b/src/ui/pokedex-page-ui-handler.ts @@ -665,7 +665,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler { show(args: any[]): boolean { // Allow the use of candies if we are in one of the whitelisted phases this.canUseCandies = ["TitlePhase", "SelectStarterPhase", "CommandPhase"].includes( - globalScene.getCurrentPhase()?.constructor.name ?? "", + globalScene.getCurrentPhase()?.phaseName ?? "", ); if (args.length >= 1 && args[0] === "refresh") { diff --git a/test/testUtils/phaseInterceptor.ts b/test/testUtils/phaseInterceptor.ts index b1d76ecd4a6..b7577550568 100644 --- a/test/testUtils/phaseInterceptor.ts +++ b/test/testUtils/phaseInterceptor.ts @@ -63,6 +63,8 @@ import { UnlockPhase } from "#app/phases/unlock-phase"; import { PostGameOverPhase } from "#app/phases/post-game-over-phase"; import { RevivalBlessingPhase } from "#app/phases/revival-blessing-phase"; +import type { PhaseClass, PhaseString } from "#app/@types/phase-types"; + export interface PromptHandler { phaseTarget?: string; mode?: UiMode; @@ -71,126 +73,6 @@ export interface PromptHandler { awaitingActionInput?: boolean; } -type PhaseClass = - | typeof LoginPhase - | typeof TitlePhase - | typeof SelectGenderPhase - | typeof NewBiomeEncounterPhase - | typeof SelectStarterPhase - | typeof PostSummonPhase - | typeof SummonPhase - | typeof ToggleDoublePositionPhase - | typeof CheckSwitchPhase - | typeof ShowAbilityPhase - | typeof MessagePhase - | typeof TurnInitPhase - | typeof CommandPhase - | typeof EnemyCommandPhase - | typeof TurnStartPhase - | typeof MovePhase - | typeof MoveEffectPhase - | typeof DamageAnimPhase - | typeof FaintPhase - | typeof BerryPhase - | typeof TurnEndPhase - | typeof BattleEndPhase - | typeof EggLapsePhase - | typeof SelectModifierPhase - | typeof NextEncounterPhase - | typeof NewBattlePhase - | typeof VictoryPhase - | typeof LearnMovePhase - | typeof MoveEndPhase - | typeof StatStageChangePhase - | typeof ShinySparklePhase - | typeof SelectTargetPhase - | typeof UnavailablePhase - | typeof QuietFormChangePhase - | typeof SwitchPhase - | typeof SwitchSummonPhase - | typeof PartyHealPhase - | typeof FormChangePhase - | typeof EvolutionPhase - | typeof EndEvolutionPhase - | typeof LevelCapPhase - | typeof AttemptRunPhase - | typeof SelectBiomePhase - | typeof MysteryEncounterPhase - | typeof MysteryEncounterOptionSelectedPhase - | typeof MysteryEncounterBattlePhase - | typeof MysteryEncounterRewardsPhase - | typeof PostMysteryEncounterPhase - | typeof RibbonModifierRewardPhase - | typeof GameOverModifierRewardPhase - | typeof ModifierRewardPhase - | typeof PartyExpPhase - | typeof ExpPhase - | typeof EncounterPhase - | typeof GameOverPhase - | typeof UnlockPhase - | typeof PostGameOverPhase - | typeof RevivalBlessingPhase; - -type PhaseString = - | "LoginPhase" - | "TitlePhase" - | "SelectGenderPhase" - | "NewBiomeEncounterPhase" - | "SelectStarterPhase" - | "PostSummonPhase" - | "SummonPhase" - | "ToggleDoublePositionPhase" - | "CheckSwitchPhase" - | "ShowAbilityPhase" - | "MessagePhase" - | "TurnInitPhase" - | "CommandPhase" - | "EnemyCommandPhase" - | "TurnStartPhase" - | "MovePhase" - | "MoveEffectPhase" - | "DamageAnimPhase" - | "FaintPhase" - | "BerryPhase" - | "TurnEndPhase" - | "BattleEndPhase" - | "EggLapsePhase" - | "SelectModifierPhase" - | "NextEncounterPhase" - | "NewBattlePhase" - | "VictoryPhase" - | "LearnMovePhase" - | "MoveEndPhase" - | "StatStageChangePhase" - | "ShinySparklePhase" - | "SelectTargetPhase" - | "UnavailablePhase" - | "QuietFormChangePhase" - | "SwitchPhase" - | "SwitchSummonPhase" - | "PartyHealPhase" - | "FormChangePhase" - | "EvolutionPhase" - | "EndEvolutionPhase" - | "LevelCapPhase" - | "AttemptRunPhase" - | "SelectBiomePhase" - | "MysteryEncounterPhase" - | "MysteryEncounterOptionSelectedPhase" - | "MysteryEncounterBattlePhase" - | "MysteryEncounterRewardsPhase" - | "PostMysteryEncounterPhase" - | "RibbonModifierRewardPhase" - | "GameOverModifierRewardPhase" - | "ModifierRewardPhase" - | "PartyExpPhase" - | "ExpPhase" - | "EncounterPhase" - | "GameOverPhase" - | "UnlockPhase" - | "PostGameOverPhase" - | "RevivalBlessingPhase"; - type PhaseInterceptorPhase = PhaseClass | PhaseString; export default class PhaseInterceptor { From 855868bfea4206bda9a027d6945fb3bd0f9c78dd Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Fri, 6 Jun 2025 16:15:13 -0500 Subject: [PATCH 08/51] [Refactor] Cleanup achvs ui handler (#5919) --- src/system/game-data.ts | 4 +- src/ui/achvs-ui-handler.ts | 464 ++++++++++++++++++++----------------- 2 files changed, 254 insertions(+), 214 deletions(-) diff --git a/src/system/game-data.ts b/src/system/game-data.ts index bc74ab15930..ab907d2f768 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -179,11 +179,11 @@ interface Unlocks { [key: number]: boolean; } -interface AchvUnlocks { +export interface AchvUnlocks { [key: string]: number; } -interface VoucherUnlocks { +export interface VoucherUnlocks { [key: string]: number; } diff --git a/src/ui/achvs-ui-handler.ts b/src/ui/achvs-ui-handler.ts index d0c8b716c7a..8588530d370 100644 --- a/src/ui/achvs-ui-handler.ts +++ b/src/ui/achvs-ui-handler.ts @@ -11,20 +11,20 @@ import { addWindow } from "#app/ui/ui-theme"; import { ScrollBar } from "#app/ui/scroll-bar"; import { PlayerGender } from "#enums/player-gender"; import { globalScene } from "#app/global-scene"; +import type { AchvUnlocks, VoucherUnlocks } from "#app/system/game-data"; -enum Page { - ACHIEVEMENTS, - VOUCHERS, -} +const Page = { + ACHIEVEMENTS: 0, + VOUCHERS: 1, +} as const; +type Page = (typeof Page)[keyof typeof Page]; interface LanguageSetting { TextSize: string; } const languageSettings: { [key: string]: LanguageSetting } = { - de: { - TextSize: "80px", - }, + de: { TextSize: "80px" }, }; export default class AchvsUiHandler extends MessageUiHandler { @@ -70,44 +70,35 @@ export default class AchvsUiHandler extends MessageUiHandler { setup() { const ui = this.getUi(); - this.mainContainer = globalScene.add.container(1, -(globalScene.game.canvas.height / 6) + 1); + /** Width of the global canvas / 6 */ + const WIDTH = globalScene.game.canvas.width / 6; + /** Height of the global canvas / 6 */ + const HEIGHT = globalScene.game.canvas.height / 6; - this.mainContainer.setInteractive( - new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), - Phaser.Geom.Rectangle.Contains, - ); + this.mainContainer = globalScene.add.container(1, -HEIGHT + 1); - this.headerBg = addWindow(0, 0, globalScene.game.canvas.width / 6 - 2, 24); - this.headerBg.setOrigin(0, 0); + this.mainContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, WIDTH, HEIGHT), Phaser.Geom.Rectangle.Contains); - this.headerText = addTextObject(0, 0, "", TextStyle.SETTINGS_LABEL); - this.headerText.setOrigin(0, 0); - this.headerText.setPositionRelative(this.headerBg, 8, 4); - this.headerActionButton = new Phaser.GameObjects.Sprite(globalScene, 0, 0, "keyboard", "ACTION.png"); - this.headerActionButton.setOrigin(0, 0); - this.headerActionButton.setPositionRelative(this.headerBg, 236, 6); - this.headerActionText = addTextObject(0, 0, "", TextStyle.WINDOW, { - fontSize: "60px", - }); - this.headerActionText.setOrigin(0, 0); - this.headerActionText.setPositionRelative(this.headerBg, 264, 8); + this.headerBg = addWindow(0, 0, WIDTH - 2, 24); + + this.headerText = addTextObject(0, 0, "", TextStyle.SETTINGS_LABEL) + .setOrigin(0) + .setPositionRelative(this.headerBg, 8, 4); + this.headerActionButton = new Phaser.GameObjects.Sprite(globalScene, 0, 0, "keyboard", "ACTION.png") + .setOrigin(0) + .setPositionRelative(this.headerBg, 236, 6); + this.headerActionText = addTextObject(0, 0, "", TextStyle.WINDOW, { fontSize: "60px" }) + .setOrigin(0) + .setPositionRelative(this.headerBg, 264, 8); // We need to get the player gender from the game data to add the correct prefix to the achievement name const genderIndex = globalScene.gameData.gender ?? PlayerGender.MALE; const genderStr = PlayerGender[genderIndex].toLowerCase(); - this.achvsName = i18next.t("achv:Achievements.name", { - context: genderStr, - }); + this.achvsName = i18next.t("achv:Achievements.name", { context: genderStr }); this.vouchersName = i18next.t("voucher:vouchers"); - this.iconsBg = addWindow( - 0, - this.headerBg.height, - globalScene.game.canvas.width / 6 - 2, - globalScene.game.canvas.height / 6 - this.headerBg.height - 68, - ); - this.iconsBg.setOrigin(0, 0); + this.iconsBg = addWindow(0, this.headerBg.height, WIDTH - 2, HEIGHT - this.headerBg.height - 68).setOrigin(0); const yOffset = 6; this.scrollBar = new ScrollBar( @@ -126,68 +117,59 @@ export default class AchvsUiHandler extends MessageUiHandler { const x = (a % this.COLS) * 18; const y = Math.floor(a / this.COLS) * 18; - const icon = globalScene.add.sprite(x, y, "items", "unknown"); - icon.setOrigin(0, 0); - icon.setScale(0.5); + const icon = globalScene.add.sprite(x, y, "items", "unknown").setOrigin(0).setScale(0.5); this.icons.push(icon); this.iconsContainer.add(icon); } const titleBg = addWindow(0, this.headerBg.height + this.iconsBg.height, 174, 24); - titleBg.setOrigin(0, 0); this.titleBg = titleBg; - this.titleText = addTextObject(0, 0, "", TextStyle.WINDOW); + this.titleText = addTextObject(0, 0, "", TextStyle.WINDOW).setOrigin(); const textSize = languageSettings[i18next.language]?.TextSize ?? this.titleText.style.fontSize; this.titleText.setFontSize(textSize); const titleBgCenterX = titleBg.x + titleBg.width / 2; const titleBgCenterY = titleBg.y + titleBg.height / 2; - this.titleText.setOrigin(0.5, 0.5); this.titleText.setPosition(titleBgCenterX, titleBgCenterY); this.scoreContainer = globalScene.add.container(titleBg.x + titleBg.width, titleBg.y); const scoreBg = addWindow(0, 0, 46, 24); - scoreBg.setOrigin(0, 0); - this.scoreContainer.add(scoreBg); - this.scoreText = addTextObject(scoreBg.width / 2, scoreBg.height / 2, "", TextStyle.WINDOW); - this.scoreText.setOrigin(0.5, 0.5); - this.scoreContainer.add(this.scoreText); + this.scoreText = addTextObject(scoreBg.width / 2, scoreBg.height / 2, "", TextStyle.WINDOW).setOrigin(); + this.scoreContainer.add([scoreBg, this.scoreText]); const unlockBg = addWindow(this.scoreContainer.x + scoreBg.width, titleBg.y, 98, 24); - unlockBg.setOrigin(0, 0); - this.unlockText = addTextObject(0, 0, "", TextStyle.WINDOW); - this.unlockText.setOrigin(0.5, 0.5); - this.unlockText.setPositionRelative(unlockBg, unlockBg.width / 2, unlockBg.height / 2); + this.unlockText = addTextObject(0, 0, "", TextStyle.WINDOW) + .setPositionRelative(unlockBg, unlockBg.width / 2, unlockBg.height / 2) + .setOrigin(); - const descriptionBg = addWindow(0, titleBg.y + titleBg.height, globalScene.game.canvas.width / 6 - 2, 42); - descriptionBg.setOrigin(0, 0); + const descriptionBg = addWindow(0, titleBg.y + titleBg.height, WIDTH - 2, 42); - const descriptionText = addTextObject(0, 0, "", TextStyle.WINDOW, { - maxLines: 2, - }); - descriptionText.setWordWrapWidth(1870); - descriptionText.setOrigin(0, 0); - descriptionText.setPositionRelative(descriptionBg, 8, 4); + const descriptionText = addTextObject(0, 0, "", TextStyle.WINDOW, { maxLines: 2 }) + .setWordWrapWidth(1870) + .setOrigin(0) + .setPositionRelative(descriptionBg, 8, 4); this.message = descriptionText; - this.mainContainer.add(this.headerBg); - this.mainContainer.add(this.headerActionButton); - this.mainContainer.add(this.headerText); - this.mainContainer.add(this.headerActionText); - this.mainContainer.add(this.iconsBg); - this.mainContainer.add(this.scrollBar); - this.mainContainer.add(this.iconsContainer); - this.mainContainer.add(titleBg); - this.mainContainer.add(this.titleText); - this.mainContainer.add(this.scoreContainer); - this.mainContainer.add(unlockBg); - this.mainContainer.add(this.unlockText); - this.mainContainer.add(descriptionBg); - this.mainContainer.add(descriptionText); + this.mainContainer.add([ + this.headerBg, + this.headerActionButton, + this.headerText, + this.headerActionText, + this.iconsBg, + this.scrollBar, + this.iconsContainer, + titleBg, + this.titleText, + this.scoreContainer, + unlockBg, + this.unlockText, + descriptionBg, + descriptionText, + ]); ui.add(this.mainContainer); @@ -246,87 +228,132 @@ export default class AchvsUiHandler extends MessageUiHandler { ); } - processInput(button: Button): boolean { - const ui = this.getUi(); + // #region Input Processing + /** + * Submethod of {@linkcode processInput} that handles the action button input + * @returns Whether the success sound should be played + */ + private processActionInput(): true { + this.setScrollCursor(0); + if (this.currentPage === Page.ACHIEVEMENTS) { + this.currentPage = Page.VOUCHERS; + this.updateVoucherIcons(); + } else if (this.currentPage === Page.VOUCHERS) { + this.currentPage = Page.ACHIEVEMENTS; + this.updateAchvIcons(); + } + this.setCursor(0, true); + this.scrollBar.setTotalRows(Math.ceil(this.currentTotal / this.COLS)); + this.scrollBar.setScrollCursor(0); + this.mainContainer.update(); + return true; + } + /** + * Submethod of {@linkcode processInput} that handles the up button input + * @returns Whether the success sound should be played + */ + private processUpInput(): boolean { + if (this.cursor >= this.COLS) { + return this.setCursor(this.cursor - this.COLS); + } + if (this.scrollCursor) { + return this.setScrollCursor(this.scrollCursor - 1); + } + + // Wrap around to the last row + const success = this.setScrollCursor(Math.ceil(this.currentTotal / this.COLS) - this.ROWS); + let newCursorIndex = this.cursor + (this.ROWS - 1) * this.COLS; + if (newCursorIndex > this.currentTotal - this.scrollCursor * this.COLS - 1) { + newCursorIndex -= this.COLS; + } + return success && this.setCursor(newCursorIndex); + } + + /** + * Submethod of {@linkcode processInput} that handles the down button input + * @returns Whether the success sound should be played + */ + private processDownInput(): boolean { + const rowIndex = Math.floor(this.cursor / this.COLS); + const itemOffset = this.scrollCursor * this.COLS; + const canMoveDown = itemOffset + 1 < this.currentTotal; + + if (rowIndex >= this.ROWS - 1) { + if (this.scrollCursor < Math.ceil(this.currentTotal / this.COLS) - this.ROWS && canMoveDown) { + // scroll down one row + return this.setScrollCursor(this.scrollCursor + 1); + } + // wrap back to the first row + return this.setScrollCursor(0) && this.setCursor(this.cursor % this.COLS); + } + if (canMoveDown) { + return this.setCursor(Math.min(this.cursor + this.COLS, this.currentTotal - itemOffset - 1)); + } + return false; + } + + /** + * Submethod of {@linkcode processInput} that handles the left button input + * @returns Whether the success sound should be played + */ + private processLeftInput(): boolean { + const itemOffset = this.scrollCursor * this.COLS; + if (this.cursor % this.COLS === 0) { + return this.setCursor(Math.min(this.cursor + this.COLS - 1, this.currentTotal - itemOffset - 1)); + } + return this.setCursor(this.cursor - 1); + } + + /** + * Submethod of {@linkcode processInput} that handles the right button input + * @returns Whether the success sound should be played + */ + private processRightInput(): boolean { + const itemOffset = this.scrollCursor * this.COLS; + if ((this.cursor + 1) % this.COLS === 0 || this.cursor + itemOffset === this.currentTotal - 1) { + return this.setCursor(this.cursor - (this.cursor % this.COLS)); + } + return this.setCursor(this.cursor + 1); + } + + /** + * Process user input to navigate through the achievements and vouchers UI. + * @param button - The button that was pressed + * @returns Whether an action was successfully processed + */ + processInput(button: Button): boolean { let success = false; - if (button === Button.ACTION) { - success = true; - this.setScrollCursor(0); - if (this.currentPage === Page.ACHIEVEMENTS) { - this.currentPage = Page.VOUCHERS; - this.updateVoucherIcons(); - } else if (this.currentPage === Page.VOUCHERS) { - this.currentPage = Page.ACHIEVEMENTS; - this.updateAchvIcons(); - } - this.setCursor(0, true); - this.scrollBar.setTotalRows(Math.ceil(this.currentTotal / this.COLS)); - this.scrollBar.setScrollCursor(0); - this.mainContainer.update(); - } - if (button === Button.CANCEL) { - success = true; - globalScene.ui.revertMode(); - } else { - const rowIndex = Math.floor(this.cursor / this.COLS); - const itemOffset = this.scrollCursor * this.COLS; - switch (button) { - case Button.UP: - if (this.cursor < this.COLS) { - if (this.scrollCursor) { - success = this.setScrollCursor(this.scrollCursor - 1); - } else { - // Wrap around to the last row - success = this.setScrollCursor(Math.ceil(this.currentTotal / this.COLS) - this.ROWS); - let newCursorIndex = this.cursor + (this.ROWS - 1) * this.COLS; - if (newCursorIndex > this.currentTotal - this.scrollCursor * this.COLS - 1) { - newCursorIndex -= this.COLS; - } - success = success && this.setCursor(newCursorIndex); - } - } else { - success = this.setCursor(this.cursor - this.COLS); - } - break; - case Button.DOWN: - const canMoveDown = itemOffset + 1 < this.currentTotal; - if (rowIndex >= this.ROWS - 1) { - if (this.scrollCursor < Math.ceil(this.currentTotal / this.COLS) - this.ROWS && canMoveDown) { - // scroll down one row - success = this.setScrollCursor(this.scrollCursor + 1); - } else { - // wrap back to the first row - success = this.setScrollCursor(0) && this.setCursor(this.cursor % this.COLS); - } - } else if (canMoveDown) { - success = this.setCursor(Math.min(this.cursor + this.COLS, this.currentTotal - itemOffset - 1)); - } - break; - case Button.LEFT: - if (this.cursor % this.COLS === 0) { - success = this.setCursor(Math.min(this.cursor + this.COLS - 1, this.currentTotal - itemOffset - 1)); - } else { - success = this.setCursor(this.cursor - 1); - } - break; - case Button.RIGHT: - if ((this.cursor + 1) % this.COLS === 0 || this.cursor + itemOffset === this.currentTotal - 1) { - success = this.setCursor(this.cursor - (this.cursor % this.COLS)); - } else { - success = this.setCursor(this.cursor + 1); - } - break; - } + switch (button) { + case Button.ACTION: + success = this.processActionInput(); + break; + case Button.CANCEL: + success = true; + globalScene.ui.revertMode(); + break; + case Button.UP: + success = this.processUpInput(); + break; + case Button.DOWN: + success = this.processDownInput(); + break; + case Button.LEFT: + success = this.processLeftInput(); + break; + case Button.RIGHT: + success = this.processRightInput(); + break; } if (success) { - ui.playSelect(); + this.getUi().playSelect(); } return success; } + // #endregion Input Processing setCursor(cursor: number, pageChange?: boolean): boolean { const ret = super.setCursor(cursor); @@ -334,33 +361,35 @@ export default class AchvsUiHandler extends MessageUiHandler { let update = ret; if (!this.cursorObj) { - this.cursorObj = globalScene.add.nineslice(0, 0, "select_cursor_highlight", undefined, 16, 16, 1, 1, 1, 1); - this.cursorObj.setOrigin(0, 0); + this.cursorObj = globalScene.add + .nineslice(0, 0, "select_cursor_highlight", undefined, 16, 16, 1, 1, 1, 1) + .setOrigin(0); this.iconsContainer.add(this.cursorObj); update = true; } this.cursorObj.setPositionRelative(this.icons[this.cursor], 0, 0); + if (!update && !pageChange) { + return ret; + } - if (update || pageChange) { - switch (this.currentPage) { - case Page.ACHIEVEMENTS: - if (pageChange) { - this.titleBg.width = 174; - this.titleText.x = this.titleBg.width / 2; - this.scoreContainer.setVisible(true); - } - this.showAchv(achvs[Object.keys(achvs)[cursor + this.scrollCursor * this.COLS]]); - break; - case Page.VOUCHERS: - if (pageChange) { - this.titleBg.width = 220; - this.titleText.x = this.titleBg.width / 2; - this.scoreContainer.setVisible(false); - } - this.showVoucher(vouchers[Object.keys(vouchers)[cursor + this.scrollCursor * this.COLS]]); - break; - } + switch (this.currentPage) { + case Page.ACHIEVEMENTS: + if (pageChange) { + this.titleBg.width = 174; + this.titleText.x = this.titleBg.width / 2; + this.scoreContainer.setVisible(true); + } + this.showAchv(achvs[Object.keys(achvs)[cursor + this.scrollCursor * this.COLS]]); + break; + case Page.VOUCHERS: + if (pageChange) { + this.titleBg.width = 220; + this.titleText.x = this.titleBg.width / 2; + this.scoreContainer.setVisible(false); + } + this.showVoucher(vouchers[Object.keys(vouchers)[cursor + this.scrollCursor * this.COLS]]); + break; } return ret; } @@ -399,30 +428,50 @@ export default class AchvsUiHandler extends MessageUiHandler { } /** - * updateAchvIcons(): void - * Determines what data is to be displayed on the UI and updates it accordingly based on the current value of this.scrollCursor + * Updates the icons displayed on the UI based on the current page and scroll cursor. + * @param items - The items to display (achievements or vouchers). + * @param unlocks - The unlocks data for the items. + * @param getIconFrame - A function to determine the frame for each item. + * @param headerText - The text for the header. + * @param actionText - The text for the action button. + * @param totalItems - The total number of items. + * @param forAchievements - `True` when updating icons for the achievements page, `false` for the vouchers page. */ - updateAchvIcons(): void { - this.headerText.text = this.achvsName; - this.headerActionText.text = this.vouchersName; + private updateIcons( + items: T extends true ? Achv[] : Voucher[], + unlocks: T extends true ? AchvUnlocks : VoucherUnlocks, + headerText: string, + actionText: string, + totalItems: number, + forAchievements: T, + ): void { + // type ItemType = T extends true ? Achv : Voucher; + // type RangeType = ItemType[]; + this.headerText.text = headerText; + this.headerActionText.text = actionText; const textPosition = this.headerBgX - this.headerActionText.displayWidth - 8; this.headerActionText.setX(textPosition); this.headerActionButton.setX(textPosition - this.headerActionButton.displayWidth - 4); - const achvUnlocks = globalScene.gameData.achvUnlocks; - const itemOffset = this.scrollCursor * this.COLS; const itemLimit = this.ROWS * this.COLS; - const achvRange = Object.values(achvs).slice(itemOffset, itemLimit + itemOffset); + const itemRange = items.slice(itemOffset, itemLimit + itemOffset); - achvRange.forEach((achv: Achv, i: number) => { + itemRange.forEach((item: (typeof itemRange)[0], i: number) => { const icon = this.icons[i]; - const unlocked = achvUnlocks.hasOwnProperty(achv.id); - const hidden = !unlocked && achv.secret && (!achv.parentId || !achvUnlocks.hasOwnProperty(achv.parentId)); - const tinted = !hidden && !unlocked; + const unlocked = unlocks.hasOwnProperty(item.id); + let tinted = !unlocked; + if (forAchievements) { + // Typescript cannot properly infer the type of `item` here, so we need to cast it + const achv = item as Achv; + const hidden = !unlocked && achv.secret && (!achv.parentId || !unlocks.hasOwnProperty(achv.parentId)); + tinted &&= !hidden; + icon.setFrame(!hidden ? achv.iconImage : "unknown"); + } else { + icon.setFrame(getVoucherTypeIcon((item as Voucher).voucherType)); + } - icon.setFrame(!hidden ? achv.iconImage : "unknown"); icon.setVisible(true); if (tinted) { icon.setTintFill(0); @@ -431,48 +480,39 @@ export default class AchvsUiHandler extends MessageUiHandler { } }); - if (achvRange.length < this.icons.length) { - this.icons.slice(achvRange.length).map(i => i.setVisible(false)); + if (itemRange.length < this.icons.length) { + this.icons.slice(itemRange.length).forEach(i => i.setVisible(false)); } - this.currentTotal = this.achvsTotal; + this.currentTotal = totalItems; } /** - * updateVoucherIcons(): void - * Determines what data is to be displayed on the UI and updates it accordingly based on the current value of this.scrollCursor + * Update the achievement icons displayed on the UI based on the current scroll cursor. + */ + updateAchvIcons(): void { + this.updateIcons( + Object.values(achvs), + globalScene.gameData.achvUnlocks, + this.achvsName, + this.vouchersName, + this.achvsTotal, + true, + ); + } + + /** + * Update the voucher icons displayed on the UI based on the current scroll cursor. */ updateVoucherIcons(): void { - this.headerText.text = this.vouchersName; - this.headerActionText.text = this.achvsName; - const textPosition = this.headerBgX - this.headerActionText.displayWidth - 8; - this.headerActionText.setX(textPosition); - this.headerActionButton.setX(textPosition - this.headerActionButton.displayWidth - 4); - - const voucherUnlocks = globalScene.gameData.voucherUnlocks; - - const itemOffset = this.scrollCursor * this.COLS; - const itemLimit = this.ROWS * this.COLS; - - const voucherRange = Object.values(vouchers).slice(itemOffset, itemLimit + itemOffset); - - voucherRange.forEach((voucher: Voucher, i: number) => { - const icon = this.icons[i]; - const unlocked = voucherUnlocks.hasOwnProperty(voucher.id); - - icon.setFrame(getVoucherTypeIcon(voucher.voucherType)); - icon.setVisible(true); - if (!unlocked) { - icon.setTintFill(0); - } else { - icon.clearTint(); - } - }); - - if (voucherRange.length < this.icons.length) { - this.icons.slice(voucherRange.length).map(i => i.setVisible(false)); - } - this.currentTotal = this.vouchersTotal; + this.updateIcons( + Object.values(vouchers), + globalScene.gameData.voucherUnlocks, + this.vouchersName, + this.achvsName, + this.vouchersTotal, + false, + ); } clear() { From 03368587083ccf7583923d1d35f07a5cd347f535 Mon Sep 17 00:00:00 2001 From: SmhMyHead <191356399+SmhMyHead@users.noreply.github.com> Date: Fri, 6 Jun 2025 23:20:02 +0200 Subject: [PATCH 09/51] [UI/UX] Legendary UP Gacha timer (#5921) * [UI/UIX] Legendary UP Gacha timer * Update egg-gacha-ui-handler.ts Seems "fixedInt" was needed on the delay of the playTimeTimer so the game speed doesn't affect it. * New timer container by damocleas. * gacha_legendary.png second version from @damocleas * Use phaser object chaining methods --------- Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> --- public/images/egg/gacha_legendary.png | Bin 2015 -> 3810 bytes src/ui/egg-gacha-ui-handler.ts | 46 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/public/images/egg/gacha_legendary.png b/public/images/egg/gacha_legendary.png index 8cd6fa38e295fcfb306a6ffbdb017bb1f101eeba..6eb41e55099a0ef71e946b7f89f0213b6e0996d7 100644 GIT binary patch literal 3810 zcmX|Ec{o+u`+xTt9COAT(?x?2AyY{lWV(jT66HvyG9)q&+tA>Wp^!+9i*S^VlFS`M zD$1=$TtjsoB10%Mzuotb-+rFG*1Mnmyu(`W`}wSQC0kpW@NkN80sxPh=`mXXgo4RQ z>}=Th`R+m!HX*^bCI(RTM&cU)Z-Ut|ef#j!GXF!}4ofUJ^HZOIjK^Yf1K z4te>#dxRD;e$~e;nX6v0?Xok>luT63N-(wYvk@?5&r0CvNt9Y2VH0ELDE(mleyWxC zfv};%{+%*BH$23LFV6P3vZ{W0crACoxV<@Nu3Hnu>fY8{{M!9yc0~R%o|A&e_1Y2d zMGnsSHN?1w1~G?k7jrG%mkIPA2}u3;;htq`r?Ji*#-rx=!~f}jw3cx{EmwN9#&Euo z5fUyDG520_#{=oJHu2wfU+vsJ@41;JPj}%GZ zxvEixyIeY)5`ySOXyVZY9dQF|>fooc)Tp=xMK&0ZxRP1~LR73E&I$;)(?BSr3_$L! z+F5QQ4Q}E<`ep_@o=8J=h(L+BE8`&t#9SeO+eydBnE0POz^g@WQA0$ISpv|tXHOfJ zI_cvGSl{*TB4c@!#q?xl_}MTqf)g7$gE?}<5BXUAITjCg!&zkdVHox4!Lx*#YW1G=f zb8Rh4qxDwKt$knc*G`YW(LDsY%^k7YrelqdpOkLR{~C%FW2DmK+4)X#UU@HznNpR$ zBSahBeOO1UEZQm3%1pjAHgYQK?2>t|eD~Xczypg}~_m7u=`fMYh&m}=hYlS_$BN6Q)8qnF1R>#ogsa?Qu3T+I_ z5sdw?R;ag8wzmF6JUcmh?z!sEcJF6j@A|wHMx|3yok<8hu6^noQHkY*MtbhtPoTDpgieXJ&W%6z?z7ux74lbcQCSho7P+W+bvtM#FY@OX zBMRejN(6@L#ib{~4-X;bMNUc54l!%AbG@!x0vm^N+JQPzlyM0_<$TmoSehWZHd5H~ z)TtKvTTMgeWsou-`025&UDyi)q+2danc}FDEw!EiXERlYq=8!7A39Bj0ZTnsB||L9 zSB|$nCP1Qj8ryC;0As#W1~-95xRv4IL`%f9iS1a$@6lKa+xYdd ziz&J|ROwjz-Y?i^hx1NZcTh=vd0f z&NLYu397v=n1hj%Zlgi=D485%4e&kjwbB|p)bjD%x`vgMkoPA!_caI2LOmeB;12H5 z(*xcKYNvz&P6?ZjMdw|Zcw1oi@P1X~y0AJAW$M5S6B~~%W4DB&_6d@WVK~2GV@nMO zaE6>buKnk*2mxWT;`!UIvJ@{Ei(sA)Oc~YX$PT}?Ds<%H;%e_lnCAqeolsfO($eai zQ+E61m}x>j*X$zzLSDh#@TV)=`IS{y93E|xJ#N+jG?g7Gu;f6@Gp8zLW<|xzZbT!! zNm5VZIjskdttp%W>)b#wG^_SB+d4`Wg}wF(Mk}j872PP8RE)oDDO)P^8={_V73})M z1y$)?_J~T~IRLy1{{Mk(r5)iar^jlm=`UJyI+F?n?bWq=*&#P10^H9)VuBa!egK74^LF(X5Wpd zu3Brr^&ggTWmbU#CDg2);79MJQ+@Iaob?A?4bxdkI+L;auWg8GLu8}L+aH4ztNj<5 z5lt9+n(P6r>hiuU*K(e7r=4E0a+J4AZw!!yTsvcde2 zn<_k;d?(f~;KKB6!FI0tTa{J4A@QYA$Cuqcb3SR{tg_T4=xV2J{mg6F=-A1!5$6}I zW9!^YH5d|NN0np!y~0;7mzE~0{7dnB^$+2mGC8mJE89uCZT0!3h-aTpN^MPxZ^r^? zCf}MLMVLuFzJK-*s6SiHY3l ztzGl&VnDB=;{{DIp&}9tmdj2{fOU?W;HNmq$$oOb#SX*i z?S!^*<(#NGm%-#b^fDyh^4KeMBjq>oAa!W}`Tu=P*^vI`2MLe0_daGxr+FwW& zvX7A9+~n;uIMmN@|2b(;p>GU}DdVM$T;n2xR2EmuZHqi6Mw$b7%c*R&_8U=2mm)3MW+NDKrxYao=a9;V`C4#ml&XV5#!F|MPQSVa;igxas0_g%?DnYI)7!V2cXee{8Pr836f0H-3cuCbClJ2FR$)7ej^S zKE6FF;)yS*BW(4Nq%K@~tIZ2ON)GS#;ZU<7yFyn5C{c{j+p@Jch!fBFBRQFa85W@M z1nWg2_JsV=dZ?(=W>6L?b~!gp#qG)ymL8u^@p$oJu+-cJ>Y`bxk(-^@<#?w!vCZD4 z=TZ#P`B|Uwr3A`w)Mz$s)xif;HgaD-Fam2vu{>mG8ZMPF$_2QC_*N!q$Io0I+34Lg zB$C^FxBUU#()iVnL*{$-dG*DGhFuMmeSQLGH?ia*SJc8${)|ZGiv8dXy+aIMF*0x~ zXa#zRc!`iECX64p4eQj1^Pmk*tm*KkqXnru1*6L$Mq6B06Ue87Yj;|LmDJ%9-ln*| z#=;IuzS=kJTuGePx$eT>A`4X_y0W~Kff?Qc=#QESKT-J4UhTqtGW>fuHhRPOrFZAt z)o8Ei>#_`Pn>;)ecfA>rgRTRaDQugRtuE4 z!N_z)e)M5rV5N&&>^IB&;&felZ1ja1)&CX&V(8)ru;8rD|H?U|96uDo4t~XARX<7r zCe1%V5W?KtHROoeadCZ3A?4@42`Iva0-^78dsw^C#C_k>A3<9rGDJkMcI49`kK}ymmhswn>idPK-{wA$6ysx# zh|1~$+1Ok80if%iPY`N>Xl^dnno43Z`HzCB zqwhd=Yx>qK9`)pVE^uQ0xvDJ*!+Y+2cuhb0=w-B2-3gv^;xPz=U4KL2&R8-YSnj9O z;6D9(SM~NtV;Qf-@i`aH{D4}7I=t{P)&Ia{gsH>gQ=qi!vVbxa6{XqdHndj-V-0~2 z3rYAcN9}$cjL;$i-?~n{JNWbjzoSxmqhdV5%q>0?F!cLWKJw%)9yI1(g@(_~&dj(h zI4WgokgEx^>Lrn3svtM8;ED{?{Hq~zg0Ks-N-@Efk}9q3bh93M@8hP&?p;ztoLzPC zsB3YhR1jcsOc0oK$q~BcjYs3vbC!I6Jq%0uAwe5U_9)>6&R?n|=ywS2;K_W%bK^_1 zVWF@zX7!Q%|Nhx8EW_*B#d=-n`KfSc=D_b|JOXs}_C9{}lDLG!k)|4*dtdX@bsoKa zzDwQE39bAX);F$PYi-J2a9YKxJSeMH9W-ZV^YUdpUk`t|V)98q6ohnWyD{s=dMC|q2FiN>oAJjv)>RuuN*efmogpKt|OqGPQ7FORc?k1P3wte zq^QDKf=!RtOBV|QaG$Z%#P~hoLANA&*3vGUj7QrwrTloe`Z}>Vh{`mv_V=%hD1QFqs{De?RZ+ZvLLkjI54T8Bi1c54iYxQUCw| delta 2002 zcmV;@2QB#G9p4X-BYy{`Nkl#N^;PkIUgU`*94LA6~!_kVIb>+4*o&u4ml+l`%p ze`b>F7>0KqKXf0eut`f$?f6~4INSNW_4$)*uDJDOw!i4y^x!`;$#oQD;{54vKY0+^ zB3NA$mS8mE$B=G^i?g{*;RqmUhPJGxKdiM)U@G@W7T+ubOn?9;z!CJ(FMb|=V;Ra} zA%w8?&^ibpMSoaXS!ut0?V5!sm-g1ymZQNH;|8m6{Oa@b7Ni6kJI|k^`0kAxK?JMi zdzPSj@vG0zhch!X7NjK3%*=!{Gc#f7(xvdTM^r&*Td}GoDgCYn0&$)Ldb05+qiW%tpD~` zM}D4G?0@%a?>3v>pa>Hn*~KsG@NL|>d+2-7*9QqyN9Mk$eN?TdTU%ST z-Db0Wb$vZF8jVn|*ETP_<@W8wmoNIMIca{k*}1te=m87U-e~fuRbf|_mpfBnKk*o*QJ`MUqv zpP&Fzo)AXjLCktOJT91{vcD`Iz3%l5=l#tIAe9JW_jH&<5z!px1b31L6$FqGm=2Rj z8_lAFnKo!*h5(Ma>B-}7?xzO_Cw~gYNv6!AqYeTne2Fd_g;0BC%^di&iZ73U;I)`cL*RAc7O3--;Lnu zsj1Z05s&6iKmD}w@e?-$aLghY|NNs@UP*jL2~;`<;44FRN7`bABU7*`I0 zzyKDerY(X1GHkV3YXAZWAb6RV;8^?NX-7do009#6L}*fp zZ*K3!&JeR#@GC(;0LR=yeSbcL)|W&3`DbIN-8!4|iBrWE0i^Q&^!Rbu{_3mz9u!`D zG5pd&Ari6=u)?+*I~D;G1zqNY3^MMHvMoPbZet)880)(;DD}I6~ zfB*sr!?M@ojoJ4O4tjn)tLYB`gi=N@`gh(h!4pRTgrQG+QRt$n@9gAcA@Msdg4_F^ zdLw|4WBek|TCH5BJ`cfFz(eeJXT{hpUO z>WDB1AR{voo|>3A6o0?&P2~852m(kYr@(m7dWEiED8&x}q|%sn&p*{$CC5Qk*()jg@SFafE9UO$0rl!KFiHRepJ%3Xm0!Zma6JNi{?d;@a z_-uba@e$rZr@*u?gY&XE0fgMA#O_cY7fm<`e>s#ce9`Bp zN1Z4E1dz(VD_s0(EoD5yI}W#j079BSnDeJIyRIaWGX)}mjHR;1E`B7q%1=f|02$*5 zdP)ldNGh$*pG#N-1d!pg`So3&JT3lZX%2!2Ab { + this.legendaryExpiration.setText(this.getLegendaryGachaTimeLeft()); + }, + }); + } + + getLegendaryGachaTimeLeft(): string { + // 86400000 is the number of miliseconds in one day + const msUntilMidnight = 86400000 - (Date.now() % 86400000); + const hours = `${Math.floor(msUntilMidnight / 3600000)}`; + const minutes = `${Math.floor((msUntilMidnight % 3600000) / 60000)}`; + const seconds = `${Math.floor((msUntilMidnight % 60000) / 1000)}`; + + return `${hours.padStart(2, "0")}:${minutes.padStart(2, "0")}:${seconds.padStart(2, "0")}`; + } + clear(): void { super.clear(); this.setGachaCursor(-1); this.eggGachaContainer.setVisible(false); + if (this.playTimeTimer) { + this.playTimeTimer.destroy(); + this.playTimeTimer = null; + } } } From 3ca11e83a6591801b592587d75963fba6dc96e98 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Fri, 6 Jun 2025 19:27:58 -0400 Subject: [PATCH 10/51] [Dev] Add lefthook script to update submodules post-checkout (#5941) --- lefthook.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lefthook.yml b/lefthook.yml index ff0ac00f9e5..0f91f658171 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -9,6 +9,11 @@ pre-commit: - rebase post-merge: + commands: + update-submodules: + run: git submodule update --init --recursive + +post-checkout: commands: update-submodules: run: git submodule update --init --recursive \ No newline at end of file From 88e4ab978bc4ad98e6a90ec1419ef15d2984a2ac Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Fri, 6 Jun 2025 20:00:09 -0400 Subject: [PATCH 11/51] [Misc] Removed cases of `a ? true : false` and useless `super` calls from subclasses (#5943) * Removed cases of `if (a) {return true}' return false` * Removed useless `super.xyz` calls from functions * Fixde missing issur * Use early return in `Pokemon#isOffsetBySubstitute` --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/battle-scene.ts | 34 ++++++-------- src/configs/inputs/configHandler.ts | 5 +- src/data/abilities/ability.ts | 46 ++++--------------- src/data/battler-tags.ts | 25 ++-------- src/data/moves/move.ts | 15 +----- .../mystery-encounter-requirements.ts | 31 ++++--------- src/data/pokemon-forms.ts | 6 +-- src/data/pokemon-species.ts | 4 -- src/field/pokemon.ts | 32 ++++--------- src/modifier/modifier.ts | 8 ---- src/sprites/sprite-utils.ts | 5 +- 11 files changed, 50 insertions(+), 161 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index f2f952ea301..9a46baba899 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -3566,21 +3566,18 @@ export default class BattleScene extends SceneBase { gameMode: this.currentBattle ? this.gameMode.getName() : "Title", biome: this.currentBattle ? getBiomeName(this.arena.biomeType) : "", wave: this.currentBattle?.waveIndex ?? 0, - party: this.party - ? this.party.map(p => { - return { - name: p.name, - form: p.getFormKey(), - types: p.getTypes().map(type => PokemonType[type]), - teraType: PokemonType[p.getTeraType()], - isTerastallized: p.isTerastallized, - level: p.level, - currentHP: p.hp, - maxHP: p.getMaxHp(), - status: p.status?.effect ? StatusEffect[p.status.effect] : "", - }; - }) - : [], + party: + this.party?.map(p => ({ + name: p.name, + form: p.getFormKey(), + types: p.getTypes().map(type => PokemonType[type]), + teraType: PokemonType[p.getTeraType()], + isTerastallized: p.isTerastallized, + level: p.level, + currentHP: p.hp, + maxHP: p.getMaxHp(), + status: p.status?.effect ? StatusEffect[p.status.effect] : "", + })) ?? [], // TODO: review if this can be nullish modeChain: this.ui?.getModeChain() ?? [], }; (window as any).gameInfo = gameInfo; @@ -3963,16 +3960,13 @@ export default class BattleScene extends SceneBase { if (previousEncounter !== null && encounterType === previousEncounter) { return false; } - if ( + return !( this.mysteryEncounterSaveData.encounteredEvents.length > 0 && encounterCandidate.maxAllowedEncounters && encounterCandidate.maxAllowedEncounters > 0 && this.mysteryEncounterSaveData.encounteredEvents.filter(e => e.type === encounterType).length >= encounterCandidate.maxAllowedEncounters - ) { - return false; - } - return true; + ); }) .map(m => allMysteryEncounters[m]); // Decrement tier diff --git a/src/configs/inputs/configHandler.ts b/src/configs/inputs/configHandler.ts index b896f303cb3..227c2b964b9 100644 --- a/src/configs/inputs/configHandler.ts +++ b/src/configs/inputs/configHandler.ts @@ -197,10 +197,7 @@ export function canIAssignThisKey(config, key) { export function canIOverrideThisSetting(config, settingName) { const key = getKeyWithSettingName(config, settingName); // || isTheLatestBind(config, settingName) no longer needed since action and cancel are protected - if (config.blacklist?.includes(key)) { - return false; - } - return true; + return !config.blacklist?.includes(key); } export function canIDeleteThisKey(config, key) { diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 1bd71df32e0..6e6c00b8a43 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -1246,7 +1246,7 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr { /** * Determine if the move type change attribute can be applied - * + * * Can be applied if: * - The ability's condition is met, e.g. pixilate only boosts normal moves, * - The move is not forbidden from having its type changed by an ability, e.g. {@linkcode MoveId.MULTI_ATTACK} @@ -1262,7 +1262,7 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr { */ override canApplyPreAttack(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _defender: Pokemon | null, move: Move, _args: [NumberHolder?, NumberHolder?, ...any]): boolean { return (!this.condition || this.condition(pokemon, _defender, move)) && - !noAbilityTypeOverrideMoves.has(move.id) && + !noAbilityTypeOverrideMoves.has(move.id) && (!pokemon.isTerastallized || (move.id !== MoveId.TERA_BLAST && (move.id !== MoveId.TERA_STARSTORM || pokemon.getTeraType() !== PokemonType.STELLAR || !pokemon.hasSpecies(SpeciesId.TERAPAGOS)))); @@ -2653,11 +2653,7 @@ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { } const ally = pokemon.getAlly(); - if (isNullOrUndefined(ally) || ally.getStatStages().every(s => s === 0)) { - return false; - } - - return true; + return !(isNullOrUndefined(ally) || ally.getStatStages().every(s => s === 0)); } override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { @@ -2723,11 +2719,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { } // transforming from or into fusion pokemon causes various problems (including crashes and save corruption) - if (this.getTarget(targets).fusionSpecies || pokemon.fusionSpecies) { - return false; - } - - return true; + return !(this.getTarget(targets).fusionSpecies || pokemon.fusionSpecies); } override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { @@ -3544,10 +3536,7 @@ export class BlockStatusDamageAbAttr extends AbAttr { } override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - if (pokemon.status && this.effects.includes(pokemon.status.effect)) { - return true; - } - return false; + return !!pokemon.status?.effect && this.effects.includes(pokemon.status.effect); } /** @@ -4803,11 +4792,7 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr { const diedToDirectDamage = move !== undefined && attacker !== undefined && move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}); const cancelled = new BooleanHolder(false); globalScene.getField(true).map(p => applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled, simulated)); - if (!diedToDirectDamage || cancelled.value || attacker!.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { - return false; - } - - return true; + return !(!diedToDirectDamage || cancelled.value || attacker!.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)); } override applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): void { @@ -6508,12 +6493,7 @@ export function initAbilities() { new Ability(AbilityId.INTIMIDATE, 3) .attr(PostSummonStatStageChangeAbAttr, [ Stat.ATK ], -1, false, true), new Ability(AbilityId.SHADOW_TAG, 3) - .attr(ArenaTrapAbAttr, (user, target) => { - if (target.hasAbility(AbilityId.SHADOW_TAG)) { - return false; - } - return true; - }), + .attr(ArenaTrapAbAttr, (_user, target) => !target.hasAbility(AbilityId.SHADOW_TAG)), new Ability(AbilityId.ROUGH_SKIN, 3) .attr(PostDefendContactDamageAbAttr, 8) .bypassFaint(), @@ -6573,10 +6553,7 @@ export function initAbilities() { .ignorable(), new Ability(AbilityId.MAGNET_PULL, 3) .attr(ArenaTrapAbAttr, (user, target) => { - if (target.getTypes(true).includes(PokemonType.STEEL) || (target.getTypes(true).includes(PokemonType.STELLAR) && target.getTypes().includes(PokemonType.STEEL))) { - return true; - } - return false; + return target.getTypes(true).includes(PokemonType.STEEL) || (target.getTypes(true).includes(PokemonType.STELLAR) && target.getTypes().includes(PokemonType.STEEL)); }), new Ability(AbilityId.SOUNDPROOF, 3) .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.SOUND_BASED)) @@ -6654,12 +6631,7 @@ export function initAbilities() { .attr(PostSummonWeatherChangeAbAttr, WeatherType.SUNNY) .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.SUNNY), new Ability(AbilityId.ARENA_TRAP, 3) - .attr(ArenaTrapAbAttr, (user, target) => { - if (target.isGrounded()) { - return true; - } - return false; - }) + .attr(ArenaTrapAbAttr, (user, target) => target.isGrounded()) .attr(DoubleBattleChanceAbAttr), new Ability(AbilityId.VITAL_SPIRIT, 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index c047f424591..4d99fd18ac0 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1203,10 +1203,7 @@ export class EncoreTag extends MoveRestrictionBattlerTag { override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.CUSTOM) { const encoredMove = pokemon.getMoveset().find(m => m.moveId === this.moveId); - if (encoredMove && encoredMove?.getPpRatio() > 0) { - return true; - } - return false; + return !isNullOrUndefined(encoredMove) && encoredMove.getPpRatio() > 0; } return super.lapse(pokemon, lapseType); } @@ -1218,10 +1215,7 @@ export class EncoreTag extends MoveRestrictionBattlerTag { * @returns `true` if the move does not match with the moveId stored and as a result, restricted */ override isMoveRestricted(move: MoveId, _user?: Pokemon): boolean { - if (move !== this.moveId) { - return true; - } - return false; + return move !== this.moveId; } override selectionDeniedText(_pokemon: Pokemon, move: MoveId): string { @@ -2768,10 +2762,7 @@ export class HealBlockTag extends MoveRestrictionBattlerTag { * @returns `true` if the move has a TRIAGE_MOVE flag and is a status move */ override isMoveRestricted(move: MoveId): boolean { - if (allMoves[move].hasFlag(MoveFlags.TRIAGE_MOVE) && allMoves[move].category === MoveCategory.STATUS) { - return true; - } - return false; + return allMoves[move].hasFlag(MoveFlags.TRIAGE_MOVE) && allMoves[move].category === MoveCategory.STATUS; } /** @@ -2785,10 +2776,7 @@ export class HealBlockTag extends MoveRestrictionBattlerTag { override isMoveTargetRestricted(move: MoveId, user: Pokemon, target: Pokemon) { const moveCategory = new NumberHolder(allMoves[move].category); applyMoveAttrs(StatusCategoryOnAllyAttr, user, target, allMoves[move], moveCategory); - if (allMoves[move].hasAttr(HealOnAllyAttr) && moveCategory.value === MoveCategory.STATUS) { - return true; - } - return false; + return allMoves[move].hasAttr(HealOnAllyAttr) && moveCategory.value === MoveCategory.STATUS; } /** @@ -3126,10 +3114,7 @@ export class TormentTag extends MoveRestrictionBattlerTag { const moveObj = allMoves[lastMove.move]; const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || user.getTag(BattlerTagType.FRENZY); const validLastMoveResult = lastMove.result === MoveResult.SUCCESS || lastMove.result === MoveResult.MISS; - if (lastMove.move === move && validLastMoveResult && lastMove.move !== MoveId.STRUGGLE && !isUnaffected) { - return true; - } - return false; + return lastMove.move === move && validLastMoveResult && lastMove.move !== MoveId.STRUGGLE && !isUnaffected; } override selectionDeniedText(pokemon: Pokemon, _move: MoveId): string { diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 91cc6350daa..e98b28e2a48 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -5472,13 +5472,6 @@ export class AddBattlerTagAttr extends MoveEffectAttr { 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 { if (!super.apply(user, target, move, args)) { return false; @@ -6832,7 +6825,7 @@ export class RandomMovesetMoveAttr extends CallMoveAttr { return false; } - this.moveId = moves[user.randBattleSeedInt(moves.length)]!.moveId; + this.moveId = moves[user.randBattleSeedInt(moves.length)].moveId; return true; }; } @@ -7367,11 +7360,7 @@ export class SketchAttr extends MoveEffectAttr { return false; } - if (user.getMoveset().find(m => m.moveId === targetMove.move)) { - return false; - } - - return true; + return !user.getMoveset().some(m => m.moveId === targetMove.move); }; } } diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index ec78408ea83..88d9ba402a9 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -275,15 +275,11 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement { override meetsRequirement(): boolean { const timeOfDay = globalScene.arena?.getTimeOfDay(); - if ( + return !( !isNullOrUndefined(timeOfDay) && this.requiredTimeOfDay?.length > 0 && !this.requiredTimeOfDay.includes(timeOfDay) - ) { - return false; - } - - return true; + ); } override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] { @@ -301,15 +297,11 @@ export class WeatherRequirement extends EncounterSceneRequirement { override meetsRequirement(): boolean { const currentWeather = globalScene.arena.weather?.weatherType; - if ( + return !( !isNullOrUndefined(currentWeather) && this.requiredWeather?.length > 0 && !this.requiredWeather.includes(currentWeather!) - ) { - return false; - } - - return true; + ); } override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] { @@ -803,7 +795,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen } filterByForm(pokemon, formChangeItem) { - if ( + return ( pokemonFormChanges.hasOwnProperty(pokemon.species.speciesId) && // Get all form changes for this species with an item trigger, including any compound triggers pokemonFormChanges[pokemon.species.speciesId] @@ -812,10 +804,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen .flatMap(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger) .flatMap(fc => fc.item) .includes(formChangeItem) - ) { - return true; - } - return false; + ); } override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { @@ -873,17 +862,15 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement { ) { return true; } - if ( + + return ( pokemon.isFusion() && pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) && pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter( e => e.item === evolutionItem && (!e.condition || e.condition.predicate(pokemon)), ).length && pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX - ) { - return true; - } - return false; + ); } override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index 1098ddc4eeb..47eb355c8b6 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -186,11 +186,7 @@ export class SpeciesFormChange { } } - if (!this.trigger.canChange(pokemon)) { - return false; - } - - return true; + return this.trigger.canChange(pokemon); } findTrigger(triggerType: Constructor): SpeciesFormChangeTrigger | nil { diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 66ed0b09eeb..df7a8c118d5 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -1281,10 +1281,6 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali }; } - isObtainable() { - return super.isObtainable(); - } - hasVariants() { let variantDataIndex: string | number = this.speciesId; if (this.forms.length > 0) { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index d06553f2227..df953f06834 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1292,19 +1292,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ isOffsetBySubstitute(): boolean { const substitute = this.getTag(SubstituteTag); - if (substitute) { - if (substitute.sprite === undefined) { - return false; - } - - // During the Pokemon's MoveEffect phase, the offset is removed to put the Pokemon "in focus" - const currentPhase = globalScene.getCurrentPhase(); - if (currentPhase?.is("MoveEffectPhase") && currentPhase.getPokemon() === this) { - return false; - } - return true; + if (!substitute || 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(); + return !(currentPhase?.is("MoveEffectPhase") && currentPhase.getPokemon() === this); } /** If this Pokemon has a Substitute on the field, removes its sprite from the field. */ @@ -2241,10 +2234,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.getAbility(ignoreOverride).id === ability && (!canApply || this.canApplyAbility())) { return true; } - if (this.getPassiveAbility().id === ability && this.hasPassive() && (!canApply || this.canApplyAbility(true))) { - return true; - } - return false; + return this.getPassiveAbility().id === ability && this.hasPassive() && (!canApply || this.canApplyAbility(true)); } /** @@ -2261,10 +2251,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).hasAttr(attrType)) { return true; } - if (this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().hasAttr(attrType)) { - return true; - } - return false; + return this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().hasAttr(attrType); } /** @@ -5464,10 +5451,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if ((ownedAbilityAttrs & 2) > 0 && this.hasSameAbilityInRootForm(1)) { return true; } - if ((ownedAbilityAttrs & 4) > 0 && this.hasSameAbilityInRootForm(2)) { - return true; - } - return false; + return (ownedAbilityAttrs & 4) > 0 && this.hasSameAbilityInRootForm(2); } /** diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 2bebbcae90c..f8c71b2c891 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1090,10 +1090,6 @@ export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier { 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}. * @param pokemon The {@linkcode Pokemon} that holds the item @@ -1216,10 +1212,6 @@ export class StatBoosterModifier extends PokemonHeldItemModifier { * @see {@linkcode apply} */ export class EvolutionStatBoosterModifier extends StatBoosterModifier { - clone() { - return super.clone() as EvolutionStatBoosterModifier; - } - matchType(modifier: Modifier): boolean { return modifier instanceof EvolutionStatBoosterModifier; } diff --git a/src/sprites/sprite-utils.ts b/src/sprites/sprite-utils.ts index 8a352de3d55..0f4adf7882f 100644 --- a/src/sprites/sprite-utils.ts +++ b/src/sprites/sprite-utils.ts @@ -21,8 +21,5 @@ export function hasExpSprite(key: string): boolean { if (keyMatch[5]) { k += keyMatch[5]; } - if (!expSpriteKeys.has(k)) { - return false; - } - return true; + return expSpriteKeys.has(k); } From a818c2b33f5981381615d29da92c850f5131b1c7 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Fri, 6 Jun 2025 23:50:16 -0400 Subject: [PATCH 12/51] [Bug] Dancer no longer breaks "last hit only" moves, respects flinch + steadfast (#5945) * WIP * Fixed Dancer last hit, flinch move interaction * Fixed steadfast interaction * Fixed comment + flaky test --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/abilities/ability.ts | 1 + src/data/battler-tags.ts | 18 ++++++--------- test/abilities/dancer.test.ts | 41 +++++++++++++++++++++++++++++++++++ test/moves/instruct.test.ts | 30 +++++++++++++++++++++++-- 4 files changed, 77 insertions(+), 13 deletions(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 6e6c00b8a43..d20eddcb3cb 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -4438,6 +4438,7 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { simulated: boolean, args: any[]): void { if (!simulated) { + dancer.turnData.extraTurns++; // If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance if (move.getMove() instanceof AttackMove || move.getMove() instanceof StatusMove) { const target = this.getTarget(dancer, source, targets); diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 4d99fd18ac0..456f519a34c 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -649,20 +649,14 @@ class NoRetreatTag extends TrappedTag { */ export class FlinchedTag extends BattlerTag { constructor(sourceMove: MoveId) { - super(BattlerTagType.FLINCHED, [BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END], 0, sourceMove); - } - - onAdd(pokemon: Pokemon): void { - super.onAdd(pokemon); - - applyAbAttrs(FlinchEffectAbAttr, pokemon, null); + super(BattlerTagType.FLINCHED, [BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END], 1, sourceMove); } /** - * Cancels the Pokemon's next Move on the turn this tag is applied - * @param pokemon The {@linkcode Pokemon} with this tag - * @param lapseType The {@linkcode BattlerTagLapseType lapse type} used for this function call - * @returns `false` (This tag is always removed after applying its effects) + * Cancels the flinched Pokemon's currently used move this turn if called mid-execution, or removes the tag at end of turn. + * @param pokemon - The {@linkcode Pokemon} with this tag. + * @param lapseType - The {@linkcode BattlerTagLapseType | lapse type} used for this function call. + * @returns Whether the tag should remain active. */ lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.PRE_MOVE) { @@ -672,6 +666,8 @@ export class FlinchedTag extends BattlerTag { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), ); + applyAbAttrs(FlinchEffectAbAttr, pokemon, null); + return true; } return super.lapse(pokemon, lapseType); diff --git a/test/abilities/dancer.test.ts b/test/abilities/dancer.test.ts index 7b4edb84789..086c69300b4 100644 --- a/test/abilities/dancer.test.ts +++ b/test/abilities/dancer.test.ts @@ -1,8 +1,10 @@ import { BattlerIndex } from "#app/battle"; +import { MoveResult } from "#app/field/pokemon"; import type { MovePhase } from "#app/phases/move-phase"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; +import { Stat } from "#enums/stat"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -99,4 +101,43 @@ describe("Abilities - Dancer", () => { expect(currentPhase.pokemon).toBe(oricorio); expect(currentPhase.move.moveId).toBe(MoveId.REVELATION_DANCE); }); + + it("should not break subsequent last hit only moves", async () => { + game.override.battleStyle("single"); + await game.classicMode.startBattle([SpeciesId.ORICORIO, SpeciesId.FEEBAS]); + + const [oricorio, feebas] = game.scene.getPlayerParty(); + + game.move.use(MoveId.BATON_PASS); + game.doSelectPartyPokemon(1); + await game.move.forceEnemyMove(MoveId.SWORDS_DANCE); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase"); + expect(game.field.getPlayerPokemon()).toBe(feebas); + expect(feebas.getStatStage(Stat.ATK)).toBe(2); + expect(oricorio.isOnField()).toBe(false); + expect(oricorio.visible).toBe(false); + }); + + it("should not trigger while flinched", async () => { + game.override.battleStyle("double").moveset(MoveId.SPLASH).enemyMoveset([MoveId.SWORDS_DANCE, MoveId.FAKE_OUT]); + await game.classicMode.startBattle([SpeciesId.ORICORIO]); + + const oricorio = game.scene.getPlayerPokemon()!; + expect(oricorio).toBeDefined(); + + // get faked out and copy swords dance + game.move.select(MoveId.SPLASH); + await game.move.forceEnemyMove(MoveId.SWORDS_DANCE); + await game.move.forceEnemyMove(MoveId.FAKE_OUT, BattlerIndex.PLAYER); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(oricorio.getLastXMoves(-1)[0]).toMatchObject({ + move: MoveId.NONE, + result: MoveResult.FAIL, + }); + expect(oricorio.getStatStage(Stat.ATK)).toBe(0); + }); }); diff --git a/test/moves/instruct.test.ts b/test/moves/instruct.test.ts index 56ac8d9d04d..4a701ed6ac5 100644 --- a/test/moves/instruct.test.ts +++ b/test/moves/instruct.test.ts @@ -1,13 +1,15 @@ import { BattlerIndex } from "#app/battle"; +import { allMoves } from "#app/data/data-lists"; import type Pokemon from "#app/field/pokemon"; import { MoveResult } from "#app/field/pokemon"; import type { MovePhase } from "#app/phases/move-phase"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; +import { Stat } from "#enums/stat"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("Moves - Instruct", () => { let phaserGame: Phaser.Game; @@ -34,7 +36,8 @@ describe("Moves - Instruct", () => { game.override .battleStyle("single") .enemySpecies(SpeciesId.SHUCKLE) - .enemyAbility(AbilityId.NO_GUARD) + .enemyAbility(AbilityId.BALL_FETCH) + .passiveAbility(AbilityId.NO_GUARD) .enemyLevel(100) .startingLevel(100) .disableCrits(); @@ -536,4 +539,27 @@ describe("Moves - Instruct", () => { expect(ivysaur.turnData.attacksReceived.length).toBe(15); }); + + it("should respect prior flinches and trigger Steadfast", async () => { + game.override.battleStyle("double"); + vi.spyOn(allMoves[MoveId.AIR_SLASH], "chance", "get").mockReturnValue(100); + await game.classicMode.startBattle([SpeciesId.AUDINO, SpeciesId.ABRA]); + + // Fake enemy 1 having attacked prior + const [, player2, enemy1, enemy2] = game.scene.getField(); + enemy1.pushMoveHistory({ move: MoveId.ABSORB, targets: [BattlerIndex.PLAYER] }); + game.field.mockAbility(enemy1, AbilityId.STEADFAST); + + game.move.use(MoveId.AIR_SLASH, BattlerIndex.PLAYER, BattlerIndex.ENEMY); + game.move.use(MoveId.INSTRUCT, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY); + await game.move.forceEnemyMove(MoveId.ABSORB); + await game.move.forceEnemyMove(MoveId.INSTRUCT, BattlerIndex.ENEMY); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2]); + await game.toEndOfTurn(); + + expect(enemy1.getLastXMoves(-1).map(m => m.move)).toEqual([MoveId.NONE, MoveId.NONE, MoveId.NONE, MoveId.ABSORB]); + expect(enemy1.getStatStage(Stat.SPD)).toBe(3); + expect(player2.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + expect(enemy2.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + }); }); From c5db8273815b012fbe4d2990cc8a31480c219b94 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Fri, 6 Jun 2025 23:09:23 -0700 Subject: [PATCH 13/51] [Dev] Enable Biome linting of `move-effect-phase.ts` (#5947) --- biome.jsonc | 3 --- src/phases/move-effect-phase.ts | 6 ++++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/biome.jsonc b/biome.jsonc index 40301b3e0bc..141c44dc87d 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -42,9 +42,6 @@ // TODO: Remove if we ever get down to 0 circular imports "organizeImports": { "enabled": false }, "linter": { - "ignore": [ - "src/phases/move-effect-phase.ts" // TODO: unignore after move-effect-phase refactor - ], "enabled": true, "rules": { "recommended": true, diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 3160d848624..d80fdc89e6f 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -219,6 +219,7 @@ export class MoveEffectPhase extends PokemonPhase { return; } break; + // biome-ignore lint/suspicious/noFallthroughSwitchClause: The fallthrough is intentional case HitCheckResult.NO_EFFECT: globalScene.queueMessage( i18next.t(this.move.id === MoveId.SHEER_COLD ? "battle:hitResultImmune" : "battle:hitResultNoEffect", { @@ -294,7 +295,8 @@ export class MoveEffectPhase extends PokemonPhase { // If other effects were overriden, stop this phase before they can be applied if (overridden.value) { - return this.end(); + this.end(); + return; } // Lapse `MOVE_EFFECT` effects (i.e. semi-invulnerability) when applicable @@ -743,7 +745,7 @@ export class MoveEffectPhase extends PokemonPhase { firstTarget?: boolean | null, selfTarget?: boolean, ): void { - return applyFilteredMoveAttrs( + applyFilteredMoveAttrs( (attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === triggerType && From 1ff45687c5259dfc109bf2bf96d64a61da78b22e Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sat, 7 Jun 2025 20:28:01 -0400 Subject: [PATCH 14/51] [Refactor] Merged `interfaces/` into `@types/`; removed runtime orphan modules https://github.com/pagefaultgames/pokerogue/pull/5951 --- src/@types/{DexData.ts => dex-data.ts} | 11 ++++------ .../held-modifier-config.ts | 0 src/{interfaces => @types}/locales.ts | 3 --- .../typedefs.ts => @types/trainer-funcs.ts} | 4 ++-- src/battle-scene.ts | 4 ++-- src/data/abilities/ability-class.ts | 2 +- src/data/egg-hatch-data.ts | 3 ++- src/data/moves/move.ts | 2 +- .../encounters/absolute-avarice-encounter.ts | 2 +- .../encounters/training-session-encounter.ts | 2 +- .../encounters/weird-dream-encounter.ts | 2 +- .../utils/encounter-phase-utils.ts | 2 +- src/data/pokemon-species.ts | 2 +- src/data/trainers/evil-admin-trainer-pools.ts | 2 +- src/data/trainers/trainer-config.ts | 2 +- src/debug.js | 17 -------------- src/global-vars/starter-colors.ts | 5 ++--- src/starter-colors.ts | 4 ---- src/system/game-data.ts | 16 ++------------ src/system/session-history.ts | 22 ------------------- src/ui/pokedex-page-ui-handler.ts | 3 ++- src/ui/pokedex-ui-handler.ts | 3 ++- src/ui/pokemon-info-container.ts | 3 ++- src/ui/starter-select-ui-handler.ts | 9 ++------ 24 files changed, 31 insertions(+), 94 deletions(-) rename src/@types/{DexData.ts => dex-data.ts} (82%) rename src/{interfaces => @types}/held-modifier-config.ts (100%) rename src/{interfaces => @types}/locales.ts (95%) rename src/{data/trainers/typedefs.ts => @types/trainer-funcs.ts} (83%) delete mode 100644 src/debug.js delete mode 100644 src/starter-colors.ts delete mode 100644 src/system/session-history.ts diff --git a/src/@types/DexData.ts b/src/@types/dex-data.ts similarity index 82% rename from src/@types/DexData.ts rename to src/@types/dex-data.ts index 19bb0357471..88cc16886bd 100644 --- a/src/@types/DexData.ts +++ b/src/@types/dex-data.ts @@ -1,6 +1,7 @@ -/** - * Dex entry for a single Pokemon Species - */ +export interface DexData { + [key: number]: DexEntry; +} + export interface DexEntry { seenAttr: bigint; caughtAttr: bigint; @@ -10,7 +11,3 @@ export interface DexEntry { hatchedCount: number; ivs: number[]; } - -export interface DexData { - [key: number]: DexEntry; -} diff --git a/src/interfaces/held-modifier-config.ts b/src/@types/held-modifier-config.ts similarity index 100% rename from src/interfaces/held-modifier-config.ts rename to src/@types/held-modifier-config.ts diff --git a/src/interfaces/locales.ts b/src/@types/locales.ts similarity index 95% rename from src/interfaces/locales.ts rename to src/@types/locales.ts index 2d26911f82f..3b5a1477e19 100644 --- a/src/interfaces/locales.ts +++ b/src/@types/locales.ts @@ -2,9 +2,6 @@ export interface Localizable { localize(): void; } -export interface TranslationEntries { - [key: string]: string | { [key: string]: string }; -} export interface SimpleTranslationEntries { [key: string]: string; } diff --git a/src/data/trainers/typedefs.ts b/src/@types/trainer-funcs.ts similarity index 83% rename from src/data/trainers/typedefs.ts rename to src/@types/trainer-funcs.ts index 3df2ba3f5f8..0546dd53024 100644 --- a/src/data/trainers/typedefs.ts +++ b/src/@types/trainer-funcs.ts @@ -2,8 +2,8 @@ import type { EnemyPokemon } from "#app/field/pokemon"; import type { PersistentModifier } from "#app/modifier/modifier"; import type { PartyMemberStrength } from "#enums/party-member-strength"; import type { SpeciesId } from "#enums/species-id"; -import type { TrainerConfig } from "./trainer-config"; -import type { TrainerPartyTemplate } from "./TrainerPartyTemplate"; +import type { TrainerConfig } from "../data/trainers/trainer-config"; +import type { TrainerPartyTemplate } from "../data/trainers/TrainerPartyTemplate"; export type PartyTemplateFunc = () => TrainerPartyTemplate; export type PartyMemberFunc = (level: number, strength: PartyMemberStrength) => EnemyPokemon; diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 9a46baba899..598ab64881a 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -120,7 +120,7 @@ import { SceneBase } from "#app/scene-base"; import CandyBar from "#app/ui/candy-bar"; import type { Variant } 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 { InputsController } from "#app/inputs-controller"; import { UiInputs } from "#app/ui-inputs"; @@ -169,7 +169,7 @@ import { import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; 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"; diff --git a/src/data/abilities/ability-class.ts b/src/data/abilities/ability-class.ts index 9da83a32c4d..10bd01f3987 100644 --- a/src/data/abilities/ability-class.ts +++ b/src/data/abilities/ability-class.ts @@ -2,7 +2,7 @@ import { AbilityId } from "#enums/ability-id"; import type { AbAttrCondition } from "#app/@types/ability-types"; import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr"; 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"; export class Ability implements Localizable { diff --git a/src/data/egg-hatch-data.ts b/src/data/egg-hatch-data.ts index 949ed1af063..e81ae69515c 100644 --- a/src/data/egg-hatch-data.ts +++ b/src/data/egg-hatch-data.ts @@ -1,6 +1,7 @@ import { globalScene } from "#app/global-scene"; 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 diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index e98b28e2a48..1bd6db97005 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -81,7 +81,7 @@ import { TerrainType } from "../terrain"; import { ModifierPoolType } from "#app/modifier/modifier-type"; import { Command } from "../../ui/command-ui-handler"; import i18next from "i18next"; -import type { Localizable } from "#app/interfaces/locales"; +import type { Localizable } from "#app/@types/locales"; import { getBerryEffectFunc } from "../berry"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#enums/arena-tag-type"; diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index f46e1360b0d..b14caa4e2c3 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -33,7 +33,7 @@ import { } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { TrainerSlot } from "#enums/trainer-slot"; 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 { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { Stat } from "#enums/stat"; diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index 597a6b009b3..b17a39ae694 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -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 { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; 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 { getStatKey } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 25be75b9d5a..2b1f775b78e 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -39,7 +39,7 @@ import { PlayerGender } from "#enums/player-gender"; import { TrainerType } from "#enums/trainer-type"; import PokemonData from "#app/system/pokemon-data"; 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 { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { PartyMemberStrength } from "#enums/party-member-strength"; diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index c9eaa2e6968..5736835d98a 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -50,7 +50,7 @@ import type PokemonSpecies from "#app/data/pokemon-species"; import type { IEggOptions } from "#app/data/egg"; import { Egg } from "#app/data/egg"; 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"; diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index df7a8c118d5..e946b526960 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -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 { PartyMemberStrength } from "#enums/party-member-strength"; import { SpeciesId } from "#enums/species-id"; diff --git a/src/data/trainers/evil-admin-trainer-pools.ts b/src/data/trainers/evil-admin-trainer-pools.ts index 7c0336e784e..74ee3e8cb3d 100644 --- a/src/data/trainers/evil-admin-trainer-pools.ts +++ b/src/data/trainers/evil-admin-trainer-pools.ts @@ -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 { SpeciesId } from "#enums/species-id"; diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 6408bf94eac..8e704b0b301 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -48,7 +48,7 @@ import type { TrainerTierPools, TrainerConfigs, PartyMemberFuncs, -} from "./typedefs"; +} from "../../@types/trainer-funcs"; /** Minimum BST for Pokemon generated onto the Elite Four's teams */ const ELITE_FOUR_MINIMUM_BST = 460; diff --git a/src/debug.js b/src/debug.js deleted file mode 100644 index 6ddf6046c7a..00000000000 --- a/src/debug.js +++ /dev/null @@ -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)); -} diff --git a/src/global-vars/starter-colors.ts b/src/global-vars/starter-colors.ts index 6abe028be99..6b019bd5c34 100644 --- a/src/global-vars/starter-colors.ts +++ b/src/global-vars/starter-colors.ts @@ -1,4 +1,3 @@ -export const starterColors: StarterColors = {}; -interface StarterColors { +export const starterColors: { [key: string]: [string, string]; -} +} = {}; diff --git a/src/starter-colors.ts b/src/starter-colors.ts deleted file mode 100644 index 6abe028be99..00000000000 --- a/src/starter-colors.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const starterColors: StarterColors = {}; -interface StarterColors { - [key: string]: [string, string]; -} diff --git a/src/system/game-data.ts b/src/system/game-data.ts index ab907d2f768..d9b8d73d3c3 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -62,6 +62,7 @@ import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { ArenaTrapTag } from "#app/data/arena-tag"; import { pokemonFormChanges } from "#app/data/pokemon-forms"; import type { PokemonType } from "#enums/pokemon-type"; +import type { DexData, DexEntry } from "../@types/dex-data"; export const defaultStarterSpecies: SpeciesId[] = [ SpeciesId.BULBASAUR, @@ -131,6 +132,7 @@ export function decrypt(data: string, bypassLogin: boolean): string { )(data); } +// TODO: Move all these exported interfaces to @types export interface SystemSaveData { trainerId: number; secretId: number; @@ -191,20 +193,6 @@ export interface VoucherCounts { [type: string]: number; } -export interface DexData { - [key: number]: DexEntry; -} - -export interface DexEntry { - seenAttr: bigint; - caughtAttr: bigint; - natureAttr: number; - seenCount: number; - caughtCount: number; - hatchedCount: number; - ivs: number[]; -} - export type StarterMoveset = [MoveId] | [MoveId, MoveId] | [MoveId, MoveId, MoveId] | [MoveId, MoveId, MoveId, MoveId]; export interface StarterFormMoveData { diff --git a/src/system/session-history.ts b/src/system/session-history.ts deleted file mode 100644 index 8eb81cb6efe..00000000000 --- a/src/system/session-history.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { GameModes } from "../game-mode"; -import type PokemonData from "./pokemon-data"; -import type PersistentModifierData from "./modifier-data"; - -export enum SessionHistoryResult { - ACTIVE, - WIN, - LOSS, -} - -export interface SessionHistory { - seed: string; - playTime: number; - result: SessionHistoryResult; - gameMode: GameModes; - party: PokemonData[]; - modifiers: PersistentModifierData[]; - money: number; - waveIndex: number; - gameVersion: string; - timestamp: number; -} diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts index 00e166f075d..5875e3394a2 100644 --- a/src/ui/pokedex-page-ui-handler.ts +++ b/src/ui/pokedex-page-ui-handler.ts @@ -20,7 +20,8 @@ import { allSpecies, getPokemonSpecies, getPokemonSpeciesForm, normalForm } from import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { starterPassiveAbilities } from "#app/data/balance/passives"; import { PokemonType } from "#enums/pokemon-type"; -import type { DexEntry, StarterAttributes } from "#app/system/game-data"; +import type { StarterAttributes } from "#app/system/game-data"; +import type { DexEntry } from "#app/@types/dex-data"; import { AbilityAttr, DexAttr } from "#app/system/game-data"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import MessageUiHandler from "#app/ui/message-ui-handler"; diff --git a/src/ui/pokedex-ui-handler.ts b/src/ui/pokedex-ui-handler.ts index 8b3633d7422..96451041306 100644 --- a/src/ui/pokedex-ui-handler.ts +++ b/src/ui/pokedex-ui-handler.ts @@ -11,7 +11,8 @@ import { allSpecies, getPokemonSpeciesForm, getPokerusStarters, normalForm } fro import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { catchableSpecies } from "#app/data/balance/biomes"; import { PokemonType } from "#enums/pokemon-type"; -import type { DexAttrProps, DexEntry, StarterAttributes, StarterPreferences } from "#app/system/game-data"; +import type { DexAttrProps, StarterAttributes, StarterPreferences } from "#app/system/game-data"; +import type { DexEntry } from "#app/@types/dex-data"; import { AbilityAttr, DexAttr, loadStarterPreferences } from "#app/system/game-data"; import MessageUiHandler from "#app/ui/message-ui-handler"; import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler"; diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index d8012a58875..3dbe3b7af7d 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -6,7 +6,8 @@ import { getNatureName } from "../data/nature"; import { PokemonType } from "#enums/pokemon-type"; import type Pokemon from "../field/pokemon"; import i18next from "i18next"; -import type { DexEntry, StarterDataEntry } from "../system/game-data"; +import type { StarterDataEntry } from "../system/game-data"; +import type { DexEntry } from "#app/@types/dex-data"; import { DexAttr } from "../system/game-data"; import { fixedInt, getShinyDescriptor } from "#app/utils/common"; import ConfirmUiHandler from "./confirm-ui-handler"; diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 3bea0b08698..b7a7e0f3e6e 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -23,13 +23,8 @@ import { allSpecies, getPokemonSpeciesForm, getPokerusStarters } from "#app/data import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { PokemonType } from "#enums/pokemon-type"; import { GameModes } from "#app/game-mode"; -import type { - DexAttrProps, - DexEntry, - StarterMoveset, - StarterAttributes, - StarterPreferences, -} from "#app/system/game-data"; +import type { DexAttrProps, StarterMoveset, StarterAttributes, StarterPreferences } from "#app/system/game-data"; +import type { DexEntry } from "#app/@types/dex-data"; import { AbilityAttr, DexAttr, loadStarterPreferences, saveStarterPreferences } from "#app/system/game-data"; import { Tutorial, handleTutorial } from "#app/tutorial"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; From d3bc33cd4ea8a315ce32623a526530c1af928a48 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sat, 7 Jun 2025 17:37:09 -0700 Subject: [PATCH 15/51] [Misc] Remove `debug.js` reference from `index.html` --- index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/index.html b/index.html index 111464b5e5c..d503617c13c 100644 --- a/index.html +++ b/index.html @@ -145,6 +145,5 @@ - \ No newline at end of file From ef6029ae4bf2f3b6517837d1d7b282e511049f69 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sat, 7 Jun 2025 20:44:58 -0400 Subject: [PATCH 16/51] [Refactor] Add methods `isPlayer` and `isEnemy` to reduce circular imports https://github.com/pagefaultgames/pokerogue/pull/5902 * Added functions `isPlayer` and `isEnemy` for type checking * Apply suggestions from Kev code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Fix merge issue * Split imports --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/battle-scene.ts | 13 ++--- src/data/abilities/ability.ts | 8 +-- src/data/moves/move.ts | 22 ++++---- src/field/pokemon.ts | 52 ++++++++++++------- src/messages.ts | 4 +- src/phases/faint-phase.ts | 5 +- src/phases/move-effect-phase.ts | 2 +- src/phases/quiet-form-change-phase.ts | 3 +- src/phases/switch-summon-phase.ts | 1 - ...an-offer-you-cant-refuse-encounter.test.ts | 4 +- .../the-pokemon-salesman-encounter.test.ts | 3 +- 11 files changed, 63 insertions(+), 54 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 598ab64881a..762e11ec9f5 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -811,6 +811,7 @@ export default class BattleScene extends SceneBase { } } + // TODO: Add a `getPartyOnSide` function for getting the party of a pokemon public getPlayerParty(): PlayerPokemon[] { return this.party; } @@ -3086,9 +3087,9 @@ export default class BattleScene extends SceneBase { const removeOld = itemModifier.stackCount === 0; - if (!removeOld || !source || this.removeModifier(itemModifier, !source.isPlayer())) { + if (!removeOld || !source || this.removeModifier(itemModifier, source.isEnemy())) { const addModifier = () => { - if (!matchingModifier || this.removeModifier(matchingModifier, !target.isPlayer())) { + if (!matchingModifier || this.removeModifier(matchingModifier, target.isEnemy())) { if (target.isPlayer()) { this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant); if (source && itemLost) { @@ -3492,12 +3493,12 @@ export default class BattleScene extends SceneBase { } if (matchingFormChange) { let phase: Phase; - if (pokemon instanceof PlayerPokemon && !matchingFormChange.quiet) { + if (pokemon.isPlayer() && !matchingFormChange.quiet) { phase = new FormChangePhase(pokemon, matchingFormChange, modal); } else { phase = new QuietFormChangePhase(pokemon, matchingFormChange); } - if (pokemon instanceof PlayerPokemon && !matchingFormChange.quiet && modal) { + if (pokemon.isPlayer() && !matchingFormChange.quiet && modal) { this.overridePhase(phase); } else if (delayed) { this.pushPhase(phase); @@ -3595,7 +3596,7 @@ export default class BattleScene extends SceneBase { activePokemon = activePokemon.concat(this.getEnemyParty()); for (const p of activePokemon) { keys.push(p.getSpriteKey(true)); - if (p instanceof PlayerPokemon) { + if (p.isPlayer()) { keys.push(p.getBattleSpriteKey(true, true)); } keys.push(p.species.getCryKey(p.formIndex)); @@ -3611,7 +3612,7 @@ export default class BattleScene extends SceneBase { * @param pokemon The (enemy) pokemon */ 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.ui.showDialogue( battleSpecDialogue[BattleSpec.FINAL_BOSS].firstStageWin, diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index d20eddcb3cb..4d307b2ce6f 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -2617,7 +2617,7 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt } override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const party = pokemon instanceof PlayerPokemon ? globalScene.getPlayerField() : globalScene.getEnemyField(); + const party = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); return party.filter(p => p.isAllowedInBattle()).length > 0; } @@ -2629,7 +2629,7 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt * @param args - n/a */ override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { - const party = pokemon instanceof PlayerPokemon ? globalScene.getPlayerField() : globalScene.getEnemyField(); + const party = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); const allowedParty = party.filter(p => p.isAllowedInBattle()); if (!simulated) { @@ -5539,7 +5539,7 @@ class ForceSwitchOutHelper { * - Whether there are available party members to switch in. * - If the Pokémon is still alive (hp > 0), and if so, it leaves the field and a new SwitchPhase is initiated. */ - if (switchOutTarget instanceof PlayerPokemon) { + if (switchOutTarget.isPlayer()) { if (globalScene.getPlayerParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) { return false; } @@ -5608,7 +5608,7 @@ class ForceSwitchOutHelper { */ public getSwitchOutCondition(pokemon: Pokemon, opponent: Pokemon): boolean { const switchOutTarget = pokemon; - const player = switchOutTarget instanceof PlayerPokemon; + const player = switchOutTarget.isPlayer(); if (player) { const blockedByAbility = new BooleanHolder(false); diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 1bd6db97005..f610c745fd2 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -837,7 +837,7 @@ export default class Move implements Localizable { 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)); power.value *= typeChangeMovePowerMultiplier.value; @@ -4125,7 +4125,7 @@ export class FriendshipPowerAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { 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); return true; @@ -6149,14 +6149,14 @@ export class RevivalBlessingAttr extends MoveEffectAttr { * @param target {@linkcode Pokemon} target of this move * @param move {@linkcode Move} being used * @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 { // If user is player, checks if the user has fainted pokemon - if (user instanceof PlayerPokemon) { + if (user.isPlayer()) { globalScene.unshiftPhase(new RevivalBlessingPhase(user)); 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 // revives one of said Pokemon selected at random. const faintedPokemon = globalScene.getEnemyParty().filter((p) => p.isFainted() && !p.isBoss()); @@ -6187,10 +6187,8 @@ export class RevivalBlessingAttr extends MoveEffectAttr { getCondition(): MoveConditionFunc { return (user, target, move) => - (user instanceof PlayerPokemon && globalScene.getPlayerParty().some((p) => p.isFainted())) || - (user instanceof EnemyPokemon && - user.hasTrainer() && - globalScene.getEnemyParty().some((p) => p.isFainted() && !p.isBoss())); + user.hasTrainer() && + (user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).some((p: Pokemon) => p.isFainted() && !p.isBoss()); } override getUserBenefitScore(user: Pokemon, _target: Pokemon, _move: Move): number { @@ -6228,7 +6226,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { // (e.g. when it uses Flip Turn), make it spit out the Tatsugiri before switching out. 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 * If it did, the user of U-turn or Volt Switch will not be switched out. @@ -6382,7 +6380,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { getSwitchOutCondition(): MoveConditionFunc { return (user, target, move) => { 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); if (!this.selfSwitch) { @@ -9857,7 +9855,7 @@ export function initMoves() { const lastEnemyFaint = globalScene.currentBattle.enemyFaintsHistory[globalScene.currentBattle.enemyFaintsHistory.length - 1]; return ( (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; }), new AttackMove(MoveId.FINAL_GAMBIT, PokemonType.FIGHTING, MoveCategory.SPECIAL, -1, 100, 5, -1, 0, 5) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index df953f06834..d26dfd193f9 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -503,7 +503,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (level > 1) { 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); } @@ -788,7 +788,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return true; } - abstract isPlayer(): boolean; + abstract isPlayer(): this is PlayerPokemon; + + abstract isEnemy(): this is EnemyPokemon; abstract hasTrainer(): boolean; @@ -2050,7 +2052,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (Overrides.ABILITY_OVERRIDE && this.isPlayer()) { 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]; } if (this.isFusion()) { @@ -2080,7 +2082,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (Overrides.PASSIVE_ABILITY_OVERRIDE && this.isPlayer()) { 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]; } if (!isNullOrUndefined(this.customPokemonData.passive) && this.customPokemonData.passive !== -1) { @@ -2152,7 +2154,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // returns override if valid for current case if ( (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; } @@ -2160,7 +2162,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ((Overrides.PASSIVE_ABILITY_OVERRIDE !== AbilityId.NONE || Overrides.HAS_PASSIVE_ABILITY_OVERRIDE) && this.isPlayer()) || ((Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== AbilityId.NONE || Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE) && - !this.isPlayer()) + this.isEnemy()) ) { return true; } @@ -2169,7 +2171,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const { currentBattle, gameMode } = globalScene; const waveIndex = currentBattle?.waveIndex; if ( - this instanceof EnemyPokemon && + this.isEnemy() && (currentBattle?.battleSpec === BattleSpec.FINAL_BOSS || gameMode.isEndlessMinorBoss(waveIndex) || gameMode.isEndlessMajorBoss(waveIndex)) @@ -2979,9 +2981,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { 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); - } 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); } @@ -3292,7 +3294,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.setVisible(true); 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({ targets: [this.battleInfo, this.battleInfo.expMaskRect], @@ -3313,7 +3316,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ease: "Cubic.easeIn", onComplete: () => { 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.setX(this.battleInfo.x - (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198)); @@ -3408,7 +3412,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns An array of Pokémon on the allied field. */ getAlliedField(): Pokemon[] { - return this instanceof PlayerPokemon ? globalScene.getPlayerField() : globalScene.getEnemyField(); + return this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); } /** @@ -4260,7 +4264,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Copy all stat stages for (const s of BATTLE_STATS) { const sourceStage = source.getStatStage(s); - if (this instanceof PlayerPokemon && sourceStage === 6) { + if (this.isPlayer() && sourceStage === 6) { globalScene.validateAchv(achvs.TRANSFER_MAX_STAT_STAGE); } this.setStatStage(s, sourceStage); @@ -5468,7 +5472,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { heldItem.stackCount--; if (heldItem.stackCount <= 0) { - globalScene.removeModifier(heldItem, !this.isPlayer()); + globalScene.removeModifier(heldItem, this.isEnemy()); } if (forBattle) { applyPostItemLostAbAttrs(PostItemLostAbAttr, this, false); @@ -5541,15 +5545,19 @@ export class PlayerPokemon extends Pokemon { this.battleInfo.initInfo(this); } - isPlayer(): boolean { + override isPlayer(): this is PlayerPokemon { return true; } - hasTrainer(): boolean { + override isEnemy(): this is EnemyPokemon { + return false; + } + + override hasTrainer(): boolean { return true; } - isBoss(): boolean { + override isBoss(): boolean { return false; } @@ -6494,15 +6502,19 @@ export class EnemyPokemon extends Pokemon { return [sortedBenefitScores[targetIndex][0]]; } - isPlayer() { + override isPlayer(): this is PlayerPokemon { return false; } - hasTrainer(): boolean { + override isEnemy(): this is EnemyPokemon { + return true; + } + + override hasTrainer(): boolean { return !!this.trainerSlot; } - isBoss(): boolean { + override isBoss(): boolean { return !!this.bossSegments; } diff --git a/src/messages.ts b/src/messages.ts index c29151a98b3..21473eb8361 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -16,7 +16,7 @@ export function getPokemonNameWithAffix(pokemon: Pokemon | undefined, useIllusio switch (globalScene.currentBattle.battleSpec) { case BattleSpec.DEFAULT: - return !pokemon.isPlayer() + return pokemon.isEnemy() ? pokemon.hasTrainer() ? i18next.t("battle:foePokemonWithAffix", { pokemonName: pokemon.getNameToRender(useIllusion), @@ -26,7 +26,7 @@ export function getPokemonNameWithAffix(pokemon: Pokemon | undefined, useIllusio }) : pokemon.getNameToRender(useIllusion); case BattleSpec.FINAL_BOSS: - return !pokemon.isPlayer() + return pokemon.isEnemy() ? i18next.t("battle:foePokemonWithAffix", { pokemonName: pokemon.getNameToRender(useIllusion) }) : pokemon.getNameToRender(useIllusion); default: diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 7332d6b9462..fac0947c4e7 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -18,7 +18,8 @@ import { BattleSpec } from "#app/enums/battle-spec"; import { StatusEffect } from "#app/enums/status-effect"; import type { EnemyPokemon } 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 { PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { SwitchType } from "#enums/switch-type"; @@ -203,7 +204,7 @@ export class FaintPhase extends PokemonPhase { } pokemon.faintCry(() => { - if (pokemon instanceof PlayerPokemon) { + if (pokemon.isPlayer()) { pokemon.addFriendship(-FRIENDSHIP_LOSS_FROM_FAINT); } pokemon.hideInfo(); diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index d80fdc89e6f..e62f65b029b 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -887,7 +887,7 @@ export class MoveEffectPhase extends PokemonPhase { sourceBattlerIndex: user.getBattlerIndex(), }); - if (user.isPlayer() && !target.isPlayer()) { + if (user.isPlayer() && target.isEnemy()) { globalScene.applyModifiers(DamageMoneyRewardModifier, true, user, new NumberHolder(damage)); } diff --git a/src/phases/quiet-form-change-phase.ts b/src/phases/quiet-form-change-phase.ts index 9f6b5cb3361..f677dcc48df 100644 --- a/src/phases/quiet-form-change-phase.ts +++ b/src/phases/quiet-form-change-phase.ts @@ -6,7 +6,6 @@ import { getTypeRgb } from "#app/data/type"; import { BattleSpec } from "#app/enums/battle-spec"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import type Pokemon from "#app/field/pokemon"; -import { EnemyPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { BattlePhase } from "./battle-phase"; import type { MovePhase } from "./move-phase"; @@ -158,7 +157,7 @@ export class QuietFormChangePhase extends BattlePhase { end(): void { this.pokemon.findAndRemoveTags(t => t.tagType === BattlerTagType.AUTOTOMIZED); - if (globalScene?.currentBattle.battleSpec === BattleSpec.FINAL_BOSS && this.pokemon instanceof EnemyPokemon) { + if (globalScene?.currentBattle.battleSpec === BattleSpec.FINAL_BOSS && this.pokemon.isEnemy()) { globalScene.playBgm(); globalScene.unshiftPhase( new PokemonHealPhase(this.pokemon.getBattlerIndex(), this.pokemon.getMaxHp(), null, false, false, false, true), diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index d81ca6029c5..1872dae1f1f 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -139,7 +139,6 @@ export class SwitchSummonPhase extends SummonPhase { return; } - if (this.switchType === SwitchType.BATON_PASS) { // If switching via baton pass, update opposing tags coming from the prior pokemon (this.player ? globalScene.getEnemyField() : globalScene.getPlayerField()).forEach((enemyPokemon: Pokemon) => diff --git a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts index f39ce753b10..786090aa8d6 100644 --- a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts +++ b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts @@ -8,7 +8,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; import type BattleScene from "#app/battle-scene"; -import { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/field/pokemon"; import { AnOfferYouCantRefuseEncounter } from "#app/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -105,7 +105,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { i18next.t("ability:intimidate.name"), ); expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.moveOrAbility).toBe(i18next.t("ability:intimidate.name")); - expect(AnOfferYouCantRefuseEncounter.misc.pokemon instanceof PlayerPokemon).toBeTruthy(); + expect(AnOfferYouCantRefuseEncounter.misc.pokemon.isPlayer()).toBeTruthy(); expect(AnOfferYouCantRefuseEncounter.misc?.price?.toString()).toBe( AnOfferYouCantRefuseEncounter.dialogueTokens?.price, ); diff --git a/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts b/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts index 15fc3ffb00b..73632990dbf 100644 --- a/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts @@ -10,7 +10,6 @@ import { runSelectMysteryEncounterOption, } from "#test/mystery-encounter/encounter-test-utils"; import type BattleScene from "#app/battle-scene"; -import { PlayerPokemon } from "#app/field/pokemon"; import { HUMAN_TRANSITABLE_BIOMES } from "#app/data/mystery-encounters/mystery-encounters"; import { getSalesmanSpeciesOffer, @@ -99,7 +98,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => { expect(ThePokemonSalesmanEncounter.dialogueTokens?.purchasePokemon).toBeDefined(); expect(ThePokemonSalesmanEncounter.dialogueTokens?.price).toBeDefined(); - expect(ThePokemonSalesmanEncounter.misc.pokemon instanceof PlayerPokemon).toBeTruthy(); + expect(ThePokemonSalesmanEncounter.misc.pokemon.isPlayer()).toBeTruthy(); expect(ThePokemonSalesmanEncounter.misc?.price?.toString()).toBe(ThePokemonSalesmanEncounter.dialogueTokens?.price); expect(onInitResult).toBe(true); }); From 31140356733b1eda36546d1089165e24a7e0f150 Mon Sep 17 00:00:00 2001 From: Jimmybald1 <122436263+Jimmybald1@users.noreply.github.com> Date: Sun, 8 Jun 2025 02:54:33 +0200 Subject: [PATCH 17/51] [Balance] Update catch rates to Gen 9 (#5954) Updated catch rates to gen 9w Co-authored-by: Jimmybald1 <147992650+IBBCalc@users.noreply.github.com> --- src/data/pokemon-species.ts | 48 ++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index e946b526960..c5d798b5841 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -2022,9 +2022,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("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 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("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 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, 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, 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.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, @@ -2270,10 +2270,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.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.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 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("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("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 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, 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, 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, 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.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), @@ -2740,10 +2740,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_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.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.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.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.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.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, 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, 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, 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.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), @@ -2751,11 +2751,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.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.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 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("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("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("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 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, 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, 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, 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, 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 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), @@ -2964,15 +2964,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("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.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.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.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 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("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 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, 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, 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, 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, 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, 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.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.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, 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 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), From 93745f14b76309e8d34887b868a6fd561e28f10d Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Sat, 7 Jun 2025 19:59:30 -0500 Subject: [PATCH 18/51] [Refactor] Decouple phase system from battle-scene (#5953) * Move phase logic into its own class * Move ts ignore comment --- src/battle-scene.ts | 353 ++---------------- src/battle.ts | 2 +- src/data/abilities/ability.ts | 122 +++--- src/data/arena-tag.ts | 108 +++--- src/data/battler-tags.ts | 246 ++++++------ src/data/berry.ts | 16 +- src/data/moves/move.ts | 224 +++++------ .../encounters/a-trainers-test-encounter.ts | 2 +- .../encounters/absolute-avarice-encounter.ts | 2 +- .../an-offer-you-cant-refuse-encounter.ts | 2 +- .../encounters/berries-abound-encounter.ts | 2 +- .../encounters/bug-type-superfan-encounter.ts | 2 +- .../encounters/dancing-lessons-encounter.ts | 4 +- .../encounters/dark-deal-encounter.ts | 2 +- .../encounters/delibirdy-encounter.ts | 12 +- .../encounters/fiery-fallout-encounter.ts | 4 +- .../encounters/fight-or-flight-encounter.ts | 4 +- .../encounters/fun-and-games-encounter.ts | 4 +- .../encounters/mysterious-chest-encounter.ts | 4 +- .../encounters/safari-zone-encounter.ts | 8 +- .../slumbering-snorlax-encounter.ts | 2 +- .../teleporting-hijinks-encounter.ts | 4 +- .../the-expert-pokemon-breeder-encounter.ts | 4 +- .../encounters/the-strong-stuff-encounter.ts | 2 +- .../the-winstrate-challenge-encounter.ts | 6 +- .../encounters/uncommon-breed-encounter.ts | 2 +- .../utils/encounter-dialogue-utils.ts | 2 +- .../utils/encounter-phase-utils.ts | 52 +-- .../utils/encounter-pokemon-utils.ts | 2 +- src/field/arena.ts | 24 +- src/field/pokemon.ts | 30 +- src/modifier/modifier.ts | 30 +- src/phase-manager.ts | 311 +++++++++++++++ src/phase.ts | 2 +- src/phases/attempt-capture-phase.ts | 2 +- src/phases/attempt-run-phase.ts | 10 +- src/phases/battle-end-phase.ts | 8 +- src/phases/berry-phase.ts | 4 +- src/phases/check-status-effect-phase.ts | 2 +- src/phases/check-switch-phase.ts | 6 +- src/phases/command-phase.ts | 8 +- src/phases/egg-hatch-phase.ts | 2 +- src/phases/egg-lapse-phase.ts | 8 +- src/phases/encounter-phase.ts | 28 +- src/phases/evolution-phase.ts | 8 +- src/phases/exp-phase.ts | 2 +- src/phases/faint-phase.ts | 16 +- src/phases/form-change-phase.ts | 2 +- src/phases/game-over-phase.ts | 42 ++- src/phases/learn-move-phase.ts | 4 +- src/phases/level-up-phase.ts | 4 +- src/phases/login-phase.ts | 6 +- src/phases/message-phase.ts | 2 +- src/phases/move-charge-phase.ts | 4 +- src/phases/move-effect-phase.ts | 18 +- src/phases/move-phase.ts | 22 +- src/phases/mystery-encounter-phases.ts | 50 +-- src/phases/new-battle-phase.ts | 6 +- src/phases/obtain-status-effect-phase.ts | 4 +- src/phases/pokemon-heal-phase.ts | 8 +- src/phases/pokemon-transform-phase.ts | 2 +- src/phases/post-game-over-phase.ts | 2 +- src/phases/post-turn-status-effect-phase.ts | 2 +- src/phases/quiet-form-change-phase.ts | 6 +- src/phases/revival-blessing-phase.ts | 10 +- src/phases/select-biome-phase.ts | 4 +- src/phases/select-modifier-phase.ts | 4 +- src/phases/select-starter-phase.ts | 4 +- src/phases/select-target-phase.ts | 4 +- src/phases/show-ability-phase.ts | 4 +- src/phases/show-party-exp-bar-phase.ts | 4 +- src/phases/stat-stage-change-phase.ts | 14 +- src/phases/summon-phase.ts | 8 +- src/phases/switch-phase.ts | 8 +- src/phases/switch-summon-phase.ts | 2 +- src/phases/tera-phase.ts | 2 +- src/phases/title-phase.ts | 22 +- src/phases/trainer-victory-phase.ts | 10 +- src/phases/turn-end-phase.ts | 4 +- src/phases/turn-init-phase.ts | 18 +- src/phases/turn-start-phase.ts | 28 +- src/phases/unavailable-phase.ts | 2 +- src/phases/victory-phase.ts | 34 +- src/phases/weather-effect-phase.ts | 2 +- src/system/game-data.ts | 24 +- src/ui/ball-ui-handler.ts | 2 +- src/ui/challenges-select-ui-handler.ts | 10 +- src/ui/command-ui-handler.ts | 18 +- src/ui/egg-hatch-scene-handler.ts | 2 +- src/ui/egg-summary-ui-handler.ts | 2 +- src/ui/fight-ui-handler.ts | 12 +- src/ui/menu-ui-handler.ts | 2 +- src/ui/mystery-encounter-ui-handler.ts | 4 +- src/ui/party-ui-handler.ts | 6 +- src/ui/pokedex-page-ui-handler.ts | 2 +- src/ui/starter-select-ui-handler.ts | 10 +- test/abilities/cud_chew.test.ts | 4 +- test/abilities/dancer.test.ts | 8 +- test/abilities/desolate-land.test.ts | 2 +- test/abilities/disguise.test.ts | 2 +- test/abilities/honey_gather.test.ts | 2 +- test/abilities/imposter.test.ts | 4 +- test/abilities/mycelium_might.test.ts | 6 +- test/abilities/neutralizing_gas.test.ts | 2 +- test/abilities/no_guard.test.ts | 2 +- test/abilities/shield_dust.test.ts | 2 +- test/abilities/speed_boost.test.ts | 4 +- test/abilities/stall.test.ts | 6 +- test/battle/battle-order.test.ts | 10 +- test/battle/battle.test.ts | 12 +- test/battle/special_battle.test.ts | 18 +- test/battlerTags/octolock.test.ts | 4 +- test/battlerTags/stockpiling.test.ts | 32 +- test/battlerTags/substitute.test.ts | 31 +- test/escape-calculations.test.ts | 16 +- test/items/lock_capsule.test.ts | 4 +- test/moves/after_you.test.ts | 2 +- test/moves/dynamax_cannon.test.ts | 16 +- test/moves/focus_punch.test.ts | 4 +- test/moves/fusion_flare_bolt.test.ts | 36 +- test/moves/instruct.test.ts | 4 +- test/moves/round.test.ts | 4 +- test/moves/shell_trap.test.ts | 8 +- test/moves/substitute.test.ts | 2 +- test/moves/transform.test.ts | 4 +- test/moves/whirlwind.test.ts | 2 +- .../mystery-encounter/encounter-test-utils.ts | 12 +- .../a-trainers-test-encounter.test.ts | 8 +- .../absolute-avarice-encounter.test.ts | 6 +- .../berries-abound-encounter.test.ts | 12 +- .../bug-type-superfan-encounter.test.ts | 36 +- .../clowning-around-encounter.test.ts | 8 +- .../dancing-lessons-encounter.test.ts | 12 +- .../encounters/delibirdy-encounter.test.ts | 12 +- .../department-store-sale-encounter.test.ts | 8 +- .../fiery-fallout-encounter.test.ts | 12 +- .../fight-or-flight-encounter.test.ts | 10 +- .../fun-and-games-encounter.test.ts | 38 +- .../global-trade-system-encounter.test.ts | 2 +- .../encounters/lost-at-sea-encounter.test.ts | 8 +- .../mysterious-challengers-encounter.test.ts | 12 +- .../encounters/part-timer-encounter.test.ts | 4 +- .../encounters/safari-zone.test.ts | 4 +- .../teleporting-hijinks-encounter.test.ts | 14 +- .../the-expert-breeder-encounter.test.ts | 12 +- .../the-pokemon-salesman-encounter.test.ts | 4 +- .../the-strong-stuff-encounter.test.ts | 6 +- .../the-winstrate-challenge-encounter.test.ts | 14 +- .../trash-to-treasure-encounter.test.ts | 8 +- .../uncommon-breed-encounter.test.ts | 20 +- .../encounters/weird-dream-encounter.test.ts | 8 +- .../mystery-encounter-utils.test.ts | 4 +- .../mystery-encounter.test.ts | 2 +- test/phases/mystery-encounter-phase.test.ts | 6 +- test/phases/phases.test.ts | 6 +- test/phases/select-modifier-phase.test.ts | 12 +- test/testUtils/gameManager.ts | 22 +- test/testUtils/helpers/challengeModeHelper.ts | 2 +- test/testUtils/helpers/classicModeHelper.ts | 2 +- test/testUtils/helpers/moveHelper.ts | 27 +- test/testUtils/helpers/reloadHelper.ts | 4 +- test/testUtils/phaseInterceptor.ts | 12 +- test/ui/starter-select.test.ts | 18 +- 163 files changed, 1465 insertions(+), 1361 deletions(-) create mode 100644 src/phase-manager.ts diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 762e11ec9f5..23601910e4d 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -144,7 +144,6 @@ import { battleSpecDialogue } from "#app/data/dialogue"; import { LoadingScene } from "#app/loading-scene"; import { LevelCapPhase } from "#app/phases/level-cap-phase"; import { LoginPhase } from "#app/phases/login-phase"; -import { MessagePhase } from "#app/phases/message-phase"; import type { MovePhase } from "#app/phases/move-phase"; import { NewBiomeEncounterPhase } from "#app/phases/new-biome-encounter-phase"; import { NextEncounterPhase } from "#app/phases/next-encounter-phase"; @@ -155,7 +154,6 @@ import { ShowTrainerPhase } from "#app/phases/show-trainer-phase"; import { SummonPhase } from "#app/phases/summon-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 MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { @@ -178,13 +176,12 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters"; import { StatusEffect } from "#enums/status-effect"; 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 { hasExpSprite } from "./sprites/sprite-utils"; import { timedEventManager } from "./global-event-manager"; import { starterColors } from "./global-vars/starter-colors"; import { startingWave } from "./starting-wave"; +import { PhaseManager } from "./phase-manager"; const DEBUG_RNG = false; @@ -297,18 +294,8 @@ export default class BattleScene extends SceneBase { public gameData: GameData; public sessionSlotId: number; - /** 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 or inserting Phases "out of order" */ - private phaseQueuePrependSpliceIndex: number; - private nextCommandPhaseQueue: Phase[]; - - private currentPhase: Phase | null; - private standbyPhase: Phase | null; + /** Manager for the phases active in the battle scene */ + public readonly phaseManager: PhaseManager; public field: Phaser.GameObjects.Container; public fieldUI: Phaser.GameObjects.Container; public charSprite: CharSprite; @@ -396,11 +383,7 @@ export default class BattleScene extends SceneBase { constructor() { super("battle"); - this.phaseQueue = []; - this.phaseQueuePrepend = []; - this.conditionalQueue = []; - this.phaseQueuePrependSpliceIndex = -1; - this.nextCommandPhaseQueue = []; + this.phaseManager = new PhaseManager(); this.eventManager = new TimedEventManager(); this.updateGameInfo(); initGlobalScene(this); @@ -716,10 +699,10 @@ export default class BattleScene extends SceneBase { ).then(() => loadMoveAnimAssets(defaultMoves, true)), this.initStarterColors(), ]).then(() => { - this.pushPhase(new LoginPhase()); - this.pushPhase(new TitlePhase()); + this.phaseManager.pushPhase(new LoginPhase()); + this.phaseManager.pushPhase(new TitlePhase()); - this.shiftPhase(); + this.phaseManager.shiftPhase(); }); } @@ -899,7 +882,7 @@ export default class BattleScene extends SceneBase { if (allyPokemon?.isActive(true)) { let targetingMovePhase: MovePhase; do { - targetingMovePhase = this.findPhase( + targetingMovePhase = this.phaseManager.findPhase( mp => mp.is("MovePhase") && mp.targets.length === 1 && @@ -1277,7 +1260,7 @@ export default class BattleScene extends SceneBase { duration: 250, ease: "Sine.easeInOut", onComplete: () => { - this.clearPhaseQueue(); + this.phaseManager.clearPhaseQueue(); this.ui.freeUIData(); this.uiContainer.remove(this.ui, true); @@ -1450,7 +1433,7 @@ export default class BattleScene extends SceneBase { } if (lastBattle?.double && !newDouble) { - this.tryRemovePhase((p: Phase) => p.is("SwitchPhase")); + this.phaseManager.tryRemovePhase((p: Phase) => p.is("SwitchPhase")); for (const p of this.getPlayerField()) { p.lapseTag(BattlerTagType.COMMANDED); } @@ -1492,7 +1475,7 @@ export default class BattleScene extends SceneBase { playerField.forEach((pokemon, p) => { if (pokemon.isOnField()) { - this.pushPhase(new ReturnPhase(p)); + this.phaseManager.pushPhase(new ReturnPhase(p)); } }); @@ -1509,7 +1492,7 @@ export default class BattleScene extends SceneBase { } if (!this.trainer.visible) { - this.pushPhase(new ShowTrainerPhase()); + this.phaseManager.pushPhase(new ShowTrainerPhase()); } } @@ -1518,13 +1501,13 @@ export default class BattleScene extends SceneBase { } if (!this.gameMode.hasRandomBiomes && !isNewBiome) { - this.pushPhase(new NextEncounterPhase()); + this.phaseManager.pushPhase(new NextEncounterPhase()); } else { - this.pushPhase(new NewBiomeEncounterPhase()); + this.phaseManager.pushPhase(new NewBiomeEncounterPhase()); const newMaxExpLevel = this.getMaxExpLevel(); if (newMaxExpLevel > maxExpLevel) { - this.pushPhase(new LevelCapPhase()); + this.phaseManager.pushPhase(new LevelCapPhase()); } } } @@ -1588,7 +1571,9 @@ export default class BattleScene extends SceneBase { return 0; } - const isEggPhase: boolean = ["EggLapsePhase", "EggHatchPhase"].includes(this.getCurrentPhase()?.phaseName ?? ""); + const isEggPhase: boolean = ["EggLapsePhase", "EggHatchPhase"].includes( + this.phaseManager.getCurrentPhase()?.phaseName ?? "", + ); if ( // Give trainers with specialty types an appropriately-typed form for Wormadam, Rotom, Arceus, Oricorio, Silvally, or Paldean Tauros. @@ -2615,286 +2600,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

(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): 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): 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 { this.money = Math.min(this.money + amount, Number.MAX_SAFE_INTEGER); this.updateMoneyText(); @@ -2942,7 +2647,7 @@ export default class BattleScene extends SceneBase { } } else if (!virtual) { const defaultModifierType = getDefaultModifierTypeForTier(modifier.type.tier); - this.queueMessage( + this.phaseManager.queueMessage( i18next.t("battle:itemStackFull", { fullItemName: modifier.type.name, itemName: defaultModifierType.name, @@ -3499,11 +3204,11 @@ export default class BattleScene extends SceneBase { phase = new QuietFormChangePhase(pokemon, matchingFormChange); } if (pokemon.isPlayer() && !matchingFormChange.quiet && modal) { - this.overridePhase(phase); + this.phaseManager.overridePhase(phase); } else if (delayed) { - this.pushPhase(phase); + this.phaseManager.pushPhase(phase); } else { - this.unshiftPhase(phase); + this.phaseManager.unshiftPhase(phase); } return true; } @@ -3520,9 +3225,9 @@ export default class BattleScene extends SceneBase { ): boolean { const phase: Phase = new PokemonAnimPhase(battleAnimType, pokemon, fieldAssets); if (delayed) { - this.pushPhase(phase); + this.phaseManager.pushPhase(phase); } else { - this.unshiftPhase(phase); + this.phaseManager.unshiftPhase(phase); } return true; } @@ -3630,19 +3335,19 @@ export default class BattleScene extends SceneBase { this.currentBattle.double = true; const availablePartyMembers = this.getPlayerParty().filter(p => p.isAllowedInBattle()); if (availablePartyMembers.length > 1) { - this.pushPhase(new ToggleDoublePositionPhase(true)); + this.phaseManager.pushPhase(new ToggleDoublePositionPhase(true)); if (!availablePartyMembers[1].isOnField()) { - this.pushPhase(new SummonPhase(1)); + this.phaseManager.pushPhase(new SummonPhase(1)); } } - this.shiftPhase(); + this.phaseManager.shiftPhase(); }, ); return; } - this.shiftPhase(); + this.phaseManager.shiftPhase(); } /** @@ -3754,7 +3459,7 @@ export default class BattleScene extends SceneBase { if (exp) { const partyMemberIndex = party.indexOf(expPartyMembers[pm]); - this.unshiftPhase( + this.phaseManager.unshiftPhase( expPartyMembers[pm].isOnField() ? new ExpPhase(partyMemberIndex, exp) : new ShowPartyExpBarPhase(partyMemberIndex, exp), diff --git a/src/battle.ts b/src/battle.ts index 8e63a680c06..dbfed57ae41 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -205,7 +205,7 @@ export default class Battle { const message = i18next.t("battle:moneyPickedUp", { moneyAmount: formattedMoneyAmount, }); - globalScene.queueMessage(message, undefined, true); + globalScene.phaseManager.queueMessage(message, undefined, true); globalScene.currentBattle.moneyScattered = 0; } diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 4d307b2ce6f..e680a12f9fe 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -159,7 +159,7 @@ export class PostTeraFormChangeStatChangeAbAttr extends AbAttr { statStageChangePhases.push(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages)); for (const statStageChangePhase of statStageChangePhases) { - globalScene.unshiftPhase(statStageChangePhase); + globalScene.phaseManager.unshiftPhase(statStageChangePhase); } } } @@ -410,7 +410,7 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr { super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); if (!pokemon.isFullHp() && !simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), + globalScene.phaseManager.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), toDmgValue(pokemon.getMaxHp() / 4), i18next.t("abilityTriggers:typeImmunityHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); cancelled.value = true; // Suppresses "No Effect" message } @@ -436,7 +436,7 @@ class TypeImmunityStatStageChangeAbAttr extends TypeImmunityAbAttr { super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); cancelled.value = true; // Suppresses "No Effect" message if (!simulated) { - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); } } } @@ -648,7 +648,7 @@ export class MoveImmunityStatStageChangeAbAttr extends MoveImmunityAbAttr { override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); } } /** @@ -675,7 +675,7 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr { */ override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { if (!simulated) { - globalScene.queueMessage(i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) })); + globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) })); } } } @@ -710,10 +710,10 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr { const ally = pokemon.getAlly(); const otherPokemon = !isNullOrUndefined(ally) ? pokemon.getOpponents().concat([ ally ]) : pokemon.getOpponents(); for (const other of otherPokemon) { - globalScene.unshiftPhase(new StatStageChangePhase((other).getBattlerIndex(), false, [ this.stat ], this.stages)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase((other).getBattlerIndex(), false, [ this.stat ], this.stages)); } } else { - globalScene.unshiftPhase(new StatStageChangePhase((this.selfTarget ? pokemon : attacker).getBattlerIndex(), this.selfTarget, [ this.stat ], this.stages)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase((this.selfTarget ? pokemon : attacker).getBattlerIndex(), this.selfTarget, [ this.stat ], this.stages)); } } } @@ -744,7 +744,7 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { if (!simulated) { - globalScene.unshiftPhase(new StatStageChangePhase((this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.stages)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase((this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.stages)); } } } @@ -790,7 +790,7 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { if (!pokemon.getTag(this.tagType) && !simulated) { pokemon.addTag(this.tagType, undefined, undefined, pokemon.id); - globalScene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name })); + globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name })); } } } @@ -915,7 +915,7 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { if (!simulated) { - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); } } @@ -1099,7 +1099,7 @@ export class PostStatStageChangeStatStageChangeAbAttr extends PostStatStageChang override applyPostStatStageChange(pokemon: Pokemon, simulated: boolean, statStagesChanged: BattleStat[], stagesChanged: number, selfTarget: boolean, args: any[]): void { if (!simulated) { - globalScene.unshiftPhase(new StatStageChangePhase((pokemon).getBattlerIndex(), true, this.statsToChange, this.stages)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase((pokemon).getBattlerIndex(), true, this.statsToChange, this.stages)); } } } @@ -1765,7 +1765,7 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; } if (globalScene.tryTransferHeldItemModifier(this.stolenItem, pokemon, false)) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("abilityTriggers:postAttackStealHeldItem", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), defenderName: defender.name, @@ -1892,7 +1892,7 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; } if (globalScene.tryTransferHeldItemModifier(this.stolenItem, pokemon, false)) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("abilityTriggers:postDefendStealHeldItem", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), attackerName: attacker.name, @@ -1999,7 +1999,7 @@ class PostVictoryStatStageChangeAbAttr extends PostVictoryAbAttr { override applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { const stat = typeof this.stat === "function" ? this.stat(pokemon) : this.stat; if (!simulated) { - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ stat ], this.stages)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ stat ], this.stages)); } } } @@ -2047,7 +2047,7 @@ export class PostKnockOutStatStageChangeAbAttr extends PostKnockOutAbAttr { override applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): void { const stat = typeof this.stat === "function" ? this.stat(pokemon) : this.stat; if (!simulated) { - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ stat ], this.stages)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ stat ], this.stages)); } } } @@ -2064,7 +2064,7 @@ export class CopyFaintedAllyAbilityAbAttr extends PostKnockOutAbAttr { override applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): void { if (!simulated) { pokemon.setTempAbility(knockedOut.getAbility()); - globalScene.queueMessage(i18next.t("abilityTriggers:copyFaintedAllyAbility", { pokemonNameWithAffix: getPokemonNameWithAffix(knockedOut), abilityName: allAbilities[knockedOut.getAbility().id].name })); + globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:copyFaintedAllyAbility", { pokemonNameWithAffix: getPokemonNameWithAffix(knockedOut), abilityName: allAbilities[knockedOut.getAbility().id].name })); } } } @@ -2130,7 +2130,7 @@ export class PostIntimidateStatStageChangeAbAttr extends AbAttr { override apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: BooleanHolder, args: any[]): void { if (!simulated) { - globalScene.pushPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages)); + globalScene.phaseManager.pushPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages)); } cancelled.value = this.overwrites; } @@ -2240,7 +2240,7 @@ export class PostSummonMessageAbAttr extends PostSummonAbAttr { override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { if (!simulated) { - globalScene.queueMessage(this.messageFunc(pokemon)); + globalScene.phaseManager.queueMessage(this.messageFunc(pokemon)); } } } @@ -2257,7 +2257,7 @@ export class PostSummonUnnamedMessageAbAttr extends PostSummonAbAttr { override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { if (!simulated) { - globalScene.queueMessage(this.message); + globalScene.phaseManager.queueMessage(this.message); } } } @@ -2332,7 +2332,7 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { if (this.selfTarget) { // we unshift the StatStageChangePhase to put it right after the showAbility and not at the end of the // phase list (which could be after CommandPhase for example) - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages)); } else { for (const opponent of pokemon.getOpponents()) { const cancelled = new BooleanHolder(false); @@ -2345,7 +2345,7 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { } } if (!cancelled.value) { - globalScene.unshiftPhase(new StatStageChangePhase(opponent.getBattlerIndex(), false, this.stats, this.stages)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(opponent.getBattlerIndex(), false, this.stats, this.stages)); } } } @@ -2370,7 +2370,7 @@ export class PostSummonAllyHealAbAttr extends PostSummonAbAttr { override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { const target = pokemon.getAlly(); if (!simulated && !isNullOrUndefined(target)) { - globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), + globalScene.phaseManager.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), toDmgValue(pokemon.getMaxHp() / this.healRatio), i18next.t("abilityTriggers:postSummonAllyHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(target), pokemonName: pokemon.name }), true, !this.showAnim)); } } @@ -2400,7 +2400,7 @@ export class PostSummonClearAllyStatStagesAbAttr extends PostSummonAbAttr { target.setStatStage(s, 0); } - globalScene.queueMessage(i18next.t("abilityTriggers:postSummonClearAllyStats", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); + globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:postSummonClearAllyStats", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); } } } @@ -2448,7 +2448,7 @@ export class DownloadAbAttr extends PostSummonAbAttr { } if (!simulated) { - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, 1)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, 1)); } } } @@ -2635,7 +2635,7 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt if (!simulated) { for (const pokemon of allowedParty) { if (pokemon.status && this.statusEffect.includes(pokemon.status.effect)) { - globalScene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); + globalScene.phaseManager.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); pokemon.resetStatus(false); pokemon.updateInfo(); } @@ -2725,7 +2725,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { const target = this.getTarget(pokemon.getOpponents()); - globalScene.unshiftPhase(new PokemonTransformPhase(pokemon.getBattlerIndex(), target.getBattlerIndex(), true)); + globalScene.phaseManager.unshiftPhase(new PokemonTransformPhase(pokemon.getBattlerIndex(), target.getBattlerIndex(), true)); } } @@ -2820,7 +2820,7 @@ export class CommanderAbAttr extends AbAttr { // Apply boosts from this effect to the ally Dondozo pokemon.getAlly()?.addTag(BattlerTagType.COMMANDED, 0, MoveId.NONE, pokemon.id); // Cancel the source Pokemon's next move (if a move is queued) - globalScene.tryRemovePhase((phase) => phase.is("MovePhase") && phase.pokemon === pokemon); + globalScene.phaseManager.tryRemovePhase((phase) => phase.is("MovePhase") && phase.pokemon === pokemon); } } } @@ -3076,7 +3076,7 @@ export class ReflectStatStageChangeAbAttr extends PreStatStageChangeAbAttr { const stages = args[1]; this.reflectedStat = stat; if (!simulated) { - globalScene.unshiftPhase(new StatStageChangePhase(attacker.getBattlerIndex(), false, [ stat ], stages, true, false, true, null, true)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(attacker.getBattlerIndex(), false, [ stat ], stages, true, false, true, null, true)); } cancelled.value = true; } @@ -3768,7 +3768,7 @@ export class ForewarnAbAttr extends PostSummonAbAttr { } } if (!simulated) { - globalScene.queueMessage(i18next.t("abilityTriggers:forewarn", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: maxMove })); + globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:forewarn", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: maxMove })); } } } @@ -3781,7 +3781,7 @@ export class FriskAbAttr extends PostSummonAbAttr { override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { if (!simulated) { for (const opponent of pokemon.getOpponents()) { - globalScene.queueMessage(i18next.t("abilityTriggers:frisk", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), opponentName: opponent.name, opponentAbilityName: opponent.getAbility().name })); + globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:frisk", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), opponentName: opponent.name, opponentAbilityName: opponent.getAbility().name })); setAbilityRevealed(opponent); } } @@ -3913,7 +3913,7 @@ export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr { override applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, args: any[]): void { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; if (!simulated) { - globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), + globalScene.phaseManager.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), toDmgValue(pokemon.getMaxHp() / (16 / this.healFactor)), i18next.t("abilityTriggers:postWeatherLapseHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); } } @@ -3935,7 +3935,7 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr { override applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, args: any[]): void { if (!simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - globalScene.queueMessage(i18next.t("abilityTriggers:postWeatherLapseDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName })); + globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:postWeatherLapseDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName })); pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / (16 / this.damageFactor)), { result: HitResult.INDIRECT }); } } @@ -4015,7 +4015,7 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr { override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { if (!simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), + globalScene.phaseManager.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), toDmgValue(pokemon.getMaxHp() / 8), i18next.t("abilityTriggers:poisonHeal", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName }), true)); } } @@ -4047,7 +4047,7 @@ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr { override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { if (!simulated && this.target?.status) { - globalScene.queueMessage(getStatusEffectHealText(this.target.status?.effect, getPokemonNameWithAffix(this.target))); + globalScene.phaseManager.queueMessage(getStatusEffectHealText(this.target.status?.effect, getPokemonNameWithAffix(this.target))); this.target.resetStatus(false); this.target.updateInfo(); } @@ -4132,7 +4132,7 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr { } globalScene.updateModifiers(pokemon.isPlayer()); - globalScene.queueMessage(i18next.t("abilityTriggers:postTurnLootCreateEatenBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: chosenBerry.name })); + globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:postTurnLootCreateEatenBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: chosenBerry.name })); return true; } } @@ -4162,7 +4162,7 @@ export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr { * @param _args - N/A */ override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder | null, _args: any[]): void { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM), ); @@ -4226,11 +4226,11 @@ export class MoodyAbAttr extends PostTurnAbAttr { if (canRaise.length > 0) { const raisedStat = canRaise[pokemon.randBattleSeedInt(canRaise.length)]; canLower = canRaise.filter(s => s !== raisedStat); - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ raisedStat ], 2)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ raisedStat ], 2)); } if (canLower.length > 0) { const loweredStat = canLower[pokemon.randBattleSeedInt(canLower.length)]; - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ loweredStat ], -1)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ loweredStat ], -1)); } } } @@ -4247,7 +4247,7 @@ export class SpeedBoostAbAttr extends PostTurnAbAttr { } override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.SPD ], 1)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.SPD ], 1)); } } @@ -4259,7 +4259,7 @@ export class PostTurnHealAbAttr extends PostTurnAbAttr { override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { if (!simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), + globalScene.phaseManager.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), toDmgValue(pokemon.getMaxHp() / 16), i18next.t("abilityTriggers:postTurnHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); } } @@ -4305,7 +4305,7 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr { if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(AbilityId.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus) { if (!simulated) { opp.damageAndUpdate(toDmgValue(opp.getMaxHp() / 8), { result: HitResult.INDIRECT }); - globalScene.queueMessage(i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) })); + globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) })); } } } @@ -4336,7 +4336,7 @@ export class FetchBallAbAttr extends PostTurnAbAttr { const lastUsed = globalScene.currentBattle.lastUsedPokeball; globalScene.pokeballCounts[lastUsed!]++; globalScene.currentBattle.lastUsedPokeball = null; - globalScene.queueMessage(i18next.t("abilityTriggers:fetchBall", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokeballName: getPokeballName(lastUsed!) })); + globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:fetchBall", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokeballName: getPokeballName(lastUsed!) })); } } @@ -4442,10 +4442,10 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { // If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance if (move.getMove() instanceof AttackMove || move.getMove() instanceof StatusMove) { const target = this.getTarget(dancer, source, targets); - globalScene.unshiftPhase(new MovePhase(dancer, target, move, true, true)); + globalScene.phaseManager.unshiftPhase(new MovePhase(dancer, target, move, true, true)); } else if (move.getMove() instanceof SelfStatusMove) { // If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself - globalScene.unshiftPhase(new MovePhase(dancer, [ dancer.getBattlerIndex() ], move, true, true)); + globalScene.phaseManager.unshiftPhase(new MovePhase(dancer, [ dancer.getBattlerIndex() ], move, true, true)); } } } @@ -4525,7 +4525,7 @@ export class StatStageChangeCopyAbAttr extends AbAttr { args: any[], ): void { if (!simulated) { - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, (args[0] as BattleStat[]), (args[1] as number), true, false, false)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, (args[0] as BattleStat[]), (args[1] as number), true, false, false)); } } } @@ -4606,7 +4606,7 @@ export class HealFromBerryUseAbAttr extends AbAttr { } const { name: abilityName } = passive ? pokemon.getPassiveAbility() : pokemon.getAbility(); - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new PokemonHealPhase( pokemon.getBattlerIndex(), toDmgValue(pokemon.getMaxHp() * this.healPercent), @@ -4739,7 +4739,7 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr { if (globalScene.tryTransferHeldItemModifier(this.randItem, pokemon, true, 1, true, undefined, false)) { postBattleLoot.splice(postBattleLoot.indexOf(this.randItem), 1); - globalScene.queueMessage(i18next.t("abilityTriggers:postBattleLoot", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), itemName: this.randItem.type.name })); + globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:postBattleLoot", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), itemName: this.randItem.type.name })); } this.randItem = undefined; } @@ -4927,7 +4927,7 @@ export class FlinchStatStageChangeAbAttr extends FlinchEffectAbAttr { override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { if (!simulated) { - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages)); } } } @@ -5495,16 +5495,16 @@ function applySingleAbAttrs( continue; } - globalScene.setPhaseQueueSplice(); + globalScene.phaseManager.setPhaseQueueSplice(); if (attr.showAbility && !simulated) { - globalScene.queueAbilityDisplay(pokemon, passive, true); + globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true); abShown = true; } const message = attr.getTriggerMessage(pokemon, ability.name, args); if (message) { if (!simulated) { - globalScene.queueMessage(message); + globalScene.phaseManager.queueMessage(message); } messages.push(message); } @@ -5512,14 +5512,14 @@ function applySingleAbAttrs( applyFunc(attr, passive); if (abShown) { - globalScene.queueAbilityDisplay(pokemon, passive, false); + globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false); } if (!simulated) { pokemon.waveData.abilitiesApplied.add(ability.id); } - globalScene.clearPhaseQueueSplice(); + globalScene.phaseManager.clearPhaseQueueSplice(); } } @@ -5546,7 +5546,7 @@ class ForceSwitchOutHelper { if (switchOutTarget.hp > 0) { switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); - globalScene.prependToPhase(new SwitchPhase(this.switchType, switchOutTarget.getFieldIndex(), true, true), MoveEndPhase); + globalScene.phaseManager.prependToPhase(new SwitchPhase(this.switchType, switchOutTarget.getFieldIndex(), true, true), MoveEndPhase); return true; } /** @@ -5560,7 +5560,7 @@ class ForceSwitchOutHelper { if (switchOutTarget.hp > 0) { switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); const summonIndex = (globalScene.currentBattle.trainer ? globalScene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0); - globalScene.prependToPhase(new SwitchSummonPhase(this.switchType, switchOutTarget.getFieldIndex(), summonIndex, false, false), MoveEndPhase); + globalScene.phaseManager.prependToPhase(new SwitchSummonPhase(this.switchType, switchOutTarget.getFieldIndex(), summonIndex, false, false), MoveEndPhase); return true; } /** @@ -5576,7 +5576,7 @@ class ForceSwitchOutHelper { if (switchOutTarget.hp > 0) { 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); if (globalScene.currentBattle.double && !isNullOrUndefined(allyPokemon)) { globalScene.redirectPokemonMoves(switchOutTarget, allyPokemon); } @@ -5586,13 +5586,13 @@ class ForceSwitchOutHelper { globalScene.clearEnemyHeldItemModifiers(); if (switchOutTarget.hp) { - globalScene.pushPhase(new BattleEndPhase(false)); + globalScene.phaseManager.pushPhase(new BattleEndPhase(false)); if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) { - globalScene.pushPhase(new SelectBiomePhase()); + globalScene.phaseManager.pushPhase(new SelectBiomePhase()); } - globalScene.pushPhase(new NewBattlePhase()); + globalScene.phaseManager.pushPhase(new NewBattlePhase()); } } } @@ -5792,7 +5792,7 @@ function applyAbAttrsInternal( for (const passive of [ false, true ]) { if (pokemon) { applySingleAbAttrs(pokemon, passive, attrType, applyFunc, successFunc, args, gainedMidTurn, simulated, messages); - globalScene.clearPhaseQueueSplice(); + globalScene.phaseManager.clearPhaseQueueSplice(); } } } @@ -6870,7 +6870,7 @@ export function initAbilities() { .ignorable(), new Ability(AbilityId.ANALYTIC, 5) .attr(MovePowerBoostAbAttr, (user, target, move) => { - const movePhase = globalScene.findPhase((phase) => phase.is("MovePhase") && phase.pokemon.id !== user?.id); + const movePhase = globalScene.phaseManager.findPhase((phase) => phase.is("MovePhase") && phase.pokemon.id !== user?.id); return isNullOrUndefined(movePhase); }, 1.3), new Ability(AbilityId.ILLUSION, 5) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 0254aab37e1..70fceb17c49 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -54,7 +54,7 @@ export abstract class ArenaTag { onRemove(_arena: Arena, quiet = false): void { if (!quiet) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t( `arenaTag:arenaOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, { moveName: this.getMoveName() }, @@ -126,7 +126,7 @@ export class MistTag extends ArenaTag { const source = globalScene.getPokemonById(this.sourceId); if (!quiet && source) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("arenaTag:mistOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source), }), @@ -161,7 +161,7 @@ export class MistTag extends ArenaTag { cancelled.value = true; if (!simulated) { - globalScene.queueMessage(i18next.t("arenaTag:mistApply")); + globalScene.phaseManager.queueMessage(i18next.t("arenaTag:mistApply")); } return true; @@ -239,7 +239,7 @@ class ReflectTag extends WeakenMoveScreenTag { onAdd(_arena: Arena, quiet = false): void { if (!quiet) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t( `arenaTag:reflectOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, ), @@ -259,7 +259,7 @@ class LightScreenTag extends WeakenMoveScreenTag { onAdd(_arena: Arena, quiet = false): void { if (!quiet) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t( `arenaTag:lightScreenOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, ), @@ -282,7 +282,7 @@ class AuroraVeilTag extends WeakenMoveScreenTag { onAdd(_arena: Arena, quiet = false): void { if (!quiet) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t( `arenaTag:auroraVeilOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, ), @@ -318,7 +318,7 @@ export class ConditionalProtectTag extends ArenaTag { } onAdd(_arena: Arena): void { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t( `arenaTag:conditionalProtectOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, { moveName: super.getMoveName() }, @@ -355,7 +355,7 @@ export class ConditionalProtectTag extends ArenaTag { isProtected.value = true; if (!simulated) { new CommonBattleAnim(CommonAnim.PROTECT, defender).play(); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("arenaTag:conditionalProtectApply", { moveName: super.getMoveName(), pokemonNameWithAffix: getPokemonNameWithAffix(defender), @@ -381,7 +381,7 @@ export class ConditionalProtectTag extends ArenaTag { */ const QuickGuardConditionFunc: ProtectConditionFunc = (_arena, moveId) => { const move = allMoves[moveId]; - const effectPhase = globalScene.getCurrentPhase(); + const effectPhase = globalScene.phaseManager.getCurrentPhase(); if (effectPhase?.is("MoveEffectPhase")) { const attacker = effectPhase.getUserPokemon(); @@ -458,7 +458,7 @@ class MatBlockTag extends ConditionalProtectTag { if (this.sourceId) { const source = globalScene.getPokemonById(this.sourceId); if (source) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("arenaTag:matBlockOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source), }), @@ -517,7 +517,7 @@ export class NoCritTag extends ArenaTag { /** Queues a message upon adding this effect to the field */ onAdd(_arena: Arena): void { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t(`arenaTag:noCritOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : "Enemy"}`, { moveName: this.getMoveName(), }), @@ -527,7 +527,7 @@ export class NoCritTag extends ArenaTag { /** Queues a message upon removing this effect from the field */ onRemove(_arena: Arena): void { const source = globalScene.getPokemonById(this.sourceId!); // TODO: is this bang correct? - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("arenaTag:noCritOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(source ?? undefined), moveName: this.getMoveName(), @@ -567,8 +567,10 @@ class WishTag extends ArenaTag { onRemove(_arena: Arena): void { const target = globalScene.getField()[this.battlerIndex]; if (target?.isActive(true)) { - globalScene.queueMessage(this.triggerMessage); - globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), this.healHp, null, true, false)); + globalScene.phaseManager.queueMessage(this.triggerMessage); + globalScene.phaseManager.unshiftPhase( + new PokemonHealPhase(target.getBattlerIndex(), this.healHp, null, true, false), + ); } } } @@ -621,11 +623,11 @@ class MudSportTag extends WeakenMoveTypeTag { } onAdd(_arena: Arena): void { - globalScene.queueMessage(i18next.t("arenaTag:mudSportOnAdd")); + globalScene.phaseManager.queueMessage(i18next.t("arenaTag:mudSportOnAdd")); } onRemove(_arena: Arena): void { - globalScene.queueMessage(i18next.t("arenaTag:mudSportOnRemove")); + globalScene.phaseManager.queueMessage(i18next.t("arenaTag:mudSportOnRemove")); } } @@ -639,11 +641,11 @@ class WaterSportTag extends WeakenMoveTypeTag { } onAdd(_arena: Arena): void { - globalScene.queueMessage(i18next.t("arenaTag:waterSportOnAdd")); + globalScene.phaseManager.queueMessage(i18next.t("arenaTag:waterSportOnAdd")); } onRemove(_arena: Arena): void { - globalScene.queueMessage(i18next.t("arenaTag:waterSportOnRemove")); + globalScene.phaseManager.queueMessage(i18next.t("arenaTag:waterSportOnRemove")); } } @@ -659,7 +661,7 @@ export class IonDelugeTag extends ArenaTag { /** Queues an on-add message */ 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 @@ -758,7 +760,7 @@ class SpikesTag extends ArenaTrapTag { const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null; if (!quiet && source) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("arenaTag:spikesOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor(), @@ -781,7 +783,7 @@ class SpikesTag extends ArenaTrapTag { const damageHpRatio = 1 / (10 - 2 * this.layers); const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("arenaTag:spikesActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -811,7 +813,7 @@ class ToxicSpikesTag extends ArenaTrapTag { const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null; if (!quiet && source) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("arenaTag:toxicSpikesOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor(), @@ -834,7 +836,7 @@ class ToxicSpikesTag extends ArenaTrapTag { if (pokemon.isOfType(PokemonType.POISON)) { this.neutralized = true; if (globalScene.arena.removeTag(this.tagType)) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("arenaTag:toxicSpikesActivateTrapPoison", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: this.getMoveName(), @@ -891,7 +893,7 @@ export class DelayedAttackTag extends ArenaTag { const ret = super.lapse(arena); if (!ret) { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new MoveEffectPhase(this.sourceId!, [this.targetIndex], allMoves[this.sourceMove!], false, true), ); // TODO: are those bangs correct? } @@ -917,7 +919,7 @@ class StealthRockTag extends ArenaTrapTag { const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null; if (!quiet && source) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("arenaTag:stealthRockOnAdd", { opponentDesc: source.getOpponentDescriptor(), }), @@ -971,7 +973,7 @@ class StealthRockTag extends ArenaTrapTag { } const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("arenaTag:stealthRockActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -1001,7 +1003,7 @@ class StickyWebTag extends ArenaTrapTag { super.onAdd(arena); const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null; if (!quiet && source) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("arenaTag:stickyWebOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor(), @@ -1020,13 +1022,13 @@ class StickyWebTag extends ArenaTrapTag { } if (!cancelled.value) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender(), }), ); const stages = new NumberHolder(-1); - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new StatStageChangePhase( pokemon.getBattlerIndex(), false, @@ -1074,7 +1076,7 @@ export class TrickRoomTag extends ArenaTag { onAdd(_arena: Arena): void { const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null; if (source) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("arenaTag:trickRoomOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source), }), @@ -1083,7 +1085,7 @@ export class TrickRoomTag extends ArenaTag { } onRemove(_arena: Arena): void { - globalScene.queueMessage(i18next.t("arenaTag:trickRoomOnRemove")); + globalScene.phaseManager.queueMessage(i18next.t("arenaTag:trickRoomOnRemove")); } } @@ -1098,7 +1100,7 @@ export class GravityTag extends ArenaTag { } onAdd(_arena: Arena): void { - globalScene.queueMessage(i18next.t("arenaTag:gravityOnAdd")); + globalScene.phaseManager.queueMessage(i18next.t("arenaTag:gravityOnAdd")); globalScene.getField(true).forEach(pokemon => { if (pokemon !== null) { pokemon.removeTag(BattlerTagType.FLOATING); @@ -1111,7 +1113,7 @@ export class GravityTag extends ArenaTag { } onRemove(_arena: Arena): void { - globalScene.queueMessage(i18next.t("arenaTag:gravityOnRemove")); + globalScene.phaseManager.queueMessage(i18next.t("arenaTag:gravityOnRemove")); } } @@ -1127,7 +1129,7 @@ class TailwindTag extends ArenaTag { onAdd(_arena: Arena, quiet = false): void { if (!quiet) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t( `arenaTag:tailwindOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, ), @@ -1141,7 +1143,7 @@ class TailwindTag extends ArenaTag { // Apply the CHARGED tag to party members with the WIND_POWER ability if (pokemon.hasAbility(AbilityId.WIND_POWER) && !pokemon.getTag(BattlerTagType.CHARGED)) { pokemon.addTag(BattlerTagType.CHARGED); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: this.getMoveName(), @@ -1151,16 +1153,18 @@ class TailwindTag extends ArenaTag { // Raise attack by one stage if party member has WIND_RIDER ability // TODO: Ability displays should be handled by the ability if (pokemon.hasAbility(AbilityId.WIND_RIDER)) { - globalScene.queueAbilityDisplay(pokemon, false, true); - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.ATK], 1, true)); - globalScene.queueAbilityDisplay(pokemon, false, false); + globalScene.phaseManager.queueAbilityDisplay(pokemon, false, true); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.ATK], 1, true), + ); + globalScene.phaseManager.queueAbilityDisplay(pokemon, false, false); } } } onRemove(_arena: Arena, quiet = false): void { if (!quiet) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t( `arenaTag:tailwindOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, ), @@ -1179,11 +1183,11 @@ class HappyHourTag extends ArenaTag { } onAdd(_arena: Arena): void { - globalScene.queueMessage(i18next.t("arenaTag:happyHourOnAdd")); + globalScene.phaseManager.queueMessage(i18next.t("arenaTag:happyHourOnAdd")); } onRemove(_arena: Arena): void { - globalScene.queueMessage(i18next.t("arenaTag:happyHourOnRemove")); + globalScene.phaseManager.queueMessage(i18next.t("arenaTag:happyHourOnRemove")); } } @@ -1193,7 +1197,7 @@ class SafeguardTag extends ArenaTag { } onAdd(_arena: Arena): void { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t( `arenaTag:safeguardOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, ), @@ -1201,7 +1205,7 @@ class SafeguardTag extends ArenaTag { } onRemove(_arena: Arena): void { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t( `arenaTag:safeguardOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, ), @@ -1237,7 +1241,7 @@ class ImprisonTag extends ArenaTrapTag { p.addTag(BattlerTagType.IMPRISON, 1, MoveId.IMPRISON, this.sourceId); } }); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:imprisonOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source), }), @@ -1294,7 +1298,7 @@ class FireGrassPledgeTag extends ArenaTag { override onAdd(_arena: Arena): void { // "A sea of fire enveloped your/the opposing team!" - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t( `arenaTag:fireGrassPledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, ), @@ -1309,13 +1313,13 @@ class FireGrassPledgeTag extends ArenaTag { .filter(pokemon => !pokemon.isOfType(PokemonType.FIRE) && !pokemon.switchOutStatus) .forEach(pokemon => { // "{pokemonNameWithAffix} was hurt by the sea of fire!" - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("arenaTag:fireGrassPledgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), ); // TODO: Replace this with a proper animation - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.MAGMA_STORM), ); pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT }); @@ -1339,7 +1343,7 @@ class WaterFirePledgeTag extends ArenaTag { override onAdd(_arena: Arena): void { // "A rainbow appeared in the sky on your/the opposing team's side!" - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t( `arenaTag:waterFirePledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, ), @@ -1373,7 +1377,7 @@ class GrassWaterPledgeTag extends ArenaTag { override onAdd(_arena: Arena): void { // "A swamp enveloped your/the opposing team!" - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t( `arenaTag:grassWaterPledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, ), @@ -1394,7 +1398,7 @@ export class FairyLockTag extends ArenaTag { } onAdd(_arena: Arena): void { - globalScene.queueMessage(i18next.t("arenaTag:fairyLockOnAdd")); + globalScene.phaseManager.queueMessage(i18next.t("arenaTag:fairyLockOnAdd")); } } @@ -1451,7 +1455,7 @@ export class SuppressAbilitiesTag extends ArenaTag { public override onRemove(_arena: Arena, quiet = false) { this.beingRemoved = true; if (!quiet) { - globalScene.queueMessage(i18next.t("arenaTag:neutralizingGasOnRemove")); + globalScene.phaseManager.queueMessage(i18next.t("arenaTag:neutralizingGasOnRemove")); } for (const pokemon of globalScene.getField(true)) { @@ -1472,7 +1476,7 @@ export class SuppressAbilitiesTag extends ArenaTag { private playActivationMessage(pokemon: Pokemon | null) { if (pokemon) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("arenaTag:neutralizingGasOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 456f519a34c..d0b4620d8eb 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -164,12 +164,12 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag { override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.PRE_MOVE) { // Cancel the affected pokemon's selected move - const phase = globalScene.getCurrentPhase() as MovePhase; + const phase = globalScene.phaseManager.getCurrentPhase() as MovePhase; const move = phase.move; if (this.isMoveRestricted(move.moveId, pokemon)) { if (this.interruptedText(pokemon, move.moveId)) { - globalScene.queueMessage(this.interruptedText(pokemon, move.moveId)); + globalScene.phaseManager.queueMessage(this.interruptedText(pokemon, move.moveId)); } phase.cancel(); } @@ -315,7 +315,7 @@ export class DisabledTag extends MoveRestrictionBattlerTag { this.moveId = move.move; - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:disabledOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[this.moveId].name, @@ -327,7 +327,7 @@ export class DisabledTag extends MoveRestrictionBattlerTag { override onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:disabledLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[this.moveId].name, @@ -456,12 +456,12 @@ export class RechargingTag extends BattlerTag { /** Cancels the source's move this turn and queues a "__ must recharge!" message */ lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.PRE_MOVE) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:rechargingLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), ); - (globalScene.getCurrentPhase() as MovePhase).cancel(); + (globalScene.phaseManager.getCurrentPhase() as MovePhase).cancel(); pokemon.getMoveQueue().shift(); } return super.lapse(pokemon, lapseType); @@ -488,7 +488,7 @@ export class BeakBlastChargingTag extends BattlerTag { new MoveChargeAnim(ChargeAnim.BEAK_BLAST_CHARGING, this.sourceMove, pokemon).play(); // Queue Beak Blast's header message - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("moveTriggers:startedHeatingUpBeak", { pokemonName: getPokemonNameWithAffix(pokemon), }), @@ -533,7 +533,7 @@ export class ShellTrapTag extends BattlerTag { } onAdd(pokemon: Pokemon): void { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("moveTriggers:setUpShellTrap", { pokemonName: getPokemonNameWithAffix(pokemon), }), @@ -552,15 +552,15 @@ export class ShellTrapTag extends BattlerTag { // Trap should only be triggered by opponent's Physical moves if (phaseData?.move.category === MoveCategory.PHYSICAL && pokemon.isOpponent(phaseData.attacker)) { - const shellTrapPhaseIndex = globalScene.phaseQueue.findIndex( + const shellTrapPhaseIndex = globalScene.phaseManager.phaseQueue.findIndex( phase => phase.is("MovePhase") && phase.pokemon === pokemon, ); - const firstMovePhaseIndex = globalScene.phaseQueue.findIndex(phase => phase.is("MovePhase")); + const firstMovePhaseIndex = globalScene.phaseManager.phaseQueue.findIndex(phase => phase.is("MovePhase")); // Only shift MovePhase timing if it's not already next up if (shellTrapPhaseIndex !== -1 && shellTrapPhaseIndex !== firstMovePhaseIndex) { - const shellTrapMovePhase = globalScene.phaseQueue.splice(shellTrapPhaseIndex, 1)[0]; - globalScene.prependToPhase(shellTrapMovePhase, MovePhase); + const shellTrapMovePhase = globalScene.phaseManager.phaseQueue.splice(shellTrapPhaseIndex, 1)[0]; + globalScene.phaseManager.prependToPhase(shellTrapMovePhase, MovePhase); } this.activated = true; @@ -598,13 +598,13 @@ export class TrappedTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - globalScene.queueMessage(this.getTrapMessage(pokemon)); + globalScene.phaseManager.queueMessage(this.getTrapMessage(pokemon)); } onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:trappedOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: this.getMoveName(), @@ -660,8 +660,8 @@ export class FlinchedTag extends BattlerTag { */ lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.PRE_MOVE) { - (globalScene.getCurrentPhase() as MovePhase).cancel(); - globalScene.queueMessage( + (globalScene.phaseManager.getCurrentPhase() as MovePhase).cancel(); + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:flinchedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -699,7 +699,7 @@ export class InterruptedTag extends BattlerTag { } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { - (globalScene.getCurrentPhase() as MovePhase).cancel(); + (globalScene.phaseManager.getCurrentPhase() as MovePhase).cancel(); return super.lapse(pokemon, lapseType); } } @@ -719,8 +719,10 @@ export class ConfusedTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - globalScene.unshiftPhase(new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION)); - globalScene.queueMessage( + globalScene.phaseManager.unshiftPhase( + new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION), + ); + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:confusedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -730,7 +732,7 @@ export class ConfusedTag extends BattlerTag { onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:confusedOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -740,7 +742,7 @@ export class ConfusedTag extends BattlerTag { onOverlap(pokemon: Pokemon): void { super.onOverlap(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:confusedOnOverlap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -754,12 +756,14 @@ export class ConfusedTag extends BattlerTag { return false; } - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:confusedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), ); - globalScene.unshiftPhase(new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION)); + globalScene.phaseManager.unshiftPhase( + new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION), + ); // 1/3 chance of hitting self with a 40 base power move if (pokemon.randBattleSeedInt(3) === 0 || Overrides.CONFUSION_ACTIVATION_OVERRIDE === true) { @@ -769,9 +773,9 @@ export class ConfusedTag extends BattlerTag { ((((2 * pokemon.level) / 5 + 2) * 40 * atk) / def / 50 + 2) * (pokemon.randBattleSeedIntRange(85, 100) / 100), ); // Intentionally don't increment rage fist's hitCount - globalScene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself")); + globalScene.phaseManager.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself")); pokemon.damageAndUpdate(damage, { result: HitResult.CONFUSION }); - (globalScene.getCurrentPhase() as MovePhase).cancel(); + (globalScene.phaseManager.getCurrentPhase() as MovePhase).cancel(); } return true; @@ -815,7 +819,7 @@ export class DestinyBondTag extends BattlerTag { } if (pokemon.isBossImmune()) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:destinyBondLapseIsBoss", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -823,7 +827,7 @@ export class DestinyBondTag extends BattlerTag { return false; } - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:destinyBondLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(source), pokemonNameWithAffix2: getPokemonNameWithAffix(pokemon), @@ -856,7 +860,7 @@ export class InfatuatedTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:infatuatedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), sourcePokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct? @@ -867,7 +871,7 @@ export class InfatuatedTag extends BattlerTag { onOverlap(pokemon: Pokemon): void { super.onOverlap(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:infatuatedOnOverlap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -878,21 +882,23 @@ export class InfatuatedTag extends BattlerTag { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (ret) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:infatuatedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), sourcePokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct? }), ); - globalScene.unshiftPhase(new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT)); + globalScene.phaseManager.unshiftPhase( + new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT), + ); if (pokemon.randBattleSeedInt(2)) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:infatuatedLapseImmobilize", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), ); - (globalScene.getCurrentPhase() as MovePhase).cancel(); + (globalScene.phaseManager.getCurrentPhase() as MovePhase).cancel(); } } @@ -902,7 +908,7 @@ export class InfatuatedTag extends BattlerTag { onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:infatuatedOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -941,7 +947,7 @@ export class SeedTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:seededOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -959,13 +965,13 @@ export class SeedTag extends BattlerTag { applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new CommonAnimPhase(source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED), ); const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT }); const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false); - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new PokemonHealPhase( source.getBattlerIndex(), !reverseDrain ? damage : damage * -1, @@ -1006,7 +1012,7 @@ export class PowderTag extends BattlerTag { super.onAdd(pokemon); // "{Pokemon} is covered in powder!" - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:powderOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -1022,7 +1028,7 @@ export class PowderTag extends BattlerTag { */ lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.PRE_MOVE) { - const movePhase = globalScene.getCurrentPhase(); + const movePhase = globalScene.phaseManager.getCurrentPhase(); if (movePhase?.is("MovePhase")) { const move = movePhase.move.getMove(); const weather = globalScene.arena.weather; @@ -1033,7 +1039,7 @@ export class PowderTag extends BattlerTag { movePhase.fail(); movePhase.showMoveText(); - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.POWDER), ); @@ -1044,7 +1050,7 @@ export class PowderTag extends BattlerTag { } // "When the flame touched the powder\non the Pokémon, it exploded!" - globalScene.queueMessage(i18next.t("battlerTags:powderLapse", { moveName: move.name })); + globalScene.phaseManager.queueMessage(i18next.t("battlerTags:powderLapse", { moveName: move.name })); } } return true; @@ -1061,7 +1067,7 @@ export class NightmareTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:nightmareOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -1071,7 +1077,7 @@ export class NightmareTag extends BattlerTag { onOverlap(pokemon: Pokemon): void { super.onOverlap(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:nightmareOnOverlap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -1082,12 +1088,14 @@ export class NightmareTag extends BattlerTag { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (ret) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:nightmareLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), ); - globalScene.unshiftPhase(new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE)); // TODO: Update animation type + globalScene.phaseManager.unshiftPhase( + new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE), + ); // TODO: Update animation type const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); @@ -1173,18 +1181,18 @@ export class EncoreTag extends MoveRestrictionBattlerTag { onAdd(pokemon: Pokemon): void { super.onRemove(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:encoreOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), ); - const movePhase = globalScene.findPhase(m => m.is("MovePhase") && m.pokemon === pokemon); + const movePhase = globalScene.phaseManager.findPhase(m => m.is("MovePhase") && m.pokemon === pokemon); if (movePhase) { const movesetMove = pokemon.getMoveset().find(m => m.moveId === this.moveId); if (movesetMove) { const lastMove = pokemon.getLastXMoves(1)[0]; - globalScene.tryReplacePhase( + globalScene.phaseManager.tryReplacePhase( m => m.is("MovePhase") && m.pokemon === pokemon, new MovePhase(pokemon, lastMove.targets ?? [], movesetMove), ); @@ -1221,7 +1229,7 @@ export class EncoreTag extends MoveRestrictionBattlerTag { onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:encoreOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -1235,7 +1243,7 @@ export class HelpingHandTag extends BattlerTag { } onAdd(pokemon: Pokemon): void { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:helpingHandOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct? pokemonName: getPokemonNameWithAffix(pokemon), @@ -1268,7 +1276,7 @@ export class IngrainTag extends TrappedTag { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (ret) { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new PokemonHealPhase( pokemon.getBattlerIndex(), toDmgValue(pokemon.getMaxHp() / 16), @@ -1307,7 +1315,9 @@ export class OctolockTag extends TrappedTag { const shouldLapse = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (shouldLapse) { - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, [Stat.DEF, Stat.SPDEF], -1)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase(pokemon.getBattlerIndex(), false, [Stat.DEF, Stat.SPDEF], -1), + ); return true; } @@ -1323,7 +1333,7 @@ export class AquaRingTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:aquaRingOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -1334,7 +1344,7 @@ export class AquaRingTag extends BattlerTag { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (ret) { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new PokemonHealPhase( pokemon.getBattlerIndex(), toDmgValue(pokemon.getMaxHp() / 16), @@ -1382,7 +1392,7 @@ export class DrowsyTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:drowsyOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -1435,13 +1445,13 @@ export abstract class DamagingTrapTag extends TrappedTag { const ret = super.lapse(pokemon, lapseType); if (ret) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:damagingTrapLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: this.getMoveName(), }), ); - globalScene.unshiftPhase(new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, this.commonAnim)); + globalScene.phaseManager.unshiftPhase(new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, this.commonAnim)); const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); @@ -1596,7 +1606,7 @@ export class ProtectedTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:protectedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -1606,14 +1616,14 @@ export class ProtectedTag extends BattlerTag { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.CUSTOM) { new CommonBattleAnim(CommonAnim.PROTECT, pokemon).play(); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:protectedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), ); // Stop multi-hit moves early - const effectPhase = globalScene.getCurrentPhase(); + const effectPhase = globalScene.phaseManager.getCurrentPhase(); if (effectPhase?.is("MoveEffectPhase")) { effectPhase.stopMultiHit(pokemon); } @@ -1754,7 +1764,9 @@ export class ContactStatStageChangeProtectedTag extends DamageProtectedTag { * @param user - The pokemon that is being attacked and has the tag */ override onContact(attacker: Pokemon, _user: Pokemon): void { - globalScene.unshiftPhase(new StatStageChangePhase(attacker.getBattlerIndex(), false, [this.stat], this.levels)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase(attacker.getBattlerIndex(), false, [this.stat], this.levels), + ); } } @@ -1771,7 +1783,7 @@ export class EnduringTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:enduringOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -1780,7 +1792,7 @@ export class EnduringTag extends BattlerTag { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.CUSTOM) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:enduringLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -1799,7 +1811,7 @@ export class SturdyTag extends BattlerTag { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.CUSTOM) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:sturdyLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -1824,7 +1836,7 @@ export class PerishSongTag extends BattlerTag { const ret = super.lapse(pokemon, lapseType); if (ret) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:perishSongLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), turnCount: this.turnCount, @@ -1861,7 +1873,7 @@ export class CenterOfAttentionTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:centerOfAttentionOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -1918,15 +1930,15 @@ export class TruantTag extends AbilityBattlerTag { const lastMove = pokemon.getLastXMoves().find(() => true); if (lastMove && lastMove.move !== MoveId.NONE) { - (globalScene.getCurrentPhase() as MovePhase).cancel(); + (globalScene.phaseManager.getCurrentPhase() as MovePhase).cancel(); // TODO: Ability displays should be handled by the ability - globalScene.queueAbilityDisplay(pokemon, passive, true); - globalScene.queueMessage( + globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true); + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:truantLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), ); - globalScene.queueAbilityDisplay(pokemon, passive, false); + globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false); } return true; @@ -1941,7 +1953,7 @@ export class SlowStartTag extends AbilityBattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:slowStartOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -1959,7 +1971,7 @@ export class SlowStartTag extends AbilityBattlerTag { onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:slowStartOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -2006,7 +2018,7 @@ export class HighestStatBoostTag extends AbilityBattlerTag { this.stat = highestStat; this.multiplier = this.stat === Stat.SPD ? 1.5 : 1.3; - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:highestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: i18next.t(getStatKey(highestStat)), @@ -2021,7 +2033,7 @@ export class HighestStatBoostTag extends AbilityBattlerTag { onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:highestStatBoostOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: allAbilities[this.ability].name, @@ -2119,7 +2131,7 @@ export class FloatingTag extends TypeImmuneTag { super.onAdd(pokemon); if (this.sourceMove === MoveId.MAGNET_RISE) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:magnetRisenOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -2130,7 +2142,7 @@ export class FloatingTag extends TypeImmuneTag { onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); if (this.sourceMove === MoveId.MAGNET_RISE) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:magnetRisenOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -2174,7 +2186,7 @@ export class TypeBoostTag extends BattlerTag { } override onAdd(pokemon: Pokemon): void { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("abilityTriggers:typeImmunityPowerBoost", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: i18next.t(`pokemonInfo:Type.${PokemonType[this.boostedType]}`), @@ -2183,7 +2195,7 @@ export class TypeBoostTag extends BattlerTag { } override onOverlap(pokemon: Pokemon): void { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("abilityTriggers:moveImmunity", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), ); } @@ -2197,7 +2209,7 @@ export class CritBoostTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:critBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -2211,7 +2223,7 @@ export class CritBoostTag extends BattlerTag { onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:critBoostOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -2257,7 +2269,7 @@ export class SaltCuredTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:saltCuredOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -2269,7 +2281,7 @@ export class SaltCuredTag extends BattlerTag { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (ret) { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE), ); @@ -2282,7 +2294,7 @@ export class SaltCuredTag extends BattlerTag { result: HitResult.INDIRECT, }); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:saltCuredLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: this.getMoveName(), @@ -2320,7 +2332,7 @@ export class CursedTag extends BattlerTag { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (ret) { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE), ); @@ -2329,7 +2341,7 @@ export class CursedTag extends BattlerTag { if (!cancelled.value) { pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT }); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:cursedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -2497,7 +2509,7 @@ export class CommandedTag extends BattlerTag { /** Caches the Tatsugiri's form key and sharply boosts the tagged Pokemon's stats */ override onAdd(pokemon: Pokemon): void { this._tatsugiriFormKey = this.getSourcePokemon()?.getFormKey() ?? "curly"; - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new StatStageChangePhase( pokemon.getBattlerIndex(), true, @@ -2572,7 +2584,7 @@ export class StockpilingTag extends BattlerTag { if (this.stockpiledCount < 3) { this.stockpiledCount++; - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:stockpilingOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), stockpiledCount: this.stockpiledCount, @@ -2580,7 +2592,7 @@ export class StockpilingTag extends BattlerTag { ); // Attempt to increase DEF and SPDEF by one stage, keeping track of successful changes. - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new StatStageChangePhase( pokemon.getBattlerIndex(), true, @@ -2608,13 +2620,13 @@ export class StockpilingTag extends BattlerTag { const spDefChange = this.statChangeCounts[Stat.SPDEF]; if (defChange) { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.DEF], -defChange, true, false, true), ); } if (spDefChange) { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.SPDEF], -spDefChange, true, false, true), ); } @@ -2635,7 +2647,7 @@ export class GulpMissileTag extends BattlerTag { return true; } - const moveEffectPhase = globalScene.getCurrentPhase(); + const moveEffectPhase = globalScene.phaseManager.getCurrentPhase(); if (moveEffectPhase?.is("MoveEffectPhase")) { const attacker = moveEffectPhase.getUserPokemon(); @@ -2655,7 +2667,9 @@ export class GulpMissileTag extends BattlerTag { } if (this.tagType === BattlerTagType.GULP_MISSILE_ARROKUDA) { - globalScene.unshiftPhase(new StatStageChangePhase(attacker.getBattlerIndex(), false, [Stat.DEF], -1)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase(attacker.getBattlerIndex(), false, [Stat.DEF], -1), + ); } else { attacker.trySetStatus(StatusEffect.PARALYSIS, true, pokemon); } @@ -2803,7 +2817,7 @@ export class HealBlockTag extends MoveRestrictionBattlerTag { override onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battle:battlerTagsHealBlockOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -2833,7 +2847,7 @@ export class TarShotTag extends BattlerTag { } override onAdd(pokemon: Pokemon): void { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:tarShotOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -2852,7 +2866,7 @@ export class ElectrifiedTag extends BattlerTag { override onAdd(pokemon: Pokemon): void { // "{pokemonNameWithAffix}'s moves have been electrified!" - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:electrifiedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -2878,7 +2892,7 @@ export class AutotomizedTag extends BattlerTag { onAdd(pokemon: Pokemon): void { const minWeight = 0.1; if (pokemon.getWeight() > minWeight) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:autotomizeOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -2924,14 +2938,14 @@ export class SubstituteTag extends BattlerTag { // Queue battle animation and message globalScene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.SUBSTITUTE_ADD); if (this.sourceMove === MoveId.SHED_TAIL) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:shedTailOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), 1500, ); } else { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:substituteOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -2951,7 +2965,7 @@ export class SubstituteTag extends BattlerTag { } else { this.sprite.destroy(); } - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:substituteOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -2987,7 +3001,7 @@ export class SubstituteTag extends BattlerTag { /** If the Substitute redirects damage, queue a message to indicate it. */ onHit(pokemon: Pokemon): void { - const moveEffectPhase = globalScene.getCurrentPhase(); + const moveEffectPhase = globalScene.phaseManager.getCurrentPhase(); if (moveEffectPhase?.is("MoveEffectPhase")) { const attacker = moveEffectPhase.getUserPokemon(); if (!attacker) { @@ -2997,7 +3011,7 @@ export class SubstituteTag extends BattlerTag { const firstHit = attacker.turnData.hitCount === attacker.turnData.hitsLeft; if (firstHit && move.hitsSubstitute(attacker, pokemon)) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:substituteOnHit", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -3074,7 +3088,7 @@ export class TormentTag extends MoveRestrictionBattlerTag { */ override onAdd(pokemon: Pokemon) { super.onAdd(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:tormentOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -3132,7 +3146,7 @@ export class TauntTag extends MoveRestrictionBattlerTag { override onAdd(pokemon: Pokemon) { super.onAdd(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:tauntOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -3143,7 +3157,7 @@ export class TauntTag extends MoveRestrictionBattlerTag { public override onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:tauntOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -3253,7 +3267,7 @@ export class SyrupBombTag extends BattlerTag { */ override onAdd(pokemon: Pokemon) { super.onAdd(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:syrupBombOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -3271,12 +3285,12 @@ export class SyrupBombTag extends BattlerTag { return false; } // Custom message in lieu of an animation in mainline - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:syrupBombLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), ); - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.SPD], -1, true, false, true), ); return --this.turnCount > 0; @@ -3302,7 +3316,7 @@ export class TelekinesisTag extends BattlerTag { } override onAdd(pokemon: Pokemon) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:telekinesisOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -3321,7 +3335,7 @@ export class PowerTrickTag extends BattlerTag { onAdd(pokemon: Pokemon): void { this.swapStat(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:powerTrickActive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -3330,7 +3344,7 @@ export class PowerTrickTag extends BattlerTag { onRemove(pokemon: Pokemon): void { this.swapStat(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:powerTrickActive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -3368,7 +3382,7 @@ export class GrudgeTag extends BattlerTag { onAdd(pokemon: Pokemon) { super.onAdd(pokemon); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:grudgeOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -3389,7 +3403,7 @@ export class GrudgeTag extends BattlerTag { const lastMoveData = sourcePokemon.getMoveset().find(m => m.moveId === lastMove.move); if (lastMoveData && lastMove.move !== MoveId.STRUGGLE) { lastMoveData.ppUsed = lastMoveData.getMovePp(); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:grudgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: lastMoveData.getName(), @@ -3417,7 +3431,9 @@ export class PsychoShiftTag extends BattlerTag { */ override lapse(pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { if (pokemon.status && pokemon.isActive(true)) { - globalScene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); + globalScene.phaseManager.queueMessage( + getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)), + ); pokemon.resetStatus(); pokemon.updateInfo(); } @@ -3439,7 +3455,7 @@ export class MagicCoatTag extends BattlerTag { */ override onAdd(pokemon: Pokemon) { // "{pokemonNameWithAffix} shrouded itself with Magic Coat!" - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:magicCoatOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -3673,7 +3689,7 @@ export function loadBattlerTag(source: BattlerTag | any): BattlerTag { * corresponding {@linkcode Move} and user {@linkcode Pokemon} */ function getMoveEffectPhaseData(_pokemon: Pokemon): { phase: MoveEffectPhase; attacker: Pokemon; move: Move } | null { - const phase = globalScene.getCurrentPhase(); + const phase = globalScene.phaseManager.getCurrentPhase(); if (phase?.is("MoveEffectPhase")) { return { phase: phase, diff --git a/src/data/berry.ts b/src/data/berry.ts index ecc3e92ca64..defc9b85541 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -3,11 +3,7 @@ import type Pokemon from "../field/pokemon"; import { HitResult } from "../field/pokemon"; import { getStatusEffectHealText } from "./status-effect"; import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils/common"; -import { - DoubleBerryEffectAbAttr, - ReduceBerryUseThresholdAbAttr, - applyAbAttrs, -} from "./abilities/ability"; +import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./abilities/ability"; import i18next from "i18next"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; @@ -79,7 +75,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { { const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4)); applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, hpHealed); - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new PokemonHealPhase( consumer.getBattlerIndex(), hpHealed.value, @@ -95,7 +91,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { case BerryType.LUM: { if (consumer.status) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( getStatusEffectHealText(consumer.status.effect, getPokemonNameWithAffix(consumer)), ); } @@ -113,7 +109,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { const stat: BattleStat = berryType - BerryType.ENIGMA; const statStages = new NumberHolder(1); applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, statStages); - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new StatStageChangePhase(consumer.getBattlerIndex(), true, [stat], statStages.value), ); } @@ -130,7 +126,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { const randStat = randSeedInt(Stat.SPD, Stat.ATK); const stages = new NumberHolder(2); applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, stages); - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new StatStageChangePhase(consumer.getBattlerIndex(), true, [randStat], stages.value), ); } @@ -144,7 +140,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { consumer.getMoveset().find(m => m.ppUsed < m.getMovePp()); if (ppRestoreMove) { ppRestoreMove.ppUsed = Math.max(ppRestoreMove.ppUsed - 10, 0); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(consumer), moveName: ppRestoreMove.getName(), diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index f610c745fd2..94d0ae50523 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -1048,7 +1048,7 @@ function ChargeMove(Base: TBase) { * @param target the {@linkcode Pokemon} targeted by this move (optional) */ showChargeText(user: Pokemon, target?: Pokemon): void { - globalScene.queueMessage(this._chargeText + globalScene.phaseManager.queueMessage(this._chargeText .replace("{USER}", getPokemonNameWithAffix(user)) .replace("{TARGET}", getPokemonNameWithAffix(target)) ); @@ -1310,7 +1310,7 @@ export class MessageHeaderAttr extends MoveHeaderAttr { : this.message(user, move); if (message) { - globalScene.queueMessage(message); + globalScene.phaseManager.queueMessage(message); return true; } return false; @@ -1363,7 +1363,7 @@ export class PreMoveMessageAttr extends MoveAttr { ? this.message as string : this.message(user, target, move); if (message) { - globalScene.queueMessage(message, 500); + globalScene.phaseManager.queueMessage(message, 500); return true; } return false; @@ -1620,14 +1620,14 @@ export class SurviveDamageAttr extends ModifiedDamageAttr { export class SplashAttr extends MoveEffectAttr { 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; } } export class CelebrateAttr extends MoveEffectAttr { 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; } } @@ -1677,7 +1677,7 @@ export class RecoilAttr extends MoveEffectAttr { } 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; return true; @@ -1789,7 +1789,7 @@ export class HalfSacrificialAttr extends MoveEffectAttr { applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); if (!cancelled.value) { 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; } @@ -1890,7 +1890,7 @@ export class HealAttr extends MoveEffectAttr { * This heals the target and shows the appropriate message. */ addHealPhase(target: Pokemon, healRatio: number) { - globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), + globalScene.phaseManager.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), toDmgValue(target.getMaxHp() * healRatio), i18next.t("moveTriggers:healHp", { pokemonName: getPokemonNameWithAffix(target) }), true, !this.showAnim)); } @@ -1934,7 +1934,7 @@ export class PartyStatusCureAttr extends MoveEffectAttr { partyPokemon.forEach(p => this.cureStatus(p, user.id)); if (this.message) { - globalScene.queueMessage(this.message); + globalScene.phaseManager.queueMessage(this.message); } return true; @@ -1954,8 +1954,8 @@ export class PartyStatusCureAttr extends MoveEffectAttr { pokemon.updateInfo(); } else { // TODO: Ability displays should be handled by the ability - globalScene.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, true); + globalScene.phaseManager.queueAbilityDisplay(pokemon, pokemon.getPassiveAbility()?.id === this.abilityCondition, false); } } } @@ -2021,7 +2021,7 @@ export class SacrificialFullRestoreAttr extends SacrificialAttr { 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); - globalScene.pushPhase( + globalScene.phaseManager.pushPhase( new PokemonHealPhase( user.getBattlerIndex(), maxPartyMemberHp, @@ -2233,7 +2233,7 @@ export class HitHealAttr extends MoveEffectAttr { message = ""; } } - globalScene.unshiftPhase(new PokemonHealPhase(user.getBattlerIndex(), healAmount, message, false, true)); + globalScene.phaseManager.unshiftPhase(new PokemonHealPhase(user.getBattlerIndex(), healAmount, message, false, true)); return true; } @@ -2565,7 +2565,7 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { 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; } @@ -2643,9 +2643,9 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { globalScene.updateModifiers(target.isPlayer()); 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 { - 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; @@ -2777,7 +2777,7 @@ export class StealEatBerryAttr extends EatBerryAttr { this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)]; applyPostItemLostAbAttrs(PostItemLostAbAttr, target, false); 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.eatBerry(user, target); @@ -2822,7 +2822,7 @@ export class HealStatusEffectAttr extends MoveEffectAttr { const pokemon = this.selfTarget ? user : target; 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.updateInfo(); @@ -3067,13 +3067,13 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr { if (!virtual) { overridden.value = true; - globalScene.unshiftPhase(new MoveAnimPhase(new MoveChargeAnim(this.chargeAnim, move.id, user))); - globalScene.queueMessage(this.chargeText.replace("{TARGET}", getPokemonNameWithAffix(target)).replace("{USER}", getPokemonNameWithAffix(user))); + globalScene.phaseManager.unshiftPhase(new MoveAnimPhase(new MoveChargeAnim(this.chargeAnim, move.id, 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 }); const side = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; globalScene.arena.addTag(this.tagType, 3, move.id, user.id, side, false, target.getBattlerIndex()); } 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; @@ -3103,29 +3103,29 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr { override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (user.turnData.combiningPledge) { // "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; } const overridden = args[0] as BooleanHolder; - const allyMovePhase = globalScene.findPhase((phase) => phase.is("MovePhase") && phase.pokemon.isPlayer() === user.isPlayer()); + const allyMovePhase = globalScene.phaseManager.findPhase((phase) => phase.is("MovePhase") && phase.pokemon.isPlayer() === user.isPlayer()); if (allyMovePhase) { const allyMove = allyMovePhase.move.getMove(); if (allyMove !== move && allyMove.hasAttr(AwaitCombinedPledgeAttr)) { [ user, allyMovePhase.pokemon ].forEach((p) => p.turnData.combiningPledge = move.id); // "{userPokemonName} is waiting for {allyPokemonName}'s move..." - globalScene.queueMessage(i18next.t("moveTriggers:awaitingPledge", { + globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:awaitingPledge", { userPokemonName: getPokemonNameWithAffix(user), allyPokemonName: getPokemonNameWithAffix(allyMovePhase.pokemon) })); // Move the ally's MovePhase (if needed) so that the ally moves next - const allyMovePhaseIndex = globalScene.phaseQueue.indexOf(allyMovePhase); - const firstMovePhaseIndex = globalScene.phaseQueue.findIndex((phase) => phase.is("MovePhase")); + const allyMovePhaseIndex = globalScene.phaseManager.phaseQueue.indexOf(allyMovePhase); + const firstMovePhaseIndex = globalScene.phaseManager.phaseQueue.findIndex((phase) => phase.is("MovePhase")); 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; @@ -3207,7 +3207,7 @@ export class StatStageChangeAttr extends MoveEffectAttr { const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); if (moveChance < 0 || moveChance === 100 || user.randBattleSeedInt(100) < moveChance) { const stages = this.getLevels(user); - globalScene.unshiftPhase(new StatStageChangePhase((this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, stages, this.showMessage)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase((this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, stages, this.showMessage)); return true; } @@ -3432,7 +3432,7 @@ export class AcupressureStatStageChangeAttr extends MoveEffectAttr { const randStats = BATTLE_STATS.filter((s) => target.getStatStage(s) < 6); if (randStats.length > 0) { const boostStat = [ randStats[user.randBattleSeedInt(randStats.length)] ]; - globalScene.unshiftPhase(new StatStageChangePhase(target.getBattlerIndex(), this.selfTarget, boostStat, 2)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(target.getBattlerIndex(), this.selfTarget, boostStat, 2)); return true; } return false; @@ -3510,7 +3510,7 @@ export class OrderUpStatBoostAttr extends MoveEffectAttr { break; } - globalScene.unshiftPhase(new StatStageChangePhase(user.getBattlerIndex(), this.selfTarget, [ increasedStat ], 1)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(user.getBattlerIndex(), this.selfTarget, [ increasedStat ], 1)); return true; } } @@ -3533,7 +3533,7 @@ export class CopyStatsAttr extends MoveEffectAttr { } target.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; } @@ -3552,7 +3552,7 @@ export class InvertStatsAttr extends MoveEffectAttr { target.updateInfo(); user.updateInfo(); - globalScene.queueMessage(i18next.t("moveTriggers:invertStats", { pokemonName: getPokemonNameWithAffix(target) })); + globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:invertStats", { pokemonName: getPokemonNameWithAffix(target) })); return true; } @@ -3570,10 +3570,10 @@ export class ResetStatsAttr extends MoveEffectAttr { // Target all pokemon on the field when Freezy Frost or Haze are used const activePokemon = globalScene.getField(true); 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 this.resetStats(target); - globalScene.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) })); + globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) })); } return true; } @@ -3623,9 +3623,9 @@ export class SwapStatStagesAttr extends MoveEffectAttr { user.updateInfo(); 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) { - globalScene.queueMessage(i18next.t("moveTriggers:switchedTwoStatChanges", { + globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:switchedTwoStatChanges", { pokemonName: getPokemonNameWithAffix(user), firstStat: i18next.t(getStatKey(this.stats[0])), secondStat: i18next.t(getStatKey(this.stats[1])) @@ -4227,7 +4227,7 @@ export class PresentPowerAttr extends VariablePowerAttr { // If this move is multi-hit, disable all other hits user.turnData.hitCount = 1; user.turnData.hitsLeft = 1; - globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), + globalScene.phaseManager.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), toDmgValue(target.getMaxHp() / 4), i18next.t("moveTriggers:regainedHealth", { pokemonName: getPokemonNameWithAffix(target) }), true)); } @@ -4476,7 +4476,7 @@ export class CueNextRoundAttr extends MoveEffectAttr { } override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean { - const nextRoundPhase = globalScene.findPhase(phase => + const nextRoundPhase = globalScene.phaseManager.findPhase(phase => phase.is("MovePhase") && phase.move.moveId === MoveId.ROUND ); @@ -4485,10 +4485,10 @@ export class CueNextRoundAttr extends MoveEffectAttr { } // Update the phase queue so that the next Pokemon using Round moves next - const nextRoundIndex = globalScene.phaseQueue.indexOf(nextRoundPhase); - const nextMoveIndex = globalScene.phaseQueue.findIndex(phase => phase.is("MovePhase")); + const nextRoundIndex = globalScene.phaseManager.phaseQueue.indexOf(nextRoundPhase); + const nextMoveIndex = globalScene.phaseManager.phaseQueue.findIndex(phase => phase.is("MovePhase")); 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) @@ -4546,14 +4546,14 @@ export class SpectralThiefAttr extends StatChangeBeforeDmgCalcAttr { */ const availableToSteal = Math.min(statStageValueTarget, 6 - statStageValueUser); - globalScene.unshiftPhase(new StatStageChangePhase(user.getBattlerIndex(), this.selfTarget, [ s ], availableToSteal)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(user.getBattlerIndex(), this.selfTarget, [ s ], availableToSteal)); target.setStatStage(s, statStageValueTarget - availableToSteal); } } target.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; } @@ -5368,7 +5368,7 @@ const crashDamageFunc = (user: Pokemon, move: Move) => { } 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); return true; @@ -5581,7 +5581,7 @@ export class FallDownAttr extends AddBattlerTagAttr { */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { 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); } @@ -5665,12 +5665,12 @@ export class CurseAttr extends MoveEffectAttr { apply(user: Pokemon, target: Pokemon, move:Move, args: any[]): boolean { if (user.getTypes(true).includes(PokemonType.GHOST)) { if (target.getTag(BattlerTagType.CURSED)) { - globalScene.queueMessage(i18next.t("battle:attackFailed")); + globalScene.phaseManager.queueMessage(i18next.t("battle:attackFailed")); return false; } const curseRecoilDamage = Math.max(1, Math.floor(user.getMaxHp() / 2)); user.damageAndUpdate(curseRecoilDamage, { result: HitResult.INDIRECT, ignoreSegments: true }); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:cursedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user), pokemonName: getPokemonNameWithAffix(target) @@ -5680,8 +5680,8 @@ export class CurseAttr extends MoveEffectAttr { target.addTag(BattlerTagType.CURSED, 0, move.id, user.id); return true; } else { - globalScene.unshiftPhase(new StatStageChangePhase(user.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF ], 1)); - globalScene.unshiftPhase(new StatStageChangePhase(user.getBattlerIndex(), true, [ Stat.SPD ], -1)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(user.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF ], 1)); + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(user.getBattlerIndex(), true, [ Stat.SPD ], -1)); return true; } } @@ -5745,7 +5745,7 @@ export class ConfuseAttr extends AddBattlerTagAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!this.selfTarget && target.isSafeguarded(user)) { 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; } @@ -5802,7 +5802,7 @@ export class IgnoreAccuracyAttr extends AddBattlerTagAttr { 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; } @@ -5818,7 +5818,7 @@ export class FaintCountdownAttr extends AddBattlerTagAttr { 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; } @@ -6102,7 +6102,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; } } @@ -6154,7 +6154,7 @@ export class RevivalBlessingAttr extends MoveEffectAttr { override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { // If user is player, checks if the user has fainted pokemon if (user.isPlayer()) { - globalScene.unshiftPhase(new RevivalBlessingPhase(user)); + globalScene.phaseManager.unshiftPhase(new RevivalBlessingPhase(user)); return true; } 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 @@ -6164,20 +6164,20 @@ export class RevivalBlessingAttr extends MoveEffectAttr { const slotIndex = globalScene.getEnemyParty().findIndex((p) => pokemon.id === p.id); pokemon.resetStatus(true, false, false, true); 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(); if (globalScene.currentBattle.double && globalScene.getEnemyParty().length > 1 && !isNullOrUndefined(allyPokemon)) { // Handle cases where revived pokemon needs to get switched in on same turn if (allyPokemon.isFainted() || allyPokemon === pokemon) { // Enemy switch phase should be removed and replaced with the revived pkmn switching in - globalScene.tryRemovePhase((phase: SwitchSummonPhase) => phase.is("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 // (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) { user.setFieldPosition(FieldPosition.LEFT); } - globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, false)); + globalScene.phaseManager.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, false)); } } return true; @@ -6255,7 +6255,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { if (this.switchType === SwitchType.FORCE_SWITCH) { switchOutTarget.leaveField(true); const slotIndex = eligibleNewIndices[user.randBattleSeedInt(eligibleNewIndices.length)]; - globalScene.prependToPhase( + globalScene.phaseManager.prependToPhase( new SwitchSummonPhase( this.switchType, switchOutTarget.getFieldIndex(), @@ -6267,7 +6267,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { ); } else { switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); - globalScene.prependToPhase( + globalScene.phaseManager.prependToPhase( new SwitchPhase( this.switchType, switchOutTarget.getFieldIndex(), @@ -6298,7 +6298,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { if (this.switchType === SwitchType.FORCE_SWITCH) { switchOutTarget.leaveField(true); const slotIndex = eligibleNewIndices[user.randBattleSeedInt(eligibleNewIndices.length)]; - globalScene.prependToPhase( + globalScene.phaseManager.prependToPhase( new SwitchSummonPhase( this.switchType, switchOutTarget.getFieldIndex(), @@ -6310,7 +6310,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { ); } else { switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); - globalScene.prependToPhase( + globalScene.phaseManager.prependToPhase( new SwitchSummonPhase( this.switchType, switchOutTarget.getFieldIndex(), @@ -6339,7 +6339,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { if (switchOutTarget.hp > 0) { 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 if (globalScene.currentBattle.double && !isNullOrUndefined(allyPokemon)) { @@ -6351,13 +6351,13 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { globalScene.clearEnemyHeldItemModifiers(switchOutTarget); if (!allyPokemon?.isActive(true) && switchOutTarget.hp) { - globalScene.pushPhase(new BattleEndPhase(false)); + globalScene.phaseManager.pushPhase(new BattleEndPhase(false)); if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) { - globalScene.pushPhase(new SelectBiomePhase()); + globalScene.phaseManager.pushPhase(new SelectBiomePhase()); } - globalScene.pushPhase(new NewBattlePhase()); + globalScene.phaseManager.pushPhase(new NewBattlePhase()); } } @@ -6525,7 +6525,7 @@ export class CopyTypeAttr extends MoveEffectAttr { user.summonData.types = targetTypes; 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; } @@ -6556,7 +6556,7 @@ export class CopyBiomeTypeAttr extends MoveEffectAttr { user.summonData.types = [ typeChange ]; 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; } @@ -6661,7 +6661,7 @@ export class ChangeTypeAttr extends MoveEffectAttr { target.summonData.types = [ this.type ]; 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; } @@ -6684,7 +6684,7 @@ export class AddTypeAttr extends MoveEffectAttr { target.summonData.addedType = this.type; 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; } @@ -6706,7 +6706,7 @@ export class FirstMoveTypeAttr extends MoveEffectAttr { const firstMoveType = target.getMoveset()[0].getMove().type; 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; } @@ -6725,7 +6725,7 @@ class CallMoveAttr extends OverrideMoveEffectAttr { const replaceMoveTarget = move.moveTarget === MoveTarget.NEAR_OTHER ? MoveTarget.NEAR_ENEMY : undefined; const moveTargets = getMoveTargets(user, move.id, replaceMoveTarget); 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."); return false; } @@ -6733,8 +6733,8 @@ class CallMoveAttr extends OverrideMoveEffectAttr { ? moveTargets.targets : [ 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 }); - globalScene.unshiftPhase(new LoadMoveAnimPhase(move.id)); - globalScene.unshiftPhase(new MovePhase(user, targets, new PokemonMove(move.id, 0, 0, true), true, true)); + globalScene.phaseManager.unshiftPhase(new LoadMoveAnimPhase(move.id)); + globalScene.phaseManager.unshiftPhase(new MovePhase(user, targets, new PokemonMove(move.id, 0, 0, true), true, true)); return true; } } @@ -6962,8 +6962,8 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr { } user.getMoveQueue().push({ move: moveId, targets: [ target.getBattlerIndex() ], ignorePP: true }); - globalScene.unshiftPhase(new LoadMoveAnimPhase(moveId)); - globalScene.unshiftPhase(new MovePhase(user, [ target.getBattlerIndex() ], new PokemonMove(moveId, 0, 0, true), true)); + globalScene.phaseManager.unshiftPhase(new LoadMoveAnimPhase(moveId)); + globalScene.phaseManager.unshiftPhase(new MovePhase(user, [ target.getBattlerIndex() ], new PokemonMove(moveId, 0, 0, true), true)); return true; } } @@ -7044,13 +7044,13 @@ export class RepeatMoveAttr extends MoveEffectAttr { } } - globalScene.queueMessage(i18next.t("moveTriggers:instructingMove", { + globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:instructingMove", { userPokemonName: getPokemonNameWithAffix(user), targetPokemonName: getPokemonNameWithAffix(target) })); target.getMoveQueue().unshift({ move: lastMove.move, targets: moveTargets, ignorePP: false }); target.turnData.extraTurns++; - globalScene.appendToPhase(new MovePhase(target, moveTargets, movesetMove), MoveEndPhase); + globalScene.phaseManager.appendToPhase(new MovePhase(target, moveTargets, movesetMove), MoveEndPhase); return true; } @@ -7165,7 +7165,7 @@ export class ReducePpMoveAttr extends MoveEffectAttr { 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.queueMessage(message); + globalScene.phaseManager.queueMessage(message); return true; } @@ -7276,7 +7276,7 @@ export class MovesetCopyMoveAttr extends OverrideMoveEffectAttr { user.summonData.moveset = user.getMoveset().slice(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; } @@ -7326,7 +7326,7 @@ export class SketchAttr extends MoveEffectAttr { 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; } @@ -7381,9 +7381,9 @@ export class AbilityChangeAttr extends MoveEffectAttr { globalScene.triggerPokemonFormChange(moveTarget, SpeciesFormChangeRevertWeatherFormTrigger); 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]); globalScene.triggerPokemonFormChange(moveTarget, SpeciesFormChangeRevertWeatherFormTrigger); return true; @@ -7408,13 +7408,13 @@ export class AbilityCopyAttr extends MoveEffectAttr { 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()); 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? - 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()); } @@ -7447,7 +7447,7 @@ export class AbilityGiveAttr extends MoveEffectAttr { 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()); @@ -7467,7 +7467,7 @@ export class SwitchAbilitiesAttr extends MoveEffectAttr { 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()); target.setTempAbility(tempAbility); @@ -7497,7 +7497,7 @@ export class SuppressAbilitiesAttr extends MoveEffectAttr { return false; } - globalScene.queueMessage(i18next.t("moveTriggers:suppressAbilities", { pokemonName: getPokemonNameWithAffix(target) })); + globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:suppressAbilities", { pokemonName: getPokemonNameWithAffix(target) })); target.suppressAbility(); @@ -7550,9 +7550,9 @@ export class TransformAttr extends MoveEffectAttr { return false; } - globalScene.unshiftPhase(new PokemonTransformPhase(user.getBattlerIndex(), target.getBattlerIndex())); + globalScene.phaseManager.unshiftPhase(new 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; } @@ -7589,7 +7589,7 @@ export class SwapStatAttr extends MoveEffectAttr { user.setStat(this.stat, target.getStat(this.stat, false), false); target.setStat(this.stat, temp, false); - globalScene.queueMessage(i18next.t("moveTriggers:switchedStat", { + globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:switchedStat", { pokemonName: getPokemonNameWithAffix(user), stat: i18next.t(getStatKey(this.stat)), })); @@ -7635,7 +7635,7 @@ export class ShiftStatAttr extends MoveEffectAttr { user.setStat(this.statToSwitch, secondStat, false); user.setStat(this.statToSwitchWith, firstStat, false); - globalScene.queueMessage(i18next.t("moveTriggers:shiftedStats", { + globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:shiftedStats", { pokemonName: getPokemonNameWithAffix(user), statToSwitch: i18next.t(getStatKey(this.statToSwitch)), statToSwitchWith: i18next.t(getStatKey(this.statToSwitchWith)) @@ -7694,7 +7694,7 @@ export class AverageStatsAttr extends MoveEffectAttr { 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; } @@ -7709,7 +7709,7 @@ export class MoneyAttr extends MoveEffectAttr { apply(user: Pokemon, target: Pokemon, move: Move): boolean { globalScene.currentBattle.moneyScattered += globalScene.getWaveMoneyAmount(0.2); - globalScene.queueMessage(i18next.t("moveTriggers:coinsScatteredEverywhere")); + globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:coinsScatteredEverywhere")); return true; } } @@ -7733,7 +7733,7 @@ export class DestinyBondAttr extends MoveEffectAttr { * @returns true */ 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); return true; } @@ -7847,12 +7847,12 @@ export class AfterYouAttr extends MoveEffectAttr { * @returns true */ 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. - const nextAttackPhase = globalScene.findPhase((phase) => phase.pokemon === target); - if (nextAttackPhase && globalScene.tryRemovePhase((phase: MovePhase) => phase.pokemon === target)) { - globalScene.prependToPhase(new MovePhase(target, [ ...nextAttackPhase.targets ], nextAttackPhase.move), MovePhase); + const nextAttackPhase = globalScene.phaseManager.findPhase((phase) => phase.pokemon === target); + if (nextAttackPhase && globalScene.phaseManager.tryRemovePhase((phase: MovePhase) => phase.pokemon === target)) { + globalScene.phaseManager.prependToPhase(new MovePhase(target, [ ...nextAttackPhase.targets ], nextAttackPhase.move), MovePhase); } return true; @@ -7875,19 +7875,19 @@ export class ForceLastAttr extends MoveEffectAttr { * @returns true */ 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((phase) => phase.pokemon === target); - if (targetMovePhase && !targetMovePhase.isForcedLast() && globalScene.tryRemovePhase((phase: MovePhase) => phase.pokemon === target)) { + const targetMovePhase = globalScene.phaseManager.findPhase((phase) => 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 - // 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)) || (phase.is("MovePhase")) && phaseForcedSlower(phase, target, !!globalScene.arena.getTag(ArenaTagType.TRICK_ROOM)) ); if (prependPhase) { - globalScene.phaseQueue.splice( - globalScene.phaseQueue.indexOf(prependPhase), + globalScene.phaseManager.phaseQueue.splice( + globalScene.phaseManager.phaseQueue.indexOf(prependPhase), 0, new MovePhase(target, [ ...targetMovePhase.targets ], targetMovePhase.move, false, false, false, true) ); @@ -7920,7 +7920,7 @@ const failIfDampCondition: MoveConditionFunc = (user, target, move) => { globalScene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled)); // Queue a message if an ability prevented usage of the move 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; }; @@ -7929,7 +7929,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 failIfLastCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => globalScene.phaseQueue.find(phase => phase.is("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 party: Pokemon[] = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); @@ -8107,7 +8107,7 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr { } const type = validTypes[user.randBattleSeedInt(validTypes.length)]; 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(); return true; @@ -8166,7 +8166,7 @@ export class ExposedMoveAttr extends AddBattlerTagAttr { 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; } @@ -8802,7 +8802,7 @@ export function initMoves() { .reflectable(), new SelfStatusMove(MoveId.BELLY_DRUM, PokemonType.NORMAL, -1, 10, -1, 0, 2) .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) .attr(StatusEffectAttr, StatusEffect.POISON) @@ -10370,7 +10370,7 @@ export function initMoves() { .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) .attr(AddBattlerTagAttr, BattlerTagType.BURNED_UP, true, false) .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) .attr(SwapStatAttr, Stat.SPD) @@ -11154,7 +11154,7 @@ export function initMoves() { }) .attr(AddBattlerTagAttr, BattlerTagType.DOUBLE_SHOCKED, true, false) .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) .makesContact(false) diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index ae5cd2b2a99..5f69090064f 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -182,7 +182,7 @@ export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder. async () => { const encounter = globalScene.currentBattle.mysteryEncounter!; // Full heal party - globalScene.unshiftPhase(new PartyHealPhase(true)); + globalScene.phaseManager.unshiftPhase(new PartyHealPhase(true)); const eggOptions: IEggOptions = { pulled: false, diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index b14caa4e2c3..e23b5024599 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -237,7 +237,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { queueEncounterMessage(`${namespace}:option.1.boss_enraged`); - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1), ); }, diff --git a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts index 2f4dfaa5f99..87cd8f207c4 100644 --- a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts +++ b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts @@ -137,7 +137,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterB }) .withOptionPhase(async () => { // Give the player a Shiny Charm - globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.SHINY_CHARM)); + globalScene.phaseManager.unshiftPhase(new ModifierRewardPhase(modifierTypes.SHINY_CHARM)); leaveEncounterWithoutBattle(true); }) .build(), diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index 7f54e51565e..3de16c7086e 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -237,7 +237,7 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder. config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON]; config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => { queueEncounterMessage(`${namespace}:option.2.boss_enraged`); - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1), ); }; diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index c080122f922..cd8289163eb 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -766,7 +766,7 @@ function doBugTypeMoveTutor(): Promise { // Option select complete, handle if they are learning a move if (result && result.selectedOptionIndex < moveOptions.length) { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new LearnMovePhase(result.selectedPokemonIndex, moveOptions[result.selectedOptionIndex].moveId), ); } diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index c7f9a99569d..66cd1ae0509 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -176,7 +176,7 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { queueEncounterMessage(`${namespace}:option.1.boss_enraged`); - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new StatStageChangePhase( pokemon.getBattlerIndex(), true, @@ -245,7 +245,7 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder const onPokemonSelected = (pokemon: PlayerPokemon) => { encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender()); - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new LearnMovePhase(globalScene.getPlayerParty().indexOf(pokemon), MoveId.REVELATION_DANCE), ); diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index c54cb232087..1385e9c5a75 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -165,7 +165,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE .withOptionPhase(async () => { // Give the player 5 Rogue Balls const encounter = globalScene.currentBattle.mysteryEncounter!; - globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.ROGUE_BALL)); + globalScene.phaseManager.unshiftPhase(new ModifierRewardPhase(modifierTypes.ROGUE_BALL)); // 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 diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index 8d3d30bcd66..c321b5f9674 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -65,10 +65,10 @@ const doEventReward = () => { return !(existingCharm && existingCharm.getStackCount() >= existingCharm.getMaxStackCount()); }); if (candidates.length > 0) { - globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes[randSeedItem(candidates)])); + globalScene.phaseManager.unshiftPhase(new ModifierRewardPhase(modifierTypes[randSeedItem(candidates)])); } else { // At max stacks, give a Voucher instead - globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.VOUCHER)); + globalScene.phaseManager.unshiftPhase(new ModifierRewardPhase(modifierTypes.VOUCHER)); } } }; @@ -181,7 +181,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with ); doEventReward(); } else { - globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.AMULET_COIN)); + globalScene.phaseManager.unshiftPhase(new ModifierRewardPhase(modifierTypes.AMULET_COIN)); doEventReward(); } @@ -266,7 +266,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with ); doEventReward(); } else { - globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.CANDY_JAR)); + globalScene.phaseManager.unshiftPhase(new ModifierRewardPhase(modifierTypes.CANDY_JAR)); doEventReward(); } } else { @@ -288,7 +288,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with ); doEventReward(); } else { - globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.BERRY_POUCH)); + globalScene.phaseManager.unshiftPhase(new ModifierRewardPhase(modifierTypes.BERRY_POUCH)); doEventReward(); } } @@ -372,7 +372,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with ); doEventReward(); } else { - globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.HEALING_CHARM)); + globalScene.phaseManager.unshiftPhase(new ModifierRewardPhase(modifierTypes.HEALING_CHARM)); doEventReward(); } diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 872e6300a29..ced04ce224d 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -92,7 +92,7 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w gender: Gender.MALE, tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.SPDEF, Stat.SPD], 1), ); }, @@ -103,7 +103,7 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w gender: Gender.FEMALE, tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.SPDEF, Stat.SPD], 1), ); }, diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index ecc2e17a06f..6df8ca8b167 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -76,7 +76,9 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder. queueEncounterMessage(`${namespace}:option.1.stat_boost`); // Randomly boost 1 stat 2 stages // Cannot boost Spd, Acc, or Evasion - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [randSeedInt(4, 1)], 2)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase(pokemon.getBattlerIndex(), true, [randSeedInt(4, 1)], 2), + ); }, }, ], diff --git a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts index 7694f62eac4..42bfe76d98e 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -411,13 +411,13 @@ function summonPlayerPokemonAnimation(pokemon: PlayerPokemon): Promise { pokemon.resetSummonData(); globalScene.time.delayedCall(1000, () => { if (pokemon.isShiny()) { - globalScene.unshiftPhase(new ShinySparklePhase(pokemon.getBattlerIndex())); + globalScene.phaseManager.unshiftPhase(new ShinySparklePhase(pokemon.getBattlerIndex())); } pokemon.resetTurnData(); globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); - globalScene.pushPhase(new PostSummonPhase(pokemon.getBattlerIndex())); + globalScene.phaseManager.pushPhase(new PostSummonPhase(pokemon.getBattlerIndex())); resolve(); }); }, diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index 9f699f7d045..42167d240f9 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -189,8 +189,8 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde const allowedPokemon = globalScene.getPokemonAllowedInBattle(); if (allowedPokemon.length === 0) { // If there are no longer any legal pokemon in the party, game over. - globalScene.clearPhaseQueue(); - globalScene.unshiftPhase(new GameOverPhase()); + globalScene.phaseManager.clearPhaseQueue(); + globalScene.phaseManager.unshiftPhase(new GameOverPhase()); } else { // Show which Pokemon was KOed, then start battle against Gimmighoul await transitionMysteryEncounterIntroVisuals(true, true, 500); diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index 7a12c86edff..54e15dcb102 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -276,7 +276,7 @@ async function summonSafariPokemon() { const encounter = globalScene.currentBattle.mysteryEncounter!; // Message pokemon remaining 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 // Safari pokemon roll twice on shiny and HA chances, but are otherwise normal @@ -325,7 +325,7 @@ async function summonSafariPokemon() { encounter.misc.pokemon = pokemon; encounter.misc.safariPokemonRemaining -= 1; - globalScene.unshiftPhase(new SummonPhase(0, false)); + globalScene.phaseManager.unshiftPhase(new SummonPhase(0, false)); encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon)); @@ -336,7 +336,7 @@ async function summonSafariPokemon() { const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier); if (ivScannerModifier) { - globalScene.pushPhase(new ScanIvsPhase(pokemon.getBattlerIndex())); + globalScene.phaseManager.pushPhase(new ScanIvsPhase(pokemon.getBattlerIndex())); } } @@ -559,7 +559,7 @@ async function doEndTurn(cursorIndex: number) { leaveEncounterWithoutBattle(true); } } else { - globalScene.queueMessage(getEncounterText(`${namespace}:safari.watching`) ?? "", 0, null, 1000); + globalScene.phaseManager.queueMessage(getEncounterText(`${namespace}:safari.watching`) ?? "", 0, null, 1000); initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index 196d27c3f30..a0898f7cfec 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -155,7 +155,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil async () => { // Fall asleep waiting for Snorlax // Full heal party - globalScene.unshiftPhase(new PartyHealPhase(true)); + globalScene.phaseManager.unshiftPhase(new PartyHealPhase(true)); queueEncounterMessage(`${namespace}:option.2.rest_result`); leaveEncounterWithoutBattle(); }, diff --git a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts index f6bf5575120..b101837adb9 100644 --- a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts +++ b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts @@ -227,7 +227,9 @@ async function doBiomeTransitionDialogueAndBattleInit() { tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { queueEncounterMessage(`${namespace}:boss_enraged`); - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1), + ); }, }, ], diff --git a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts index 5c3a4dd1a81..fdfcc8f2f13 100644 --- a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts @@ -658,8 +658,8 @@ function onGameOver() { globalScene.playBgm(globalScene.arena.bgm); // Clear any leftover battle phases - globalScene.clearPhaseQueue(); - globalScene.clearPhaseQueueSplice(); + globalScene.phaseManager.clearPhaseQueue(); + globalScene.phaseManager.clearPhaseQueueSplice(); // Return enemy Pokemon const pokemon = globalScene.getEnemyPokemon(); diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index 33f43617913..e2b6d62745b 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -116,7 +116,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { queueEncounterMessage(`${namespace}:option.2.stat_boost`); - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.DEF, Stat.SPDEF], 1), ); }, diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index 0776d89ed63..05e036e9189 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -143,7 +143,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = MysteryEncounter }, async () => { // Refuse the challenge, they full heal the party and give the player a Rarer Candy - globalScene.unshiftPhase(new PartyHealPhase(true)); + globalScene.phaseManager.unshiftPhase(new PartyHealPhase(true)); setEncounterRewards({ guaranteedModifierTypeFuncs: [modifierTypes.RARER_CANDY], fillRemaining: false, @@ -209,7 +209,7 @@ function endTrainerBattleAndShowDialogue(): Promise { for (const pokemon of playerField) { pokemon.lapseTag(BattlerTagType.COMMANDED); } - playerField.forEach((_, p) => globalScene.unshiftPhase(new ReturnPhase(p))); + playerField.forEach((_, p) => globalScene.phaseManager.unshiftPhase(new ReturnPhase(p))); for (const pokemon of globalScene.getPlayerParty()) { // Only trigger form change when Eiscue is in Noice form @@ -227,7 +227,7 @@ function endTrainerBattleAndShowDialogue(): Promise { applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon); } - globalScene.unshiftPhase(new ShowTrainerPhase()); + globalScene.phaseManager.unshiftPhase(new ShowTrainerPhase()); // Hide the trainer and init next battle const trainer = globalScene.currentBattle.trainer; // Unassign previous trainer from battle so it isn't destroyed before animation completes diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index e51a8554120..e917574a065 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -103,7 +103,7 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder. tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { queueEncounterMessage(`${namespace}:option.1.stat_boost`); - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1), ); }, diff --git a/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts b/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts index 296d94093d9..e1055f57496 100644 --- a/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts @@ -51,7 +51,7 @@ function getTextWithDialogueTokens(keyOrString: string): string | null { */ export function queueEncounterMessage(contentKey: string): void { const text: string | null = getEncounterText(contentKey); - globalScene.queueMessage(text ?? "", null, true); + globalScene.phaseManager.queueMessage(text ?? "", null, true); } /** diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 5736835d98a..c566ed68476 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -428,7 +428,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): console.log("Moveset:", moveset); }); - globalScene.pushPhase(new MysteryEncounterBattlePhase(partyConfig.disableSwitch)); + globalScene.phaseManager.pushPhase(new MysteryEncounterBattlePhase(partyConfig.disableSwitch)); await Promise.all(loadEnemyAssets); battle.enemyParty.forEach((enemyPokemon_2, e_1) => { @@ -480,7 +480,7 @@ export function updatePlayerMoney(changeValue: number, playSound = true, showMes } if (showMessage) { if (changeValue < 0) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("mysteryEncounterMessages:paid_money", { amount: -changeValue, }), @@ -488,7 +488,7 @@ export function updatePlayerMoney(changeValue: number, playSound = true, showMes true, ); } else { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("mysteryEncounterMessages:receive_money", { amount: changeValue, }), @@ -767,9 +767,9 @@ export function setEncounterRewards( } if (customShopRewards) { - globalScene.unshiftPhase(new SelectModifierPhase(0, undefined, customShopRewards)); + globalScene.phaseManager.unshiftPhase(new SelectModifierPhase(0, undefined, customShopRewards)); } else { - globalScene.tryRemovePhase(p => p.is("MysteryEncounterRewardsPhase")); + globalScene.phaseManager.tryRemovePhase(p => p.is("MysteryEncounterRewardsPhase")); } if (eggRewards) { @@ -807,7 +807,7 @@ export function setEncounterExp(participantId: number | number[], baseExpValue: const participantIds = Array.isArray(participantId) ? participantId : [participantId]; globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => { - globalScene.unshiftPhase(new PartyExpPhase(baseExpValue, useWaveIndex, new Set(participantIds))); + globalScene.phaseManager.unshiftPhase(new PartyExpPhase(baseExpValue, useWaveIndex, new Set(participantIds))); return true; }; @@ -829,7 +829,7 @@ export class OptionSelectSettings { * @param optionSelectSettings */ export function initSubsequentOptionSelect(optionSelectSettings: OptionSelectSettings) { - globalScene.pushPhase(new MysteryEncounterPhase(optionSelectSettings)); + globalScene.phaseManager.pushPhase(new MysteryEncounterPhase(optionSelectSettings)); } /** @@ -843,8 +843,8 @@ export function leaveEncounterWithoutBattle( encounterMode: MysteryEncounterMode = MysteryEncounterMode.NO_BATTLE, ) { globalScene.currentBattle.mysteryEncounter!.encounterMode = encounterMode; - globalScene.clearPhaseQueue(); - globalScene.clearPhaseQueueSplice(); + globalScene.phaseManager.clearPhaseQueue(); + globalScene.phaseManager.clearPhaseQueueSplice(); handleMysteryEncounterVictory(addHealPhase); } @@ -857,8 +857,8 @@ export function handleMysteryEncounterVictory(addHealPhase = false, doNotContinu const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle()); if (allowedPkm.length === 0) { - globalScene.clearPhaseQueue(); - globalScene.unshiftPhase(new GameOverPhase()); + globalScene.phaseManager.clearPhaseQueue(); + globalScene.phaseManager.unshiftPhase(new GameOverPhase()); return; } @@ -869,8 +869,8 @@ export function handleMysteryEncounterVictory(addHealPhase = false, doNotContinu return; } if (encounter.encounterMode === MysteryEncounterMode.NO_BATTLE) { - globalScene.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase)); - globalScene.pushPhase(new EggLapsePhase()); + globalScene.phaseManager.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase)); + globalScene.phaseManager.pushPhase(new EggLapsePhase()); } else if ( !globalScene .getEnemyParty() @@ -878,15 +878,15 @@ export function handleMysteryEncounterVictory(addHealPhase = false, doNotContinu encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true), ) ) { - globalScene.pushPhase(new BattleEndPhase(true)); + globalScene.phaseManager.pushPhase(new BattleEndPhase(true)); if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) { - globalScene.pushPhase(new TrainerVictoryPhase()); + globalScene.phaseManager.pushPhase(new TrainerVictoryPhase()); } if (globalScene.gameMode.isEndless || !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) { - globalScene.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase)); + globalScene.phaseManager.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase)); if (!encounter.doContinueEncounter) { // Only lapse eggs once for multi-battle encounters - globalScene.pushPhase(new EggLapsePhase()); + globalScene.phaseManager.pushPhase(new EggLapsePhase()); } } } @@ -900,8 +900,8 @@ export function handleMysteryEncounterBattleFailed(addHealPhase = false, doNotCo const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle()); if (allowedPkm.length === 0) { - globalScene.clearPhaseQueue(); - globalScene.unshiftPhase(new GameOverPhase()); + globalScene.phaseManager.clearPhaseQueue(); + globalScene.phaseManager.unshiftPhase(new GameOverPhase()); return; } @@ -912,14 +912,14 @@ export function handleMysteryEncounterBattleFailed(addHealPhase = false, doNotCo return; } if (encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE) { - globalScene.pushPhase(new BattleEndPhase(false)); + globalScene.phaseManager.pushPhase(new BattleEndPhase(false)); } - globalScene.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase)); + globalScene.phaseManager.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase)); if (!encounter.doContinueEncounter) { // Only lapse eggs once for multi-battle encounters - globalScene.pushPhase(new EggLapsePhase()); + globalScene.phaseManager.pushPhase(new EggLapsePhase()); } } @@ -1004,12 +1004,14 @@ export function handleMysteryEncounterBattleStartEffects() { } else { source = globalScene.getEnemyField()[0]; } - // @ts-ignore: source cannot be undefined - globalScene.pushPhase(new MovePhase(source, effect.targets, effect.move, effect.followUp, effect.ignorePp)); + globalScene.phaseManager.pushPhase( + // @ts-ignore: source cannot be undefined + new MovePhase(source, effect.targets, effect.move, effect.followUp, effect.ignorePp), + ); }); // Pseudo turn end phase to reset flinch states, Endure, etc. - globalScene.pushPhase(new MysteryEncounterBattleStartCleanupPhase()); + globalScene.phaseManager.pushPhase(new MysteryEncounterBattleStartCleanupPhase()); encounter.startOfBattleEffectsComplete = true; } diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index d4102c045c0..757d8173820 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -675,7 +675,7 @@ export async function catchPokemon( if (!globalScene.getEnemyParty().some(p => p.id === pokemon.id)) { globalScene.getEnemyParty().push(pokemon); } - globalScene.unshiftPhase(new VictoryPhase(pokemon.id, true)); + globalScene.phaseManager.unshiftPhase(new VictoryPhase(pokemon.id, true)); globalScene.pokemonInfoContainer.hide(); if (pokeball) { removePb(pokeball); diff --git a/src/field/arena.ts b/src/field/arena.ts index 2ec98c53afa..1c54382c89b 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -297,8 +297,8 @@ export class Arena { */ trySetWeatherOverride(weather: WeatherType): boolean { this.weather = new Weather(weather, 0); - globalScene.unshiftPhase(new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (weather - 1))); - globalScene.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct? + globalScene.phaseManager.unshiftPhase(new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (weather - 1))); + globalScene.phaseManager.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct? return true; } @@ -328,10 +328,10 @@ export class Arena { this.weather?.isImmutable() && ![WeatherType.HARSH_SUN, WeatherType.HEAVY_RAIN, WeatherType.STRONG_WINDS, WeatherType.NONE].includes(weather) ) { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (oldWeatherType - 1), true), ); - globalScene.queueMessage(getLegendaryWeatherContinuesMessage(oldWeatherType)!); + globalScene.phaseManager.queueMessage(getLegendaryWeatherContinuesMessage(oldWeatherType)!); return false; } @@ -348,10 +348,12 @@ export class Arena { ); // TODO: is this bang correct? if (this.weather) { - globalScene.unshiftPhase(new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (weather - 1), true)); - globalScene.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct? + globalScene.phaseManager.unshiftPhase( + new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (weather - 1), true), + ); + globalScene.phaseManager.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct? } else { - globalScene.queueMessage(getWeatherClearMessage(oldWeatherType)!); // TODO: is this bang correct? + globalScene.phaseManager.queueMessage(getWeatherClearMessage(oldWeatherType)!); // TODO: is this bang correct? } globalScene @@ -431,11 +433,13 @@ export class Arena { if (this.terrain) { if (!ignoreAnim) { - globalScene.unshiftPhase(new CommonAnimPhase(undefined, undefined, CommonAnim.MISTY_TERRAIN + (terrain - 1))); + globalScene.phaseManager.unshiftPhase( + new 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 { - globalScene.queueMessage(getTerrainClearMessage(oldTerrainType)!); // TODO: is this bang correct? + globalScene.phaseManager.queueMessage(getTerrainClearMessage(oldTerrainType)!); // TODO: is this bang correct? } globalScene diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index d26dfd193f9..a6d41074700 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1298,7 +1298,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return false; } // During the Pokemon's MoveEffect phase, the offset is removed to put the Pokemon "in focus" - const currentPhase = globalScene.getCurrentPhase(); + const currentPhase = globalScene.phaseManager.getCurrentPhase(); return !(currentPhase?.is("MoveEffectPhase") && currentPhase.getPokemon() === this); } @@ -2537,7 +2537,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ) { multiplier /= 2; if (!simulated) { - globalScene.queueMessage(i18next.t("weather:strongWindsEffectMessage")); + globalScene.phaseManager.queueMessage(i18next.t("weather:strongWindsEffectMessage")); } } return multiplier as TypeDamageMultiplier; @@ -4011,8 +4011,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() ) */ - globalScene.setPhaseQueueSplice(); - globalScene.unshiftPhase(new FaintPhase(this.getBattlerIndex(), preventEndure)); + globalScene.phaseManager.setPhaseQueueSplice(); + globalScene.phaseManager.unshiftPhase(new FaintPhase(this.getBattlerIndex(), preventEndure)); this.destroySubstitute(); this.lapseTag(BattlerTagType.COMMANDED); } @@ -4049,7 +4049,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ): number { const isIndirectDamage = [HitResult.INDIRECT, HitResult.INDIRECT_KO].includes(result); const damagePhase = new DamageAnimPhase(this.getBattlerIndex(), damage, result as DamageResult, isCritical); - globalScene.unshiftPhase(damagePhase); + globalScene.phaseManager.unshiftPhase(damagePhase); if (this.switchOutStatus && source) { damage = 0; } @@ -4615,7 +4615,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { : i18next.t("abilityTriggers:moveImmunity", { pokemonNameWithAffix: getPokemonNameWithAffix(this), }); - globalScene.queueMessage(message); + globalScene.phaseManager.queueMessage(message); } /** @@ -4735,7 +4735,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (sourcePokemon && sourcePokemon !== this && this.isSafeguarded(sourcePokemon)) { if (!quiet) { - globalScene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(this) })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(this) }), + ); } return false; } @@ -4764,7 +4766,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * cancel the attack's subsequent hits. */ if (effect === StatusEffect.SLEEP || effect === StatusEffect.FREEZE) { - const currentPhase = globalScene.getCurrentPhase(); + const currentPhase = globalScene.phaseManager.getCurrentPhase(); if (currentPhase?.is("MoveEffectPhase") && currentPhase.getUserPokemon() === this) { this.turnData.hitCount = 1; this.turnData.hitsLeft = 1; @@ -4775,7 +4777,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (overrideStatus) { this.resetStatus(false); } - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new ObtainStatusEffectPhase(this.getBattlerIndex(), effect, turnsRemaining, sourceText, sourcePokemon), ); return true; @@ -4825,7 +4827,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (asPhase) { - globalScene.unshiftPhase(new ResetStatusPhase(this, confusion, reloadAssets)); + globalScene.phaseManager.unshiftPhase(new ResetStatusPhase(this, confusion, reloadAssets)); } else { this.clearStatus(confusion, reloadAssets); } @@ -5632,7 +5634,7 @@ export class PlayerPokemon extends Pokemon { this.getFieldIndex(), (slotIndex: number, _option: PartyOption) => { if (slotIndex >= globalScene.currentBattle.getBattlerCount() && slotIndex < 6) { - globalScene.prependToPhase( + globalScene.phaseManager.prependToPhase( new SwitchSummonPhase(switchType, this.getFieldIndex(), slotIndex, false), MoveEndPhase, ); @@ -5997,7 +5999,9 @@ export class PlayerPokemon extends Pokemon { const newPartyMemberIndex = globalScene.getPlayerParty().indexOf(this); pokemon .getMoveset(true) - .map((m: PokemonMove) => globalScene.unshiftPhase(new LearnMovePhase(newPartyMemberIndex, m.getMove().id))); + .map((m: PokemonMove) => + globalScene.phaseManager.unshiftPhase(new LearnMovePhase(newPartyMemberIndex, m.getMove().id)), + ); pokemon.destroy(); this.updateFusionPalette(); } @@ -6639,7 +6643,7 @@ export class EnemyPokemon extends Pokemon { stages++; } - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new StatStageChangePhase(this.getBattlerIndex(), true, [boostedStat!], stages, true, true), ); this.bossSegmentIndex--; diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index f8c71b2c891..e1517b3bcde 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1548,7 +1548,7 @@ export class SurviveDamageModifier extends PokemonHeldItemModifier { if (!surviveDamage.value && pokemon.randBattleSeedInt(10) < this.getStackCount()) { surviveDamage.value = true; - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("modifier:surviveDamageApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name, @@ -1598,7 +1598,7 @@ export class BypassSpeedChanceModifier extends PokemonHeldItemModifier { const hasQuickClaw = this.type instanceof PokemonHeldItemModifierType && this.type.id === "QUICK_CLAW"; if (isCommandFight && hasQuickClaw) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("modifier:bypassSpeedChanceApply", { pokemonName: getPokemonNameWithAffix(pokemon), itemName: i18next.t("modifierType:ModifierType.QUICK_CLAW.name"), @@ -1684,7 +1684,7 @@ export class TurnHealModifier extends PokemonHeldItemModifier { */ override apply(pokemon: Pokemon): boolean { if (!pokemon.isFullHp()) { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new PokemonHealPhase( pokemon.getBattlerIndex(), toDmgValue(pokemon.getMaxHp() / 16) * this.stackCount, @@ -1782,7 +1782,7 @@ export class HitHealModifier extends PokemonHeldItemModifier { override apply(pokemon: Pokemon): boolean { if (pokemon.turnData.totalDamageDealt && !pokemon.isFullHp()) { // TODO: this shouldn't be undefined AFAIK - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new PokemonHealPhase( pokemon.getBattlerIndex(), toDmgValue(pokemon.turnData.totalDamageDealt / 8) * this.stackCount, @@ -1950,7 +1950,7 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier { */ override apply(pokemon: Pokemon): boolean { // Restore the Pokemon to half HP - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new PokemonHealPhase( pokemon.getBattlerIndex(), toDmgValue(pokemon.getMaxHp() / 2), @@ -2012,7 +2012,7 @@ export class ResetNegativeStatStageModifier extends PokemonHeldItemModifier { } if (statRestored) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("modifier:resetNegativeStatStageApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name, @@ -2323,7 +2323,7 @@ export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier { playerPokemon.addFriendship(FRIENDSHIP_GAIN_FROM_RARE_CANDY); - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new LevelUpPhase( globalScene.getPlayerParty().indexOf(playerPokemon), playerPokemon.level - levelCount.value, @@ -2344,7 +2344,7 @@ export class TmModifier extends ConsumablePokemonModifier { * @returns always `true` */ override apply(playerPokemon: PlayerPokemon): boolean { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new LearnMovePhase(globalScene.getPlayerParty().indexOf(playerPokemon), this.type.moveId, LearnMoveType.TM), ); @@ -2367,7 +2367,7 @@ export class RememberMoveModifier extends ConsumablePokemonModifier { * @returns always `true` */ override apply(playerPokemon: PlayerPokemon, cost?: number): boolean { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new LearnMovePhase( globalScene.getPlayerParty().indexOf(playerPokemon), playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex], @@ -2410,7 +2410,9 @@ export class EvolutionItemModifier extends ConsumablePokemonModifier { } if (matchingEvolution) { - globalScene.unshiftPhase(new EvolutionPhase(playerPokemon, matchingEvolution, playerPokemon.level - 1)); + globalScene.phaseManager.unshiftPhase( + new EvolutionPhase(playerPokemon, matchingEvolution, playerPokemon.level - 1), + ); return true; } @@ -3008,7 +3010,7 @@ export class MoneyInterestModifier extends PersistentModifier { moneyAmount: formattedMoneyAmount, typeName: this.type.name, }); - globalScene.queueMessage(message, undefined, true); + globalScene.phaseManager.queueMessage(message, undefined, true); return true; } @@ -3262,7 +3264,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { } for (const mt of transferredModifierTypes) { - globalScene.queueMessage(this.getTransferMessage(pokemon, targetPokemon, mt)); + globalScene.phaseManager.queueMessage(this.getTransferMessage(pokemon, targetPokemon, mt)); } return !!transferredModifierTypes.length; @@ -3572,7 +3574,7 @@ export class EnemyTurnHealModifier extends EnemyPersistentModifier { */ override apply(enemyPokemon: Pokemon): boolean { if (!enemyPokemon.isFullHp()) { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new PokemonHealPhase( enemyPokemon.getBattlerIndex(), Math.max(Math.floor(enemyPokemon.getMaxHp() / (100 / this.healPercent)) * this.stackCount, 1), @@ -3668,7 +3670,7 @@ export class EnemyStatusEffectHealChanceModifier extends EnemyPersistentModifier */ override apply(enemyPokemon: Pokemon): boolean { if (enemyPokemon.status && Phaser.Math.RND.realInRange(0, 1) < this.chance * this.getStackCount()) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( getStatusEffectHealText(enemyPokemon.status.effect, getPokemonNameWithAffix(enemyPokemon)), ); enemyPokemon.resetStatus(); diff --git a/src/phase-manager.ts b/src/phase-manager.ts new file mode 100644 index 00000000000..8869f4b27b7 --- /dev/null +++ b/src/phase-manager.ts @@ -0,0 +1,311 @@ +import { HideAbilityPhase } from "./phases/hide-ability-phase"; +import { ShowAbilityPhase } from "./phases/show-ability-phase"; +import { TurnInitPhase } from "./phases/turn-init-phase"; +import type { Phase } from "#app/phase"; +import type { default as Pokemon } from "#app/field/pokemon"; +import type { Constructor } from "#app/utils/common"; +import { MessagePhase } from "./phases/message-phase"; +import { globalScene } from "#app/global-scene"; + +/** + * 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.* + */ + +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

(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): 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; + } + + /** + * 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 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): 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 - 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()); + } +} diff --git a/src/phase.ts b/src/phase.ts index 9e2468ebdff..5e81679d29e 100644 --- a/src/phase.ts +++ b/src/phase.ts @@ -5,7 +5,7 @@ export abstract class Phase { start() {} end() { - globalScene.shiftPhase(); + globalScene.phaseManager.shiftPhase(); } /** diff --git a/src/phases/attempt-capture-phase.ts b/src/phases/attempt-capture-phase.ts index 6c2f5f4dd76..09eeb6e0a19 100644 --- a/src/phases/attempt-capture-phase.ts +++ b/src/phases/attempt-capture-phase.ts @@ -257,7 +257,7 @@ export class AttemptCapturePhase extends PokemonPhase { null, () => { const end = () => { - globalScene.unshiftPhase(new VictoryPhase(this.battlerIndex)); + globalScene.phaseManager.unshiftPhase(new VictoryPhase(this.battlerIndex)); globalScene.pokemonInfoContainer.hide(); this.removePb(); this.end(); diff --git a/src/phases/attempt-run-phase.ts b/src/phases/attempt-run-phase.ts index ced81567c43..2bf6ba2fb5b 100644 --- a/src/phases/attempt-run-phase.ts +++ b/src/phases/attempt-run-phase.ts @@ -39,7 +39,7 @@ export class AttemptRunPhase extends PokemonPhase { enemyField.forEach(enemyPokemon => applyPreLeaveFieldAbAttrs(PreLeaveFieldAbAttr, enemyPokemon)); 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({ targets: [globalScene.arenaEnemy, enemyField].flat(), @@ -60,16 +60,16 @@ export class AttemptRunPhase extends PokemonPhase { enemyPokemon.trySetStatus(StatusEffect.FAINT); }); - globalScene.pushPhase(new BattleEndPhase(false)); + globalScene.phaseManager.pushPhase(new BattleEndPhase(false)); if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) { - globalScene.pushPhase(new SelectBiomePhase()); + globalScene.phaseManager.pushPhase(new SelectBiomePhase()); } - globalScene.pushPhase(new NewBattlePhase()); + globalScene.phaseManager.pushPhase(new NewBattlePhase()); } else { 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(); diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index 96f6d02b1fc..4e3543be03a 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -19,7 +19,7 @@ export class BattleEndPhase extends BattlePhase { super.start(); // cull any extra `BattleEnd` phases from the queue. - globalScene.phaseQueue = globalScene.phaseQueue.filter(phase => { + globalScene.phaseManager.phaseQueue = globalScene.phaseManager.phaseQueue.filter(phase => { if (phase.is("BattleEndPhase")) { this.isVictory ||= phase.isVictory; return false; @@ -28,7 +28,7 @@ export class BattleEndPhase extends BattlePhase { }); // `phaseQueuePrepend` is private, so we have to use this inefficient loop. while ( - globalScene.tryRemoveUnshiftedPhase(phase => { + globalScene.phaseManager.tryRemoveUnshiftedPhase(phase => { if (phase.is("BattleEndPhase")) { this.isVictory ||= phase.isVictory; return true; @@ -55,8 +55,8 @@ export class BattleEndPhase extends BattlePhase { // Endless graceful end if (globalScene.gameMode.isEndless && globalScene.currentBattle.waveIndex >= 5850) { - globalScene.clearPhaseQueue(); - globalScene.unshiftPhase(new GameOverPhase(true)); + globalScene.phaseManager.clearPhaseQueue(); + globalScene.phaseManager.unshiftPhase(new GameOverPhase(true)); } for (const pokemon of globalScene.getField()) { diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index 8d66d498039..e9e177a8fe4 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -50,7 +50,7 @@ export class BerryPhase extends FieldPhase { const cancelled = new BooleanHolder(false); pokemon.getOpponents().forEach(opp => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled)); if (cancelled.value) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("abilityTriggers:preventBerryUse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -58,7 +58,7 @@ export class BerryPhase extends FieldPhase { return; } - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM), ); diff --git a/src/phases/check-status-effect-phase.ts b/src/phases/check-status-effect-phase.ts index 0c5884bbac7..ec15676ebcc 100644 --- a/src/phases/check-status-effect-phase.ts +++ b/src/phases/check-status-effect-phase.ts @@ -15,7 +15,7 @@ export class CheckStatusEffectPhase extends Phase { const field = globalScene.getField(); for (const o of this.order) { if (field[o].status?.isPostTurn()) { - globalScene.unshiftPhase(new PostTurnStatusEffectPhase(o)); + globalScene.phaseManager.unshiftPhase(new PostTurnStatusEffectPhase(o)); } } this.end(); diff --git a/src/phases/check-switch-phase.ts b/src/phases/check-switch-phase.ts index 0506cd36c46..2299bf0c0a5 100644 --- a/src/phases/check-switch-phase.ts +++ b/src/phases/check-switch-phase.ts @@ -35,7 +35,7 @@ export class CheckSwitchPhase extends BattlePhase { // ...if the checked Pokemon is somehow not on the field if (globalScene.field.getAll().indexOf(pokemon) === -1) { - globalScene.unshiftPhase(new SummonMissingPhase(this.fieldIndex)); + globalScene.phaseManager.unshiftPhase(new SummonMissingPhase(this.fieldIndex)); return super.end(); } @@ -68,7 +68,9 @@ export class CheckSwitchPhase extends BattlePhase { UiMode.CONFIRM, () => { globalScene.ui.setMode(UiMode.MESSAGE); - globalScene.unshiftPhase(new SwitchPhase(SwitchType.INITIAL_SWITCH, this.fieldIndex, false, true)); + globalScene.phaseManager.unshiftPhase( + new SwitchPhase(SwitchType.INITIAL_SWITCH, this.fieldIndex, false, true), + ); this.end(); }, () => { diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index 209c1eefc85..7a2e427ecce 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -192,7 +192,7 @@ export class CommandPhase extends FieldPhase { } console.log(moveTargets, getPokemonNameWithAffix(playerPokemon)); if (moveTargets.targets.length > 1 && moveTargets.multiple) { - globalScene.unshiftPhase(new SelectTargetPhase(this.fieldIndex)); + globalScene.phaseManager.unshiftPhase(new SelectTargetPhase(this.fieldIndex)); } if (turnCommand.move && (moveTargets.targets.length <= 1 || moveTargets.multiple)) { turnCommand.move.targets = moveTargets.targets; @@ -203,7 +203,7 @@ export class CommandPhase extends FieldPhase { ) { turnCommand.move.targets = playerPokemon.getMoveQueue()[0].targets; } else { - globalScene.unshiftPhase(new SelectTargetPhase(this.fieldIndex)); + globalScene.phaseManager.unshiftPhase(new SelectTargetPhase(this.fieldIndex)); } globalScene.currentBattle.preTurnCommands[this.fieldIndex] = preTurnCommand; globalScene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; @@ -457,8 +457,8 @@ export class CommandPhase extends FieldPhase { cancel() { if (this.fieldIndex) { - globalScene.unshiftPhase(new CommandPhase(0)); - globalScene.unshiftPhase(new CommandPhase(1)); + globalScene.phaseManager.unshiftPhase(new CommandPhase(0)); + globalScene.phaseManager.unshiftPhase(new CommandPhase(1)); this.end(); } } diff --git a/src/phases/egg-hatch-phase.ts b/src/phases/egg-hatch-phase.ts index dfcdc05f9a2..d6c40a1510e 100644 --- a/src/phases/egg-hatch-phase.ts +++ b/src/phases/egg-hatch-phase.ts @@ -225,7 +225,7 @@ export class EggHatchPhase extends Phase { } end() { - if (globalScene.findPhase(p => p.is("EggHatchPhase"))) { + if (globalScene.phaseManager.findPhase(p => p.is("EggHatchPhase"))) { this.eggHatchHandler.clear(); } else { globalScene.time.delayedCall(250, () => globalScene.setModifiersVisible(true)); diff --git a/src/phases/egg-lapse-phase.ts b/src/phases/egg-lapse-phase.ts index 182d6f304d6..a19bf8f50e5 100644 --- a/src/phases/egg-lapse-phase.ts +++ b/src/phases/egg-lapse-phase.ts @@ -62,12 +62,12 @@ export class EggLapsePhase extends Phase { true, ); } 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.showSummary(); } else { // regular hatches, no summary - globalScene.queueMessage(i18next.t("battle:eggHatching")); + globalScene.phaseManager.queueMessage(i18next.t("battle:eggHatching")); this.hatchEggsRegular(eggsToHatch); this.end(); } @@ -83,7 +83,7 @@ export class EggLapsePhase extends Phase { hatchEggsRegular(eggsToHatch: Egg[]) { let eggsToHatchCount: number = eggsToHatch.length; for (const egg of eggsToHatch) { - globalScene.unshiftPhase(new EggHatchPhase(this, egg, eggsToHatchCount)); + globalScene.phaseManager.unshiftPhase(new EggHatchPhase(this, egg, eggsToHatchCount)); eggsToHatchCount--; } } @@ -99,7 +99,7 @@ export class EggLapsePhase extends Phase { } showSummary() { - globalScene.unshiftPhase(new EggSummaryPhase(this.eggHatchData)); + globalScene.phaseManager.unshiftPhase(new EggSummaryPhase(this.eggHatchData)); this.end(); } diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index df84f8f8ff4..cdd17c6d6d6 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -68,7 +68,7 @@ export class EncounterPhase extends BattlePhase { // Failsafe if players somehow skip floor 200 in classic mode if (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex > 200) { - globalScene.unshiftPhase(new GameOverPhase()); + globalScene.phaseManager.unshiftPhase(new GameOverPhase()); } const loadEnemyAssets: Promise[] = []; @@ -438,9 +438,9 @@ export class EncounterPhase extends BattlePhase { const doTrainerSummon = () => { this.hideEnemyTrainer(); const availablePartyMembers = globalScene.getEnemyParty().filter(p => !p.isFainted()).length; - globalScene.unshiftPhase(new SummonPhase(0, false)); + globalScene.phaseManager.unshiftPhase(new SummonPhase(0, false)); if (globalScene.currentBattle.double && availablePartyMembers > 1) { - globalScene.unshiftPhase(new SummonPhase(1, false)); + globalScene.phaseManager.unshiftPhase(new SummonPhase(1, false)); } this.end(); }; @@ -496,7 +496,7 @@ export class EncounterPhase extends BattlePhase { globalScene.ui.clearText(); globalScene.ui.getMessageHandler().hideNameText(); - globalScene.unshiftPhase(new MysteryEncounterPhase()); + globalScene.phaseManager.unshiftPhase(new MysteryEncounterPhase()); this.end(); }; @@ -554,7 +554,7 @@ export class EncounterPhase extends BattlePhase { enemyField.forEach((enemyPokemon, e) => { if (enemyPokemon.isShiny(true)) { - globalScene.unshiftPhase(new ShinySparklePhase(BattlerIndex.ENEMY + e)); + globalScene.phaseManager.unshiftPhase(new ShinySparklePhase(BattlerIndex.ENEMY + e)); } /** This sets Eternatus' held item to be untransferrable, preventing it from being stolen */ if ( @@ -576,7 +576,7 @@ export class EncounterPhase extends BattlePhase { if (![BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(globalScene.currentBattle.battleType)) { enemyField.map(p => - globalScene.pushConditionalPhase(new PostSummonPhase(p.getBattlerIndex()), () => { + globalScene.phaseManager.pushConditionalPhase(new PostSummonPhase(p.getBattlerIndex()), () => { // if there is not a player party, we can't continue if (!globalScene.getPlayerParty().length) { return false; @@ -594,7 +594,7 @@ export class EncounterPhase extends BattlePhase { ); const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier); if (ivScannerModifier) { - enemyField.map(p => globalScene.pushPhase(new ScanIvsPhase(p.getBattlerIndex()))); + enemyField.map(p => globalScene.phaseManager.pushPhase(new ScanIvsPhase(p.getBattlerIndex()))); } } @@ -602,21 +602,21 @@ export class EncounterPhase extends BattlePhase { const availablePartyMembers = globalScene.getPokemonAllowedInBattle(); if (!availablePartyMembers[0].isOnField()) { - globalScene.pushPhase(new SummonPhase(0)); + globalScene.phaseManager.pushPhase(new SummonPhase(0)); } if (globalScene.currentBattle.double) { if (availablePartyMembers.length > 1) { - globalScene.pushPhase(new ToggleDoublePositionPhase(true)); + globalScene.phaseManager.pushPhase(new ToggleDoublePositionPhase(true)); if (!availablePartyMembers[1].isOnField()) { - globalScene.pushPhase(new SummonPhase(1)); + globalScene.phaseManager.pushPhase(new SummonPhase(1)); } } } else { if (availablePartyMembers.length > 1 && availablePartyMembers[1].isOnField()) { - globalScene.pushPhase(new ReturnPhase(1)); + globalScene.phaseManager.pushPhase(new ReturnPhase(1)); } - globalScene.pushPhase(new ToggleDoublePositionPhase(false)); + globalScene.phaseManager.pushPhase(new ToggleDoublePositionPhase(false)); } if ( @@ -625,9 +625,9 @@ export class EncounterPhase extends BattlePhase { ) { const minPartySize = globalScene.currentBattle.double ? 2 : 1; if (availablePartyMembers.length > minPartySize) { - globalScene.pushPhase(new CheckSwitchPhase(0, globalScene.currentBattle.double)); + globalScene.phaseManager.pushPhase(new CheckSwitchPhase(0, globalScene.currentBattle.double)); if (globalScene.currentBattle.double) { - globalScene.pushPhase(new CheckSwitchPhase(1, globalScene.currentBattle.double)); + globalScene.phaseManager.pushPhase(new CheckSwitchPhase(1, globalScene.currentBattle.double)); } } } diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index 5e635f4cd82..937a7d31542 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -262,7 +262,7 @@ export class EvolutionPhase extends Phase { SoundFade.fadeOut(globalScene, this.evolutionBgm, 100); - globalScene.unshiftPhase(new EndEvolutionPhase()); + globalScene.phaseManager.unshiftPhase(new EndEvolutionPhase()); globalScene.ui.showText( i18next.t("menu:stoppedEvolving", { @@ -355,9 +355,11 @@ export class EvolutionPhase extends Phase { .getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation) .filter(lm => lm[0] === EVOLVE_MOVE); for (const lm of levelMoves) { - globalScene.unshiftPhase(new LearnMovePhase(globalScene.getPlayerParty().indexOf(this.pokemon), lm[1])); + globalScene.phaseManager.unshiftPhase( + new LearnMovePhase(globalScene.getPlayerParty().indexOf(this.pokemon), lm[1]), + ); } - globalScene.unshiftPhase(new EndEvolutionPhase()); + globalScene.phaseManager.unshiftPhase(new EndEvolutionPhase()); globalScene.playSound("se/shine"); this.doSpray(); diff --git a/src/phases/exp-phase.ts b/src/phases/exp-phase.ts index 14d7d7578f8..8084ae78221 100644 --- a/src/phases/exp-phase.ts +++ b/src/phases/exp-phase.ts @@ -34,7 +34,7 @@ export class ExpPhase extends PlayerPartyMemberPokemonPhase { pokemon.addExp(exp.value); const newLevel = pokemon.level; if (newLevel > lastLevel) { - globalScene.unshiftPhase(new LevelUpPhase(this.partyMemberIndex, lastLevel, newLevel)); + globalScene.phaseManager.unshiftPhase(new LevelUpPhase(this.partyMemberIndex, lastLevel, newLevel)); } pokemon.updateInfo().then(() => this.end()); }, diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index fac0947c4e7..41e21162dbf 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -115,7 +115,7 @@ export class FaintPhase extends PokemonPhase { }); } - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), @@ -166,7 +166,7 @@ export class FaintPhase extends PokemonPhase { const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true)); if (!legalPlayerPokemon.length) { /** If the player doesn't have any legal Pokemon, end the game */ - globalScene.unshiftPhase(new GameOverPhase()); + globalScene.phaseManager.unshiftPhase(new GameOverPhase()); } else if ( globalScene.currentBattle.double && legalPlayerPokemon.length === 1 && @@ -176,23 +176,25 @@ export class FaintPhase extends PokemonPhase { * 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. */ - globalScene.unshiftPhase(new ToggleDoublePositionPhase(true)); + globalScene.phaseManager.unshiftPhase(new ToggleDoublePositionPhase(true)); } else if (legalPlayerPartyPokemon.length > 0) { /** * 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. */ - globalScene.pushPhase(new SwitchPhase(SwitchType.SWITCH, this.fieldIndex, true, false)); + globalScene.phaseManager.pushPhase(new SwitchPhase(SwitchType.SWITCH, this.fieldIndex, true, false)); } } else { - globalScene.unshiftPhase(new VictoryPhase(this.battlerIndex)); + globalScene.phaseManager.unshiftPhase(new VictoryPhase(this.battlerIndex)); if ([BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(globalScene.currentBattle.battleType)) { const hasReservePartyMember = !!globalScene .getEnemyParty() .filter(p => p.isActive() && !p.isOnField() && p.trainerSlot === (pokemon as EnemyPokemon).trainerSlot) .length; if (hasReservePartyMember) { - globalScene.pushPhase(new SwitchSummonPhase(SwitchType.SWITCH, this.fieldIndex, -1, false, false)); + globalScene.phaseManager.pushPhase( + new SwitchSummonPhase(SwitchType.SWITCH, this.fieldIndex, -1, false, false), + ); } } } @@ -247,7 +249,7 @@ export class FaintPhase extends PokemonPhase { } else { // Final boss' HP threshold has been bypassed; cancel faint and force check for 2nd phase enemy.hp++; - globalScene.unshiftPhase(new DamageAnimPhase(enemy.getBattlerIndex(), 0, HitResult.INDIRECT)); + globalScene.phaseManager.unshiftPhase(new DamageAnimPhase(enemy.getBattlerIndex(), 0, HitResult.INDIRECT)); this.end(); } return true; diff --git a/src/phases/form-change-phase.ts b/src/phases/form-change-phase.ts index f5e428c6d3d..c0d2a9a11eb 100644 --- a/src/phases/form-change-phase.ts +++ b/src/phases/form-change-phase.ts @@ -100,7 +100,7 @@ export class FormChangePhase extends EvolutionPhase { globalScene.time.delayedCall(900, () => { this.pokemon.changeForm(this.formChange).then(() => { if (!this.modal) { - globalScene.unshiftPhase(new EndEvolutionPhase()); + globalScene.phaseManager.unshiftPhase(new EndEvolutionPhase()); } globalScene.playSound("se/shine"); diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts index 1d03739d610..166bb955c24 100644 --- a/src/phases/game-over-phase.ts +++ b/src/phases/game-over-phase.ts @@ -47,7 +47,7 @@ export class GameOverPhase extends BattlePhase { start() { super.start(); - globalScene.hideAbilityBar(); + globalScene.phaseManager.hideAbilityBar(); // Failsafe if players somehow skip floor 200 in classic mode if (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex > 200) { @@ -84,23 +84,23 @@ export class GameOverPhase extends BattlePhase { () => { globalScene.ui.fadeOut(1250).then(() => { globalScene.reset(); - globalScene.clearPhaseQueue(); + globalScene.phaseManager.clearPhaseQueue(); globalScene.gameData.loadSession(globalScene.sessionSlotId).then(() => { - globalScene.pushPhase(new EncounterPhase(true)); + globalScene.phaseManager.pushPhase(new EncounterPhase(true)); const availablePartyMembers = globalScene.getPokemonAllowedInBattle().length; - globalScene.pushPhase(new SummonPhase(0)); + globalScene.phaseManager.pushPhase(new SummonPhase(0)); if (globalScene.currentBattle.double && availablePartyMembers > 1) { - globalScene.pushPhase(new SummonPhase(1)); + globalScene.phaseManager.pushPhase(new SummonPhase(1)); } if ( globalScene.currentBattle.waveIndex > 1 && globalScene.currentBattle.battleType !== BattleType.TRAINER ) { - globalScene.pushPhase(new CheckSwitchPhase(0, globalScene.currentBattle.double)); + globalScene.phaseManager.pushPhase(new CheckSwitchPhase(0, globalScene.currentBattle.double)); if (globalScene.currentBattle.double && availablePartyMembers > 1) { - globalScene.pushPhase(new CheckSwitchPhase(1, globalScene.currentBattle.double)); + globalScene.phaseManager.pushPhase(new CheckSwitchPhase(1, globalScene.currentBattle.double)); } } @@ -148,7 +148,7 @@ export class GameOverPhase extends BattlePhase { globalScene.ui.fadeOut(fadeDuration).then(() => { activeBattlers.map(a => a.setVisible(false)); globalScene.setFieldScale(1, true); - globalScene.clearPhaseQueue(); + globalScene.phaseManager.clearPhaseQueue(); globalScene.ui.clearText(); if (this.isVictory && globalScene.gameMode.isChallenge) { @@ -160,15 +160,17 @@ export class GameOverPhase extends BattlePhase { this.handleUnlocks(); for (const species of this.firstRibbons) { - globalScene.unshiftPhase(new RibbonModifierRewardPhase(modifierTypes.VOUCHER_PLUS, species)); + globalScene.phaseManager.unshiftPhase( + new RibbonModifierRewardPhase(modifierTypes.VOUCHER_PLUS, species), + ); } if (!firstClear) { - globalScene.unshiftPhase(new GameOverModifierRewardPhase(modifierTypes.VOUCHER_PREMIUM)); + globalScene.phaseManager.unshiftPhase(new GameOverModifierRewardPhase(modifierTypes.VOUCHER_PREMIUM)); } } this.getRunHistoryEntry().then(runHistoryEntry => { globalScene.gameData.saveRunHistory(runHistoryEntry, this.isVictory); - globalScene.pushPhase(new PostGameOverPhase(endCardPhase)); + globalScene.phaseManager.pushPhase(new PostGameOverPhase(endCardPhase)); this.end(); }); }; @@ -198,7 +200,7 @@ export class GameOverPhase extends BattlePhase { globalScene.ui.fadeOut(500).then(() => { globalScene.charSprite.hide().then(() => { const endCardPhase = new EndCardPhase(); - globalScene.unshiftPhase(endCardPhase); + globalScene.phaseManager.unshiftPhase(endCardPhase); clear(endCardPhase); }); }); @@ -208,7 +210,7 @@ export class GameOverPhase extends BattlePhase { }); } else { const endCardPhase = new EndCardPhase(); - globalScene.unshiftPhase(endCardPhase); + globalScene.phaseManager.unshiftPhase(endCardPhase); clear(endCardPhase); } } else { @@ -230,9 +232,9 @@ export class GameOverPhase extends BattlePhase { }) .then(success => doGameOver(!globalScene.gameMode.isDaily || !!success)) .catch(_err => { - globalScene.clearPhaseQueue(); - globalScene.clearPhaseQueueSplice(); - globalScene.unshiftPhase(new MessagePhase(i18next.t("menu:serverCommunicationFailed"), 2500)); + globalScene.phaseManager.clearPhaseQueue(); + globalScene.phaseManager.clearPhaseQueueSplice(); + globalScene.phaseManager.unshiftPhase(new MessagePhase(i18next.t("menu:serverCommunicationFailed"), 2500)); // force the game to reload after 2 seconds. setTimeout(() => { window.location.reload(); @@ -251,22 +253,22 @@ export class GameOverPhase extends BattlePhase { handleUnlocks(): void { if (this.isVictory && globalScene.gameMode.isClassic) { if (!globalScene.gameData.unlocks[Unlockables.ENDLESS_MODE]) { - globalScene.unshiftPhase(new UnlockPhase(Unlockables.ENDLESS_MODE)); + globalScene.phaseManager.unshiftPhase(new UnlockPhase(Unlockables.ENDLESS_MODE)); } if ( globalScene.getPlayerParty().filter(p => p.fusionSpecies).length && !globalScene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE] ) { - globalScene.unshiftPhase(new UnlockPhase(Unlockables.SPLICED_ENDLESS_MODE)); + globalScene.phaseManager.unshiftPhase(new UnlockPhase(Unlockables.SPLICED_ENDLESS_MODE)); } if (!globalScene.gameData.unlocks[Unlockables.MINI_BLACK_HOLE]) { - globalScene.unshiftPhase(new UnlockPhase(Unlockables.MINI_BLACK_HOLE)); + globalScene.phaseManager.unshiftPhase(new UnlockPhase(Unlockables.MINI_BLACK_HOLE)); } if ( !globalScene.gameData.unlocks[Unlockables.EVIOLITE] && globalScene.getPlayerParty().some(p => p.getSpeciesForm(true).speciesId in pokemonEvolutions) ) { - globalScene.unshiftPhase(new UnlockPhase(Unlockables.EVIOLITE)); + globalScene.phaseManager.unshiftPhase(new UnlockPhase(Unlockables.EVIOLITE)); } } } diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts index d455ed35591..7464cebe7da 100644 --- a/src/phases/learn-move-phase.ts +++ b/src/phases/learn-move-phase.ts @@ -195,7 +195,7 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { pokemon.usedTMs = []; } pokemon.usedTMs.push(this.moveId); - globalScene.tryRemovePhase(phase => phase.is("SelectModifierPhase")); + globalScene.phaseManager.tryRemovePhase(phase => phase.is("SelectModifierPhase")); } else if (this.learnMoveType === LearnMoveType.MEMORY) { if (this.cost !== -1) { if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { @@ -205,7 +205,7 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { } globalScene.playSound("se/buy"); } else { - globalScene.tryRemovePhase(phase => phase.is("SelectModifierPhase")); + globalScene.phaseManager.tryRemovePhase(phase => phase.is("SelectModifierPhase")); } } pokemon.setMove(index, this.moveId); diff --git a/src/phases/level-up-phase.ts b/src/phases/level-up-phase.ts index 7cf86a313df..b3b82f13f42 100644 --- a/src/phases/level-up-phase.ts +++ b/src/phases/level-up-phase.ts @@ -66,14 +66,14 @@ export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { // this feels like an unnecessary optimization const levelMoves = this.getPokemon().getLevelMoves(this.lastLevel + 1); for (const lm of levelMoves) { - globalScene.unshiftPhase(new LearnMovePhase(this.partyMemberIndex, lm[1])); + globalScene.phaseManager.unshiftPhase(new LearnMovePhase(this.partyMemberIndex, lm[1])); } } if (!this.pokemon.pauseEvolutions) { const evolution = this.pokemon.getEvolution(); if (evolution) { this.pokemon.breakIllusion(); - globalScene.unshiftPhase(new EvolutionPhase(this.pokemon, evolution, this.lastLevel)); + globalScene.phaseManager.unshiftPhase(new EvolutionPhase(this.pokemon, evolution, this.lastLevel)); } } return super.end(); diff --git a/src/phases/login-phase.ts b/src/phases/login-phase.ts index ec12b5ddaa4..5e1728d4415 100644 --- a/src/phases/login-phase.ts +++ b/src/phases/login-phase.ts @@ -70,7 +70,7 @@ export class LoginPhase extends Phase { }); }, () => { - globalScene.unshiftPhase(new LoginPhase(false)); + globalScene.phaseManager.unshiftPhase(new LoginPhase(false)); this.end(); }, ], @@ -94,7 +94,7 @@ export class LoginPhase extends Phase { removeCookie(sessionIdKey); globalScene.reset(true, true); } else { - globalScene.unshiftPhase(new UnavailablePhase()); + globalScene.phaseManager.unshiftPhase(new UnavailablePhase()); super.end(); } return null; @@ -114,7 +114,7 @@ export class LoginPhase extends Phase { globalScene.ui.setMode(UiMode.MESSAGE); if (!globalScene.gameData.gender) { - globalScene.unshiftPhase(new SelectGenderPhase()); + globalScene.phaseManager.unshiftPhase(new SelectGenderPhase()); } handleTutorial(Tutorial.Intro).then(() => super.end()); diff --git a/src/phases/message-phase.ts b/src/phases/message-phase.ts index 335258abe5c..2a485d837b0 100644 --- a/src/phases/message-phase.ts +++ b/src/phases/message-phase.ts @@ -44,7 +44,7 @@ export class MessagePhase extends Phase { page0 = page0.split(repname[p]).join(pokename[p]); page1 = page1.split(repname[p]).join(pokename[p]); } - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new MessagePhase(page1, this.callbackDelay, this.prompt, this.promptDelay, this.speaker), ); this.text = page0.trim(); diff --git a/src/phases/move-charge-phase.ts b/src/phases/move-charge-phase.ts index 789651623fa..263306acbf2 100644 --- a/src/phases/move-charge-phase.ts +++ b/src/phases/move-charge-phase.ts @@ -62,9 +62,9 @@ export class MoveChargePhase extends PokemonPhase { if (instantCharge.value) { // this MoveEndPhase will be duplicated by the queued MovePhase if not removed - globalScene.tryRemovePhase(phase => phase.is("MoveEndPhase") && phase.getPokemon() === user); + globalScene.phaseManager.tryRemovePhase(phase => phase.is("MoveEndPhase") && phase.getPokemon() === user); // queue a new MovePhase for this move's attack phase - globalScene.unshiftPhase(new MovePhase(user, [this.targetIndex], this.move, false)); + globalScene.phaseManager.unshiftPhase(new MovePhase(user, [this.targetIndex], this.move, false)); } else { user.getMoveQueue().push({ move: move.id, targets: [this.targetIndex] }); } diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index e62f65b029b..e135f5bd161 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -221,7 +221,7 @@ export class MoveEffectPhase extends PokemonPhase { break; // biome-ignore lint/suspicious/noFallthroughSwitchClause: The fallthrough is intentional case HitCheckResult.NO_EFFECT: - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t(this.move.id === MoveId.SHEER_COLD ? "battle:hitResultImmune" : "battle:hitResultNoEffect", { pokemonName: getPokemonNameWithAffix(target), }), @@ -232,7 +232,7 @@ export class MoveEffectPhase extends PokemonPhase { applyMoveAttrs(NoEffectAttr, user, target, this.move); break; case HitCheckResult.MISS: - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }), ); applyMoveAttrs(MissEffectAttr, user, target, this.move); @@ -384,7 +384,7 @@ export class MoveEffectPhase extends PokemonPhase { } if (this.queuedPhases.length) { - globalScene.appendToPhase(this.queuedPhases, MoveEndPhase); + globalScene.phaseManager.appendToPhase(this.queuedPhases, MoveEndPhase); } const moveType = user.getMoveType(this.move, true); if (this.move.category !== MoveCategory.STATUS && !user.stellarTypesBoosted.includes(moveType)) { @@ -410,14 +410,14 @@ export class MoveEffectPhase extends PokemonPhase { */ if (user) { if (user.turnData.hitsLeft && --user.turnData.hitsLeft >= 1 && this.getFirstTarget()?.isActive()) { - globalScene.unshiftPhase(this.getNewHitPhase()); + globalScene.phaseManager.unshiftPhase(this.getNewHitPhase()); } else { // Queue message for number of hits made by multi-move // If multi-hit attack only hits once, still want to render a message const hitsTotal = user.turnData.hitCount - Math.max(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 - globalScene.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal })); + globalScene.phaseManager.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal })); } globalScene.applyModifiers(HitHealModifier, this.player, user); this.getTargets().forEach(target => (target.turnData.moveEffectiveness = null)); @@ -858,7 +858,7 @@ export class MoveEffectPhase extends PokemonPhase { }); if (isCritical) { - globalScene.queueMessage(i18next.t("battle:hitResultCriticalHit")); + globalScene.phaseManager.queueMessage(i18next.t("battle:hitResultCriticalHit")); } if (damage <= 0) { @@ -901,9 +901,9 @@ export class MoveEffectPhase extends PokemonPhase { */ protected onFaintTarget(user: Pokemon, target: Pokemon): void { // 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.unshiftPhase(new FaintPhase(target.getBattlerIndex(), false, user)); target.destroySubstitute(); target.lapseTag(BattlerTagType.COMMANDED); @@ -936,7 +936,7 @@ export class MoveEffectPhase extends PokemonPhase { break; } if (msg) { - globalScene.queueMessage(msg); + globalScene.phaseManager.queueMessage(msg); } } diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 300c27e01fc..03f94ad3d1d 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -268,10 +268,10 @@ export class MovePhase extends BattlePhase { if (activated) { this.cancel(); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( getStatusEffectActivationText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)), ); - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new CommonAnimPhase( this.pokemon.getBattlerIndex(), undefined, @@ -279,7 +279,7 @@ export class MovePhase extends BattlePhase { ), ); } else if (healed) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( getStatusEffectHealText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)), ); this.pokemon.resetStatus(); @@ -407,7 +407,7 @@ export class MovePhase extends BattlePhase { if (success) { const move = this.move.getMove(); applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, move); - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new MoveEffectPhase(this.pokemon.getBattlerIndex(), this.targets, move, this.reflected, this.move.virtual), ); } else { @@ -457,7 +457,9 @@ export class MovePhase extends BattlePhase { applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove()); this.showMoveText(); - globalScene.unshiftPhase(new MoveChargePhase(this.pokemon.getBattlerIndex(), this.targets[0], this.move)); + globalScene.phaseManager.unshiftPhase( + new MoveChargePhase(this.pokemon.getBattlerIndex(), this.targets[0], this.move), + ); } else { this.pokemon.pushMoveHistory({ move: this.move.moveId, @@ -479,7 +481,7 @@ export class MovePhase extends BattlePhase { * Queues a {@linkcode MoveEndPhase} and then ends the phase */ public end(): void { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new MoveEndPhase(this.pokemon.getBattlerIndex(), this.getActiveTargetPokemon(), this.followUp), ); @@ -545,12 +547,12 @@ export class MovePhase extends BattlePhase { if (this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr)) { redirectTarget.value = currentTarget; // TODO: Ability displays should be handled by the ability - globalScene.queueAbilityDisplay( + globalScene.phaseManager.queueAbilityDisplay( this.pokemon, this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr), true, ); - globalScene.queueAbilityDisplay( + globalScene.phaseManager.queueAbilityDisplay( this.pokemon, this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr), false, @@ -649,7 +651,7 @@ export class MovePhase extends BattlePhase { return; } - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t(this.reflected ? "battle:magicCoatActivated" : "battle:useMove", { pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon), moveName: this.move.getName(), @@ -660,6 +662,6 @@ export class MovePhase extends BattlePhase { } public showFailedText(failedText: string = i18next.t("battle:attackFailed")): void { - globalScene.queueMessage(failedText); + globalScene.phaseManager.queueMessage(failedText); } } diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index 5365ab3da32..b1ca11d45a5 100644 --- a/src/phases/mystery-encounter-phases.ts +++ b/src/phases/mystery-encounter-phases.ts @@ -58,8 +58,8 @@ export class MysteryEncounterPhase extends Phase { super.start(); // Clears out queued phases that are part of standard battle - globalScene.clearPhaseQueue(); - globalScene.clearPhaseQueueSplice(); + globalScene.phaseManager.clearPhaseQueue(); + globalScene.phaseManager.clearPhaseQueueSplice(); const encounter = globalScene.currentBattle.mysteryEncounter!; encounter.updateSeedOffset(); @@ -124,7 +124,7 @@ export class MysteryEncounterPhase extends Phase { */ continueEncounter() { const endDialogueAndContinueEncounter = () => { - globalScene.pushPhase(new MysteryEncounterOptionSelectedPhase()); + globalScene.phaseManager.pushPhase(new MysteryEncounterOptionSelectedPhase()); this.end(); }; @@ -247,8 +247,8 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase { }); // Remove any status tick phases - while (globalScene.findPhase(p => p.is("PostTurnStatusEffectPhase"))) { - globalScene.tryRemovePhase(p => p.is("PostTurnStatusEffectPhase")); + while (globalScene.phaseManager.findPhase(p => p.is("PostTurnStatusEffectPhase"))) { + globalScene.phaseManager.tryRemovePhase(p => p.is("PostTurnStatusEffectPhase")); } // The total number of Pokemon in the player's party that can legally fight @@ -256,7 +256,7 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase { // The total number of legal player Pokemon that aren't currently on the field const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true)); if (!legalPlayerPokemon.length) { - globalScene.unshiftPhase(new GameOverPhase()); + globalScene.phaseManager.unshiftPhase(new GameOverPhase()); return this.end(); } @@ -265,13 +265,13 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase { const playerField = globalScene.getPlayerField(); playerField.forEach((pokemon, i) => { if (!pokemon.isAllowedInBattle() && legalPlayerPartyPokemon.length > i) { - globalScene.unshiftPhase(new SwitchPhase(SwitchType.SWITCH, i, true, false)); + globalScene.phaseManager.unshiftPhase(new SwitchPhase(SwitchType.SWITCH, i, true, false)); } }); // 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) { - globalScene.unshiftPhase(new ToggleDoublePositionPhase(true)); + globalScene.phaseManager.unshiftPhase(new ToggleDoublePositionPhase(true)); } this.end(); @@ -348,9 +348,9 @@ export class MysteryEncounterBattlePhase extends Phase { globalScene.playBgm(); } const availablePartyMembers = globalScene.getEnemyParty().filter(p => !p.isFainted()).length; - globalScene.unshiftPhase(new SummonPhase(0, false)); + globalScene.phaseManager.unshiftPhase(new SummonPhase(0, false)); if (globalScene.currentBattle.double && availablePartyMembers > 1) { - globalScene.unshiftPhase(new SummonPhase(1, false)); + globalScene.phaseManager.unshiftPhase(new SummonPhase(1, false)); } if (!globalScene.currentBattle.mysteryEncounter?.hideBattleIntroMessage) { @@ -368,9 +368,9 @@ export class MysteryEncounterBattlePhase extends Phase { const doTrainerSummon = () => { this.hideEnemyTrainer(); const availablePartyMembers = globalScene.getEnemyParty().filter(p => !p.isFainted()).length; - globalScene.unshiftPhase(new SummonPhase(0, false)); + globalScene.phaseManager.unshiftPhase(new SummonPhase(0, false)); if (globalScene.currentBattle.double && availablePartyMembers > 1) { - globalScene.unshiftPhase(new SummonPhase(1, false)); + globalScene.phaseManager.unshiftPhase(new SummonPhase(1, false)); } this.endBattleSetup(); }; @@ -426,37 +426,37 @@ export class MysteryEncounterBattlePhase extends Phase { if (encounterMode !== MysteryEncounterMode.TRAINER_BATTLE) { const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier); if (ivScannerModifier) { - enemyField.map(p => globalScene.pushPhase(new ScanIvsPhase(p.getBattlerIndex()))); + enemyField.map(p => globalScene.phaseManager.pushPhase(new ScanIvsPhase(p.getBattlerIndex()))); } } const availablePartyMembers = globalScene.getPlayerParty().filter(p => p.isAllowedInBattle()); if (!availablePartyMembers[0].isOnField()) { - globalScene.pushPhase(new SummonPhase(0)); + globalScene.phaseManager.pushPhase(new SummonPhase(0)); } if (globalScene.currentBattle.double) { if (availablePartyMembers.length > 1) { - globalScene.pushPhase(new ToggleDoublePositionPhase(true)); + globalScene.phaseManager.pushPhase(new ToggleDoublePositionPhase(true)); if (!availablePartyMembers[1].isOnField()) { - globalScene.pushPhase(new SummonPhase(1)); + globalScene.phaseManager.pushPhase(new SummonPhase(1)); } } } else { if (availablePartyMembers.length > 1 && availablePartyMembers[1].isOnField()) { globalScene.getPlayerField().forEach(pokemon => pokemon.lapseTag(BattlerTagType.COMMANDED)); - globalScene.pushPhase(new ReturnPhase(1)); + globalScene.phaseManager.pushPhase(new ReturnPhase(1)); } - globalScene.pushPhase(new ToggleDoublePositionPhase(false)); + globalScene.phaseManager.pushPhase(new ToggleDoublePositionPhase(false)); } if (encounterMode !== MysteryEncounterMode.TRAINER_BATTLE && !this.disableSwitch) { const minPartySize = globalScene.currentBattle.double ? 2 : 1; if (availablePartyMembers.length > minPartySize) { - globalScene.pushPhase(new CheckSwitchPhase(0, globalScene.currentBattle.double)); + globalScene.phaseManager.pushPhase(new CheckSwitchPhase(0, globalScene.currentBattle.double)); if (globalScene.currentBattle.double) { - globalScene.pushPhase(new CheckSwitchPhase(1, globalScene.currentBattle.double)); + globalScene.phaseManager.pushPhase(new CheckSwitchPhase(1, globalScene.currentBattle.double)); } } } @@ -562,8 +562,8 @@ export class MysteryEncounterRewardsPhase extends Phase { if (encounter.doEncounterRewards) { encounter.doEncounterRewards(); } else if (this.addHealPhase) { - globalScene.tryRemovePhase(p => p.is("SelectModifierPhase")); - globalScene.unshiftPhase( + globalScene.phaseManager.tryRemovePhase(p => p.is("SelectModifierPhase")); + globalScene.phaseManager.unshiftPhase( new SelectModifierPhase(0, undefined, { fillRemaining: false, rerollMultiplier: -1, @@ -571,7 +571,7 @@ export class MysteryEncounterRewardsPhase extends Phase { ); } - globalScene.pushPhase(new PostMysteryEncounterPhase()); + globalScene.phaseManager.pushPhase(new PostMysteryEncounterPhase()); this.end(); } } @@ -618,10 +618,10 @@ export class PostMysteryEncounterPhase extends Phase { continueEncounter() { const endPhase = () => { if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) { - globalScene.pushPhase(new SelectBiomePhase()); + globalScene.phaseManager.pushPhase(new SelectBiomePhase()); } - globalScene.pushPhase(new NewBattlePhase()); + globalScene.phaseManager.pushPhase(new NewBattlePhase()); this.end(); }; diff --git a/src/phases/new-battle-phase.ts b/src/phases/new-battle-phase.ts index c4cfc72fb53..65ecc81df2d 100644 --- a/src/phases/new-battle-phase.ts +++ b/src/phases/new-battle-phase.ts @@ -7,9 +7,11 @@ export class NewBattlePhase extends BattlePhase { super.start(); // cull any extra `NewBattle` phases from the queue. - globalScene.phaseQueue = globalScene.phaseQueue.filter(phase => !phase.is("NewBattlePhase")); + globalScene.phaseManager.phaseQueue = globalScene.phaseManager.phaseQueue.filter( + phase => !phase.is("NewBattlePhase"), + ); // `phaseQueuePrepend` is private, so we have to use this inefficient loop. - while (globalScene.tryRemoveUnshiftedPhase(phase => phase.is("NewBattlePhase"))) {} + while (globalScene.phaseManager.tryRemoveUnshiftedPhase(phase => phase.is("NewBattlePhase"))) {} globalScene.newBattle(); diff --git a/src/phases/obtain-status-effect-phase.ts b/src/phases/obtain-status-effect-phase.ts index 820db910681..2982bc982d9 100644 --- a/src/phases/obtain-status-effect-phase.ts +++ b/src/phases/obtain-status-effect-phase.ts @@ -41,7 +41,7 @@ export class ObtainStatusEffectPhase extends PokemonPhase { } pokemon.updateInfo(true); new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(false, () => { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( getStatusEffectObtainText( this.statusEffect, getPokemonNameWithAffix(pokemon), @@ -59,7 +59,7 @@ export class ObtainStatusEffectPhase extends PokemonPhase { return; } } else if (pokemon.status?.effect === this.statusEffect) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( getStatusEffectOverlapText(this.statusEffect ?? StatusEffect.NONE, getPokemonNameWithAffix(pokemon)), ); } diff --git a/src/phases/pokemon-heal-phase.ts b/src/phases/pokemon-heal-phase.ts index 60bbb17c30a..6e9b6b5c622 100644 --- a/src/phases/pokemon-heal-phase.ts +++ b/src/phases/pokemon-heal-phase.ts @@ -68,7 +68,7 @@ export class PokemonHealPhase extends CommonAnimPhase { let lastStatusEffect = StatusEffect.NONE; if (healBlock && this.hpHealed > 0) { - globalScene.queueMessage(healBlock.onActivation(pokemon)); + globalScene.phaseManager.queueMessage(healBlock.onActivation(pokemon)); this.message = null; return super.end(); } @@ -119,11 +119,13 @@ export class PokemonHealPhase extends CommonAnimPhase { } if (this.message) { - globalScene.queueMessage(this.message); + globalScene.phaseManager.queueMessage(this.message); } if (this.healStatus && lastStatusEffect && !hasMessage) { - globalScene.queueMessage(getStatusEffectHealText(lastStatusEffect, getPokemonNameWithAffix(pokemon))); + globalScene.phaseManager.queueMessage( + getStatusEffectHealText(lastStatusEffect, getPokemonNameWithAffix(pokemon)), + ); } if (!healOrDamage && !lastStatusEffect) { diff --git a/src/phases/pokemon-transform-phase.ts b/src/phases/pokemon-transform-phase.ts index c0f3b048003..4f18a19b2fb 100644 --- a/src/phases/pokemon-transform-phase.ts +++ b/src/phases/pokemon-transform-phase.ts @@ -65,7 +65,7 @@ export class PokemonTransformPhase extends PokemonPhase { globalScene.playSound("battle_anims/PRSFX- Transform"); } - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("abilityTriggers:postSummonTransform", { pokemonNameWithAffix: getPokemonNameWithAffix(user), targetName: target.name, diff --git a/src/phases/post-game-over-phase.ts b/src/phases/post-game-over-phase.ts index f985419da7a..37a3297cc52 100644 --- a/src/phases/post-game-over-phase.ts +++ b/src/phases/post-game-over-phase.ts @@ -28,7 +28,7 @@ export class PostGameOverPhase extends Phase { return globalScene.reset(true); } globalScene.reset(); - globalScene.unshiftPhase(new TitlePhase()); + globalScene.phaseManager.unshiftPhase(new TitlePhase()); this.end(); }); }); diff --git a/src/phases/post-turn-status-effect-phase.ts b/src/phases/post-turn-status-effect-phase.ts index 47a84059745..33fb012492d 100644 --- a/src/phases/post-turn-status-effect-phase.ts +++ b/src/phases/post-turn-status-effect-phase.ts @@ -32,7 +32,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { applyAbAttrs(BlockStatusDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( getStatusEffectActivationText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)), ); const damage = new NumberHolder(0); diff --git a/src/phases/quiet-form-change-phase.ts b/src/phases/quiet-form-change-phase.ts index f677dcc48df..363b026831f 100644 --- a/src/phases/quiet-form-change-phase.ts +++ b/src/phases/quiet-form-change-phase.ts @@ -159,7 +159,7 @@ export class QuietFormChangePhase extends BattlePhase { this.pokemon.findAndRemoveTags(t => t.tagType === BattlerTagType.AUTOTOMIZED); if (globalScene?.currentBattle.battleSpec === BattleSpec.FINAL_BOSS && this.pokemon.isEnemy()) { globalScene.playBgm(); - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new PokemonHealPhase(this.pokemon.getBattlerIndex(), this.pokemon.getMaxHp(), null, false, false, false, true), ); this.pokemon.findAndRemoveTags(() => true); @@ -168,7 +168,9 @@ export class QuietFormChangePhase extends BattlePhase { this.pokemon.initBattleInfo(); this.pokemon.cry(); - const movePhase = globalScene.findPhase(p => p.is("MovePhase") && p.pokemon === this.pokemon) as MovePhase; + const movePhase = globalScene.phaseManager.findPhase( + p => p.is("MovePhase") && p.pokemon === this.pokemon, + ) as MovePhase; if (movePhase) { movePhase.cancel(); } diff --git a/src/phases/revival-blessing-phase.ts b/src/phases/revival-blessing-phase.ts index 3f70c93dd7a..2db73103a88 100644 --- a/src/phases/revival-blessing-phase.ts +++ b/src/phases/revival-blessing-phase.ts @@ -35,7 +35,7 @@ export class RevivalBlessingPhase extends BattlePhase { pokemon.resetTurnData(); pokemon.resetStatus(true, false, false, false); pokemon.heal(Math.min(toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp())); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("moveTriggers:revivalBlessing", { pokemonName: pokemon.name, }), @@ -51,16 +51,16 @@ export class RevivalBlessingPhase extends BattlePhase { ) { if (slotIndex <= 1) { // Revived ally pokemon - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new SwitchSummonPhase(SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, true), ); - globalScene.unshiftPhase(new ToggleDoublePositionPhase(true)); + globalScene.phaseManager.unshiftPhase(new ToggleDoublePositionPhase(true)); } else if (allyPokemon.isFainted()) { // Revived party pokemon, and ally pokemon is fainted - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, true), ); - globalScene.unshiftPhase(new ToggleDoublePositionPhase(true)); + globalScene.phaseManager.unshiftPhase(new ToggleDoublePositionPhase(true)); } } } diff --git a/src/phases/select-biome-phase.ts b/src/phases/select-biome-phase.ts index ef6b39e8b8f..633bf20d34c 100644 --- a/src/phases/select-biome-phase.ts +++ b/src/phases/select-biome-phase.ts @@ -22,9 +22,9 @@ export class SelectBiomePhase extends BattlePhase { const setNextBiome = (nextBiome: BiomeId) => { if (nextWaveIndex % 10 === 1) { globalScene.applyModifiers(MoneyInterestModifier, true); - globalScene.unshiftPhase(new PartyHealPhase(false)); + globalScene.phaseManager.unshiftPhase(new PartyHealPhase(false)); } - globalScene.unshiftPhase(new SwitchBiomePhase(nextBiome)); + globalScene.phaseManager.unshiftPhase(new SwitchBiomePhase(nextBiome)); this.end(); }; diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 6e429d9ad7f..5ac3b9e6d76 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -123,7 +123,7 @@ export class SelectModifierPhase extends BattlePhase { return false; } globalScene.reroll = true; - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new SelectModifierPhase( this.rerollCount + 1, this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[], @@ -247,7 +247,7 @@ export class SelectModifierPhase extends BattlePhase { // If the player selects either of these, then escapes out of consuming them, // they are returned to a shop in the same state. if (modifier.type instanceof RememberMoveModifierType || modifier.type instanceof TmModifierType) { - globalScene.unshiftPhase(this.copy()); + globalScene.phaseManager.unshiftPhase(this.copy()); } if (cost && !(modifier.type instanceof RememberMoveModifierType)) { diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index d25c2dd7211..2b60fcaf054 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -25,8 +25,8 @@ export class SelectStarterPhase extends Phase { globalScene.ui.clearText(); globalScene.ui.setMode(UiMode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: number) => { if (slotId === -1) { - globalScene.clearPhaseQueue(); - globalScene.pushPhase(new TitlePhase()); + globalScene.phaseManager.clearPhaseQueue(); + globalScene.phaseManager.pushPhase(new TitlePhase()); return this.end(); } globalScene.sessionSlotId = slotId; diff --git a/src/phases/select-target-phase.ts b/src/phases/select-target-phase.ts index 46ba378a56c..515f7ed98ca 100644 --- a/src/phases/select-target-phase.ts +++ b/src/phases/select-target-phase.ts @@ -28,12 +28,12 @@ export class SelectTargetPhase extends PokemonPhase { const errorMessage = user .getRestrictingTag(move!, user, fieldSide[targets[0]])! .selectionDeniedText(user, moveObject.id); - globalScene.queueMessage(i18next.t(errorMessage, { moveName: moveObject.name }), 0, true); + globalScene.phaseManager.queueMessage(i18next.t(errorMessage, { moveName: moveObject.name }), 0, true); targets = []; } if (targets.length < 1) { globalScene.currentBattle.turnCommands[this.fieldIndex] = null; - globalScene.unshiftPhase(new CommandPhase(this.fieldIndex)); + globalScene.phaseManager.unshiftPhase(new CommandPhase(this.fieldIndex)); } else { turnCommand!.targets = targets; //TODO: is the bang correct here? } diff --git a/src/phases/show-ability-phase.ts b/src/phases/show-ability-phase.ts index 81aa69537e5..e4be6124784 100644 --- a/src/phases/show-ability-phase.ts +++ b/src/phases/show-ability-phase.ts @@ -36,8 +36,8 @@ export class ShowAbilityPhase extends PokemonPhase { // If the bar is already out, hide it before showing the new one if (globalScene.abilityBar.isVisible()) { - globalScene.unshiftPhase(new HideAbilityPhase()); - globalScene.unshiftPhase(new ShowAbilityPhase(this.battlerIndex, this.passive)); + globalScene.phaseManager.unshiftPhase(new HideAbilityPhase()); + globalScene.phaseManager.unshiftPhase(new ShowAbilityPhase(this.battlerIndex, this.passive)); return this.end(); } diff --git a/src/phases/show-party-exp-bar-phase.ts b/src/phases/show-party-exp-bar-phase.ts index 6b4236f0868..765bd498f66 100644 --- a/src/phases/show-party-exp-bar-phase.ts +++ b/src/phases/show-party-exp-bar-phase.ts @@ -29,9 +29,9 @@ export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { pokemon.addExp(exp.value); const newLevel = pokemon.level; if (newLevel > lastLevel) { - globalScene.unshiftPhase(new LevelUpPhase(this.partyMemberIndex, lastLevel, newLevel)); + globalScene.phaseManager.unshiftPhase(new LevelUpPhase(this.partyMemberIndex, lastLevel, newLevel)); } - globalScene.unshiftPhase(new HidePartyExpBarPhase()); + globalScene.phaseManager.unshiftPhase(new HidePartyExpBarPhase()); pokemon.updateInfo(); if (globalScene.expParty === ExpNotification.SKIP) { diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index baa93c63099..49f3952ef01 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -72,7 +72,7 @@ export class StatStageChangePhase extends PokemonPhase { if (this.stats.length > 1) { for (let i = 0; i < this.stats.length; i++) { const stat = [this.stats[i]]; - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new StatStageChangePhase( this.battlerIndex, this.selfTarget, @@ -212,7 +212,7 @@ export class StatStageChangePhase extends PokemonPhase { if (this.showMessage) { const messages = this.getStatStageChangeMessages(filteredStats, stages.value, relLevels); for (const message of messages) { - globalScene.queueMessage(message); + globalScene.phaseManager.queueMessage(message); } } @@ -235,7 +235,7 @@ export class StatStageChangePhase extends PokemonPhase { applyPostStatStageChangeAbAttrs(PostStatStageChangeAbAttr, pokemon, filteredStats, this.stages, this.selfTarget); // Look for any other stat change phases; if this is the last one, do White Herb check - const existingPhase = globalScene.findPhase( + const existingPhase = globalScene.phaseManager.findPhase( p => p.is("StatStageChangePhase") && p.battlerIndex === this.battlerIndex, ); if (!existingPhase?.is("StatStageChangePhase")) { @@ -315,7 +315,7 @@ export class StatStageChangePhase extends PokemonPhase { let existingPhase: StatStageChangePhase; if (this.stats.length === 1) { while ( - (existingPhase = globalScene.findPhase( + (existingPhase = globalScene.phaseManager.findPhase( p => p.is("StatStageChangePhase") && p.battlerIndex === this.battlerIndex && @@ -328,13 +328,13 @@ export class StatStageChangePhase extends PokemonPhase { ) { this.stages += existingPhase.stages; - if (!globalScene.tryRemovePhase(p => p === existingPhase)) { + if (!globalScene.phaseManager.tryRemovePhase(p => p === existingPhase)) { break; } } } while ( - (existingPhase = globalScene.findPhase( + (existingPhase = globalScene.phaseManager.findPhase( p => p.is("StatStageChangePhase") && p.battlerIndex === this.battlerIndex && @@ -346,7 +346,7 @@ export class StatStageChangePhase extends PokemonPhase { ) as StatStageChangePhase) ) { this.stats.push(...existingPhase.stats); - if (!globalScene.tryRemovePhase(p => p === existingPhase)) { + if (!globalScene.phaseManager.tryRemovePhase(p => p === existingPhase)) { break; } } diff --git a/src/phases/summon-phase.ts b/src/phases/summon-phase.ts index 2cd7b122bb3..15f42e76a5a 100644 --- a/src/phases/summon-phase.ts +++ b/src/phases/summon-phase.ts @@ -57,8 +57,8 @@ export class SummonPhase extends PartyMemberPokemonPhase { if (legalIndex === -1) { console.error("Party Details:\n", party); console.error("All available Pokemon were fainted or illegal!"); - globalScene.clearPhaseQueue(); - globalScene.unshiftPhase(new GameOverPhase()); + globalScene.phaseManager.clearPhaseQueue(); + globalScene.phaseManager.unshiftPhase(new GameOverPhase()); this.end(); return; } @@ -275,7 +275,7 @@ export class SummonPhase extends PartyMemberPokemonPhase { const pokemon = this.getPokemon(); if (pokemon.isShiny(true)) { - globalScene.unshiftPhase(new ShinySparklePhase(pokemon.getBattlerIndex())); + globalScene.phaseManager.unshiftPhase(new ShinySparklePhase(pokemon.getBattlerIndex())); } pokemon.resetTurnData(); @@ -291,7 +291,7 @@ export class SummonPhase extends PartyMemberPokemonPhase { } queuePostSummon(): void { - globalScene.pushPhase(new PostSummonPhase(this.getPokemon().getBattlerIndex())); + globalScene.phaseManager.pushPhase(new PostSummonPhase(this.getPokemon().getBattlerIndex())); } end() { diff --git a/src/phases/switch-phase.ts b/src/phases/switch-phase.ts index 6017aa0fa70..5f2ae55900e 100644 --- a/src/phases/switch-phase.ts +++ b/src/phases/switch-phase.ts @@ -76,9 +76,13 @@ export class SwitchPhase extends BattlePhase { if (slotIndex >= globalScene.currentBattle.getBattlerCount() && slotIndex < 6) { // Remove any pre-existing PostSummonPhase under the same field index. // Pre-existing PostSummonPhases may occur when this phase is invoked during a prompt to switch at the start of a wave. - globalScene.tryRemovePhase(p => p.is("PostSummonPhase") && p.player && p.fieldIndex === this.fieldIndex); + globalScene.phaseManager.tryRemovePhase( + p => p.is("PostSummonPhase") && p.player && p.fieldIndex === this.fieldIndex, + ); const switchType = option === PartyOption.PASS_BATON ? SwitchType.BATON_PASS : this.switchType; - globalScene.unshiftPhase(new SwitchSummonPhase(switchType, fieldIndex, slotIndex, this.doReturn)); + globalScene.phaseManager.unshiftPhase( + new SwitchSummonPhase(switchType, fieldIndex, slotIndex, this.doReturn), + ); } globalScene.ui.setMode(UiMode.MESSAGE).then(() => super.end()); }, diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index 1872dae1f1f..de8b9f3a5d9 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -265,6 +265,6 @@ export class SwitchSummonPhase extends SummonPhase { } queuePostSummon(): void { - globalScene.unshiftPhase(new PostSummonPhase(this.getPokemon().getBattlerIndex())); + globalScene.phaseManager.unshiftPhase(new PostSummonPhase(this.getPokemon().getBattlerIndex())); } } diff --git a/src/phases/tera-phase.ts b/src/phases/tera-phase.ts index 5e4ea2fe54e..5f403f5419e 100644 --- a/src/phases/tera-phase.ts +++ b/src/phases/tera-phase.ts @@ -21,7 +21,7 @@ export class TeraPhase extends BattlePhase { start() { super.start(); - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( i18next.t("battle:pokemonTerastallized", { pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon), type: i18next.t(`pokemonInfo:Type.${PokemonType[this.pokemon.getTeraType()]}`), diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index aa9ae49ca8b..fb980d87359 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -124,8 +124,8 @@ export class TitlePhase extends Phase { options.push({ label: i18next.t("menu:cancel"), handler: () => { - globalScene.clearPhaseQueue(); - globalScene.pushPhase(new TitlePhase()); + globalScene.phaseManager.clearPhaseQueue(); + globalScene.phaseManager.pushPhase(new TitlePhase()); super.end(); return true; }, @@ -198,9 +198,9 @@ export class TitlePhase extends Phase { initDailyRun(): void { globalScene.ui.clearText(); globalScene.ui.setMode(UiMode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: number) => { - globalScene.clearPhaseQueue(); + globalScene.phaseManager.clearPhaseQueue(); if (slotId === -1) { - globalScene.pushPhase(new TitlePhase()); + globalScene.phaseManager.pushPhase(new TitlePhase()); return super.end(); } globalScene.sessionSlotId = slotId; @@ -304,23 +304,23 @@ export class TitlePhase extends Phase { globalScene.arena.preloadBgm(); globalScene.gameMode = getGameMode(this.gameMode); if (this.gameMode === GameModes.CHALLENGE) { - globalScene.pushPhase(new SelectChallengePhase()); + globalScene.phaseManager.pushPhase(new SelectChallengePhase()); } else { - globalScene.pushPhase(new SelectStarterPhase()); + globalScene.phaseManager.pushPhase(new SelectStarterPhase()); } globalScene.newArena(globalScene.gameMode.getStartingBiome()); } else { globalScene.playBgm(); } - globalScene.pushPhase(new EncounterPhase(this.loaded)); + globalScene.phaseManager.pushPhase(new EncounterPhase(this.loaded)); if (this.loaded) { const availablePartyMembers = globalScene.getPokemonAllowedInBattle().length; - globalScene.pushPhase(new SummonPhase(0, true, true)); + globalScene.phaseManager.pushPhase(new SummonPhase(0, true, true)); if (globalScene.currentBattle.double && availablePartyMembers > 1) { - globalScene.pushPhase(new SummonPhase(1, true, true)); + globalScene.phaseManager.pushPhase(new SummonPhase(1, true, true)); } if ( @@ -329,9 +329,9 @@ export class TitlePhase extends Phase { ) { const minPartySize = globalScene.currentBattle.double ? 2 : 1; if (availablePartyMembers > minPartySize) { - globalScene.pushPhase(new CheckSwitchPhase(0, globalScene.currentBattle.double)); + globalScene.phaseManager.pushPhase(new CheckSwitchPhase(0, globalScene.currentBattle.double)); if (globalScene.currentBattle.double) { - globalScene.pushPhase(new CheckSwitchPhase(1, globalScene.currentBattle.double)); + globalScene.phaseManager.pushPhase(new CheckSwitchPhase(1, globalScene.currentBattle.double)); } } } diff --git a/src/phases/trainer-victory-phase.ts b/src/phases/trainer-victory-phase.ts index bd035248530..5b7b26d52fb 100644 --- a/src/phases/trainer-victory-phase.ts +++ b/src/phases/trainer-victory-phase.ts @@ -20,11 +20,13 @@ export class TrainerVictoryPhase extends BattlePhase { globalScene.playBgm(globalScene.currentBattle.trainer?.config.victoryBgm); - globalScene.unshiftPhase(new MoneyRewardPhase(globalScene.currentBattle.trainer?.config.moneyMultiplier!)); // TODO: is this bang correct? + globalScene.phaseManager.unshiftPhase( + new MoneyRewardPhase(globalScene.currentBattle.trainer?.config.moneyMultiplier!), + ); // TODO: is this bang correct? const modifierRewardFuncs = globalScene.currentBattle.trainer?.config.modifierRewardFuncs!; // TODO: is this bang correct? for (const modifierRewardFunc of modifierRewardFuncs) { - globalScene.unshiftPhase(new ModifierRewardPhase(modifierRewardFunc)); + globalScene.phaseManager.unshiftPhase(new ModifierRewardPhase(modifierRewardFunc)); } const trainerType = globalScene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct? @@ -35,7 +37,7 @@ export class TrainerVictoryPhase extends BattlePhase { globalScene.currentBattle.trainer?.config.isBoss ) { if (timedEventManager.getUpgradeUnlockedVouchers()) { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new ModifierRewardPhase( [ modifierTypes.VOUCHER_PLUS, @@ -46,7 +48,7 @@ export class TrainerVictoryPhase extends BattlePhase { ), ); } else { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new ModifierRewardPhase( [modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM][ vouchers[TrainerType[trainerType]].voucherType diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index 4d486c4bbd6..832f60043ce 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -25,7 +25,7 @@ export class TurnEndPhase extends FieldPhase { globalScene.currentBattle.incrementTurn(); globalScene.eventTarget.dispatchEvent(new TurnEndEvent(globalScene.currentBattle.turn)); - globalScene.hideAbilityBar(); + globalScene.phaseManager.hideAbilityBar(); const handlePokemon = (pokemon: Pokemon) => { if (!pokemon.switchOutStatus) { @@ -34,7 +34,7 @@ export class TurnEndPhase extends FieldPhase { globalScene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon); if (globalScene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new PokemonHealPhase( pokemon.getBattlerIndex(), Math.max(pokemon.getMaxHp() >> 4, 1), diff --git a/src/phases/turn-init-phase.ts b/src/phases/turn-init-phase.ts index 7f94acd3b32..61ec5fd8a71 100644 --- a/src/phases/turn-init-phase.ts +++ b/src/phases/turn-init-phase.ts @@ -22,14 +22,18 @@ export class TurnInitPhase extends FieldPhase { globalScene.getPlayerField().forEach(p => { // If this pokemon is in play and evolved into something illegal under the current challenge, force a switch if (p.isOnField() && !p.isAllowedInBattle()) { - globalScene.queueMessage(i18next.t("challenges:illegalEvolution", { pokemon: p.name }), null, true); + globalScene.phaseManager.queueMessage( + i18next.t("challenges:illegalEvolution", { pokemon: p.name }), + null, + true, + ); const allowedPokemon = globalScene.getPokemonAllowedInBattle(); if (!allowedPokemon.length) { // If there are no longer any legal pokemon in the party, game over. - globalScene.clearPhaseQueue(); - globalScene.unshiftPhase(new GameOverPhase()); + globalScene.phaseManager.clearPhaseQueue(); + globalScene.phaseManager.unshiftPhase(new GameOverPhase()); } else if ( allowedPokemon.length >= globalScene.currentBattle.getBattlerCount() || (globalScene.currentBattle.double && !allowedPokemon[0].isActive(true)) @@ -42,7 +46,7 @@ export class TurnInitPhase extends FieldPhase { p.leaveField(); } if (allowedPokemon.length === 1 && globalScene.currentBattle.double) { - globalScene.unshiftPhase(new ToggleDoublePositionPhase(true)); + globalScene.phaseManager.unshiftPhase(new ToggleDoublePositionPhase(true)); } } }); @@ -65,11 +69,13 @@ export class TurnInitPhase extends FieldPhase { pokemon.resetTurnData(); - globalScene.pushPhase(pokemon.isPlayer() ? new CommandPhase(i) : new EnemyCommandPhase(i - BattlerIndex.ENEMY)); + globalScene.phaseManager.pushPhase( + pokemon.isPlayer() ? new CommandPhase(i) : new EnemyCommandPhase(i - BattlerIndex.ENEMY), + ); } }); - globalScene.pushPhase(new TurnStartPhase()); + globalScene.phaseManager.pushPhase(new TurnStartPhase()); this.end(); } diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index 2d009b30bf3..c07feac0888 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -153,7 +153,7 @@ export class TurnStartPhase extends FieldPhase { switch (preTurnCommand?.command) { case Command.TERA: - globalScene.pushPhase(new TeraPhase(pokemon)); + globalScene.phaseManager.pushPhase(new TeraPhase(pokemon)); } } @@ -176,11 +176,13 @@ export class TurnStartPhase extends FieldPhase { pokemon.getMoveset().find(m => m.moveId === queuedMove.move && m.ppUsed < m.getMovePp()) || new PokemonMove(queuedMove.move); if (move.getMove().hasAttr(MoveHeaderAttr)) { - globalScene.unshiftPhase(new MoveHeaderPhase(pokemon, move)); + globalScene.phaseManager.unshiftPhase(new MoveHeaderPhase(pokemon, move)); } if (pokemon.isPlayer()) { if (turnCommand.cursor === -1) { - globalScene.pushPhase(new MovePhase(pokemon, turnCommand.targets || turnCommand.move!.targets, move)); //TODO: is the bang correct here? + globalScene.phaseManager.pushPhase( + new MovePhase(pokemon, turnCommand.targets || turnCommand.move!.targets, move), + ); //TODO: is the bang correct here? } else { const playerPhase = new MovePhase( pokemon, @@ -189,10 +191,10 @@ export class TurnStartPhase extends FieldPhase { false, queuedMove.ignorePP, ); //TODO: is the bang correct here? - globalScene.pushPhase(playerPhase); + globalScene.phaseManager.pushPhase(playerPhase); } } else { - globalScene.pushPhase( + globalScene.phaseManager.pushPhase( new MovePhase( pokemon, turnCommand.targets || turnCommand.move!.targets, @@ -204,11 +206,13 @@ export class TurnStartPhase extends FieldPhase { } break; case Command.BALL: - globalScene.unshiftPhase(new AttemptCapturePhase(turnCommand.targets![0] % 2, turnCommand.cursor!)); //TODO: is the bang correct here? + globalScene.phaseManager.unshiftPhase( + new AttemptCapturePhase(turnCommand.targets![0] % 2, turnCommand.cursor!), + ); //TODO: is the bang correct here? break; case Command.POKEMON: const switchType = turnCommand.args?.[0] ? SwitchType.BATON_PASS : SwitchType.SWITCH; - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new SwitchSummonPhase(switchType, pokemon.getFieldIndex(), turnCommand.cursor!, true, pokemon.isPlayer()), ); break; @@ -233,18 +237,18 @@ export class TurnStartPhase extends FieldPhase { runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon; } } - globalScene.unshiftPhase(new AttemptRunPhase(runningPokemon.getFieldIndex())); + globalScene.phaseManager.unshiftPhase(new AttemptRunPhase(runningPokemon.getFieldIndex())); break; } } - globalScene.pushPhase(new WeatherEffectPhase()); - globalScene.pushPhase(new BerryPhase()); + globalScene.phaseManager.pushPhase(new WeatherEffectPhase()); + globalScene.phaseManager.pushPhase(new BerryPhase()); /** Add a new phase to check who should be taking status damage */ - globalScene.pushPhase(new CheckStatusEffectPhase(moveOrder)); + globalScene.phaseManager.pushPhase(new CheckStatusEffectPhase(moveOrder)); - globalScene.pushPhase(new TurnEndPhase()); + globalScene.phaseManager.pushPhase(new TurnEndPhase()); /** * this.end() will call shiftPhase(), which dumps everything from PrependQueue (aka everything that is unshifted()) to the front diff --git a/src/phases/unavailable-phase.ts b/src/phases/unavailable-phase.ts index 4c4333ceb90..a6fc4a1be61 100644 --- a/src/phases/unavailable-phase.ts +++ b/src/phases/unavailable-phase.ts @@ -7,7 +7,7 @@ export class UnavailablePhase extends Phase { public readonly phaseName = "UnavailablePhase"; start(): void { globalScene.ui.setMode(UiMode.UNAVAILABLE, () => { - globalScene.unshiftPhase(new LoginPhase(true)); + globalScene.phaseManager.unshiftPhase(new LoginPhase(true)); this.end(); }); } diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts index 2d21f8abc08..0a08e010720 100644 --- a/src/phases/victory-phase.ts +++ b/src/phases/victory-phase.ts @@ -51,12 +51,12 @@ export class VictoryPhase extends PokemonPhase { .getEnemyParty() .find(p => (globalScene.currentBattle.battleType === BattleType.WILD ? p.isOnField() : !p?.isFainted(true))) ) { - globalScene.pushPhase(new BattleEndPhase(true)); + globalScene.phaseManager.pushPhase(new BattleEndPhase(true)); if (globalScene.currentBattle.battleType === BattleType.TRAINER) { - globalScene.pushPhase(new TrainerVictoryPhase()); + globalScene.phaseManager.pushPhase(new TrainerVictoryPhase()); } if (globalScene.gameMode.isEndless || !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) { - globalScene.pushPhase(new EggLapsePhase()); + globalScene.phaseManager.pushPhase(new EggLapsePhase()); if (globalScene.gameMode.isClassic) { switch (globalScene.currentBattle.waveIndex) { case ClassicFixedBossWaves.RIVAL_1: @@ -64,34 +64,36 @@ export class VictoryPhase extends PokemonPhase { // Get event modifiers for this wave timedEventManager .getFixedBattleEventRewards(globalScene.currentBattle.waveIndex) - .map(r => globalScene.pushPhase(new ModifierRewardPhase(modifierTypes[r]))); + .map(r => globalScene.phaseManager.pushPhase(new ModifierRewardPhase(modifierTypes[r]))); break; case ClassicFixedBossWaves.EVIL_BOSS_2: // Should get Lock Capsule on 165 before shop phase so it can be used in the rewards shop - globalScene.pushPhase(new ModifierRewardPhase(modifierTypes.LOCK_CAPSULE)); + globalScene.phaseManager.pushPhase(new ModifierRewardPhase(modifierTypes.LOCK_CAPSULE)); break; } } if (globalScene.currentBattle.waveIndex % 10) { - globalScene.pushPhase(new SelectModifierPhase(undefined, undefined, this.getFixedBattleCustomModifiers())); + globalScene.phaseManager.pushPhase( + new SelectModifierPhase(undefined, undefined, this.getFixedBattleCustomModifiers()), + ); } else if (globalScene.gameMode.isDaily) { - globalScene.pushPhase(new ModifierRewardPhase(modifierTypes.EXP_CHARM)); + globalScene.phaseManager.pushPhase(new ModifierRewardPhase(modifierTypes.EXP_CHARM)); if ( globalScene.currentBattle.waveIndex > 10 && !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex) ) { - globalScene.pushPhase(new ModifierRewardPhase(modifierTypes.GOLDEN_POKEBALL)); + globalScene.phaseManager.pushPhase(new ModifierRewardPhase(modifierTypes.GOLDEN_POKEBALL)); } } else { const superExpWave = !globalScene.gameMode.isEndless ? (globalScene.offsetGym ? 0 : 20) : 10; if (globalScene.gameMode.isEndless && globalScene.currentBattle.waveIndex === 10) { - globalScene.pushPhase(new ModifierRewardPhase(modifierTypes.EXP_SHARE)); + globalScene.phaseManager.pushPhase(new ModifierRewardPhase(modifierTypes.EXP_SHARE)); } if ( globalScene.currentBattle.waveIndex <= 750 && (globalScene.currentBattle.waveIndex <= 500 || globalScene.currentBattle.waveIndex % 30 === superExpWave) ) { - globalScene.pushPhase( + globalScene.phaseManager.pushPhase( new ModifierRewardPhase( globalScene.currentBattle.waveIndex % 30 !== superExpWave || globalScene.currentBattle.waveIndex > 250 ? modifierTypes.EXP_CHARM @@ -100,30 +102,30 @@ export class VictoryPhase extends PokemonPhase { ); } if (globalScene.currentBattle.waveIndex <= 150 && !(globalScene.currentBattle.waveIndex % 50)) { - globalScene.pushPhase(new ModifierRewardPhase(modifierTypes.GOLDEN_POKEBALL)); + globalScene.phaseManager.pushPhase(new ModifierRewardPhase(modifierTypes.GOLDEN_POKEBALL)); } if (globalScene.gameMode.isEndless && !(globalScene.currentBattle.waveIndex % 50)) { - globalScene.pushPhase( + globalScene.phaseManager.pushPhase( new ModifierRewardPhase( !(globalScene.currentBattle.waveIndex % 250) ? modifierTypes.VOUCHER_PREMIUM : modifierTypes.VOUCHER_PLUS, ), ); - globalScene.pushPhase(new AddEnemyBuffModifierPhase()); + globalScene.phaseManager.pushPhase(new AddEnemyBuffModifierPhase()); } } if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) { - globalScene.pushPhase(new SelectBiomePhase()); + globalScene.phaseManager.pushPhase(new SelectBiomePhase()); } - globalScene.pushPhase(new NewBattlePhase()); + globalScene.phaseManager.pushPhase(new NewBattlePhase()); } else { globalScene.currentBattle.battleType = BattleType.CLEAR; globalScene.score += globalScene.gameMode.getClearScoreBonus(); globalScene.updateScoreText(); - globalScene.pushPhase(new GameOverPhase(true)); + globalScene.phaseManager.pushPhase(new GameOverPhase(true)); } } diff --git a/src/phases/weather-effect-phase.ts b/src/phases/weather-effect-phase.ts index cd91b89771c..2918cd462df 100644 --- a/src/phases/weather-effect-phase.ts +++ b/src/phases/weather-effect-phase.ts @@ -65,7 +65,7 @@ export class WeatherEffectPhase extends CommonAnimPhase { const damage = toDmgValue(pokemon.getMaxHp() / 16); - globalScene.queueMessage(getWeatherDamageMessage(this.weather!.weatherType, pokemon) ?? ""); + globalScene.phaseManager.queueMessage(getWeatherDamageMessage(this.weather!.weatherType, pokemon) ?? ""); pokemon.damageAndUpdate(damage, { result: HitResult.INDIRECT, ignoreSegments: true }); }; diff --git a/src/system/game-data.ts b/src/system/game-data.ts index d9b8d73d3c3..9a24194b8e9 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -426,8 +426,8 @@ export class GameData { globalScene.ui.savingIcon.hide(); if (error) { if (error.startsWith("session out of date")) { - globalScene.clearPhaseQueue(); - globalScene.unshiftPhase(new ReloadSessionPhase()); + globalScene.phaseManager.clearPhaseQueue(); + globalScene.phaseManager.unshiftPhase(new ReloadSessionPhase()); } console.error(error); return resolve(false); @@ -459,7 +459,7 @@ export class GameData { saveDataOrErr[0] !== "{" ) { if (saveDataOrErr === 404) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( "Save data could not be found. If this is a new account, you can safely ignore this message.", null, true, @@ -467,7 +467,7 @@ export class GameData { return resolve(true); } if (typeof saveDataOrErr === "string" && saveDataOrErr?.includes("Too many connections")) { - globalScene.queueMessage( + globalScene.phaseManager.queueMessage( "Too many people are trying to connect and the server is overloaded. Please try again later.", null, true, @@ -746,8 +746,8 @@ export class GameData { }); if (systemData) { - globalScene.clearPhaseQueue(); - globalScene.unshiftPhase(new ReloadSessionPhase(JSON.stringify(systemData))); + globalScene.phaseManager.clearPhaseQueue(); + globalScene.phaseManager.unshiftPhase(new ReloadSessionPhase(JSON.stringify(systemData))); this.clearLocalData(); return false; } @@ -1248,8 +1248,8 @@ export class GameData { pokerogueApi.savedata.session.delete({ slot: slotId, clientSessionId }).then(error => { if (error) { if (error.startsWith("session out of date")) { - globalScene.clearPhaseQueue(); - globalScene.unshiftPhase(new ReloadSessionPhase()); + globalScene.phaseManager.clearPhaseQueue(); + globalScene.phaseManager.unshiftPhase(new ReloadSessionPhase()); } console.error(error); resolve(false); @@ -1320,8 +1320,8 @@ export class GameData { localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`); } else { if (jsonResponse?.error?.startsWith("session out of date")) { - globalScene.clearPhaseQueue(); - globalScene.unshiftPhase(new ReloadSessionPhase()); + globalScene.phaseManager.clearPhaseQueue(); + globalScene.phaseManager.unshiftPhase(new ReloadSessionPhase()); } console.error(jsonResponse); @@ -1458,8 +1458,8 @@ export class GameData { } if (error) { if (error.startsWith("session out of date")) { - globalScene.clearPhaseQueue(); - globalScene.unshiftPhase(new ReloadSessionPhase()); + globalScene.phaseManager.clearPhaseQueue(); + globalScene.phaseManager.unshiftPhase(new ReloadSessionPhase()); } console.error(error); return resolve(false); diff --git a/src/ui/ball-ui-handler.ts b/src/ui/ball-ui-handler.ts index 66d7847213f..bb1e8d0e85e 100644 --- a/src/ui/ball-ui-handler.ts +++ b/src/ui/ball-ui-handler.ts @@ -78,7 +78,7 @@ export default class BallUiHandler extends UiHandler { const pokeballTypeCount = Object.keys(globalScene.pokeballCounts).length; if (button === Button.ACTION || button === Button.CANCEL) { - const commandPhase = globalScene.getCurrentPhase() as CommandPhase; + const commandPhase = globalScene.phaseManager.getCurrentPhase() as CommandPhase; success = true; if (button === Button.ACTION && this.cursor < pokeballTypeCount) { if (globalScene.pokeballCounts[this.cursor]) { diff --git a/src/ui/challenges-select-ui-handler.ts b/src/ui/challenges-select-ui-handler.ts index d1df16a457b..d2c2dbc6c0d 100644 --- a/src/ui/challenges-select-ui-handler.ts +++ b/src/ui/challenges-select-ui-handler.ts @@ -383,16 +383,16 @@ export default class GameChallengesUiHandler extends UiHandler { this.cursorObj?.setVisible(true); this.updateChallengeArrows(this.startCursor.visible); } else { - globalScene.clearPhaseQueue(); - globalScene.pushPhase(new TitlePhase()); - globalScene.getCurrentPhase()?.end(); + globalScene.phaseManager.clearPhaseQueue(); + globalScene.phaseManager.pushPhase(new TitlePhase()); + globalScene.phaseManager.getCurrentPhase()?.end(); } success = true; } else if (button === Button.SUBMIT || button === Button.ACTION) { if (this.hasSelectedChallenge) { if (this.startCursor.visible) { - globalScene.unshiftPhase(new SelectStarterPhase()); - globalScene.getCurrentPhase()?.end(); + globalScene.phaseManager.unshiftPhase(new SelectStarterPhase()); + globalScene.phaseManager.getCurrentPhase()?.end(); } else { this.startCursor.setVisible(true); this.cursorObj?.setVisible(false); diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts index 0d67fdea624..0d38672323a 100644 --- a/src/ui/command-ui-handler.ts +++ b/src/ui/command-ui-handler.ts @@ -74,11 +74,11 @@ export default class CommandUiHandler extends UiHandler { this.commandsContainer.setVisible(true); let commandPhase: CommandPhase; - const currentPhase = globalScene.getCurrentPhase(); + const currentPhase = globalScene.phaseManager.getCurrentPhase(); if (currentPhase?.is("CommandPhase")) { commandPhase = currentPhase; } else { - commandPhase = globalScene.getStandbyPhase() as CommandPhase; + commandPhase = globalScene.phaseManager.getStandbyPhase() as CommandPhase; } if (this.canTera()) { @@ -124,7 +124,7 @@ export default class CommandUiHandler extends UiHandler { switch (cursor) { // Fight case Command.FIGHT: - ui.setMode(UiMode.FIGHT, (globalScene.getCurrentPhase() as CommandPhase).getFieldIndex()); + ui.setMode(UiMode.FIGHT, (globalScene.phaseManager.getCurrentPhase() as CommandPhase).getFieldIndex()); success = true; break; // Ball @@ -137,7 +137,7 @@ export default class CommandUiHandler extends UiHandler { ui.setMode( UiMode.PARTY, PartyUiMode.SWITCH, - (globalScene.getCurrentPhase() as CommandPhase).getPokemon().getFieldIndex(), + (globalScene.phaseManager.getCurrentPhase() as CommandPhase).getPokemon().getFieldIndex(), null, PartyUiHandler.FilterNonFainted, ); @@ -145,16 +145,20 @@ export default class CommandUiHandler extends UiHandler { break; // Run case Command.RUN: - (globalScene.getCurrentPhase() as CommandPhase).handleCommand(Command.RUN, 0); + (globalScene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.RUN, 0); success = true; break; case Command.TERA: - ui.setMode(UiMode.FIGHT, (globalScene.getCurrentPhase() as CommandPhase).getFieldIndex(), Command.TERA); + ui.setMode( + UiMode.FIGHT, + (globalScene.phaseManager.getCurrentPhase() as CommandPhase).getFieldIndex(), + Command.TERA, + ); success = true; break; } } else { - (globalScene.getCurrentPhase() as CommandPhase).cancel(); + (globalScene.phaseManager.getCurrentPhase() as CommandPhase).cancel(); } } else { switch (button) { diff --git a/src/ui/egg-hatch-scene-handler.ts b/src/ui/egg-hatch-scene-handler.ts index b2863a213f8..85c4199ff1d 100644 --- a/src/ui/egg-hatch-scene-handler.ts +++ b/src/ui/egg-hatch-scene-handler.ts @@ -44,7 +44,7 @@ export default class EggHatchSceneHandler extends UiHandler { processInput(button: Button): boolean { if (button === Button.ACTION || button === Button.CANCEL) { - const phase = globalScene.getCurrentPhase(); + const phase = globalScene.phaseManager.getCurrentPhase(); if (phase?.is("EggHatchPhase") && phase.trySkip()) { return true; } diff --git a/src/ui/egg-summary-ui-handler.ts b/src/ui/egg-summary-ui-handler.ts index ed2506ba0c1..90dc60e16b7 100644 --- a/src/ui/egg-summary-ui-handler.ts +++ b/src/ui/egg-summary-ui-handler.ts @@ -221,7 +221,7 @@ export default class EggSummaryUiHandler extends MessageUiHandler { let error = false; if (button === Button.CANCEL) { if (!this.blockExit) { - const phase = globalScene.getCurrentPhase(); + const phase = globalScene.phaseManager.getCurrentPhase(); if (phase?.is("EggSummaryPhase")) { phase.end(); } diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 208e627023b..3fe06cdf039 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -126,7 +126,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { messageHandler.bg.setVisible(false); messageHandler.commandWindow.setVisible(false); messageHandler.movesWindowContainer.setVisible(true); - const pokemon = (globalScene.getCurrentPhase() as CommandPhase).getPokemon(); + const pokemon = (globalScene.phaseManager.getCurrentPhase() as CommandPhase).getPokemon(); if (pokemon.tempSummonData.turnCount <= 1) { this.setCursor(0); } else { @@ -147,7 +147,9 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { if (button === Button.CANCEL || button === Button.ACTION) { if (button === Button.ACTION) { - if ((globalScene.getCurrentPhase() as CommandPhase).handleCommand(this.fromCommand, cursor, false)) { + if ( + (globalScene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(this.fromCommand, cursor, false) + ) { success = true; } else { ui.playError(); @@ -237,7 +239,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { ui.add(this.cursorObj); } - const pokemon = (globalScene.getCurrentPhase() as CommandPhase).getPokemon(); + const pokemon = (globalScene.phaseManager.getCurrentPhase() as CommandPhase).getPokemon(); const moveset = pokemon.getMoveset(); const hasMove = cursor < moveset.length; @@ -318,7 +320,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { } displayMoves() { - const pokemon = (globalScene.getCurrentPhase() as CommandPhase).getPokemon(); + const pokemon = (globalScene.phaseManager.getCurrentPhase() as CommandPhase).getPokemon(); const moveset = pokemon.getMoveset(); for (let moveIndex = 0; moveIndex < 4; moveIndex++) { @@ -389,7 +391,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { clearMoves() { this.movesContainer.removeAll(true); - const opponents = (globalScene.getCurrentPhase() as CommandPhase).getPokemon().getOpponents(); + const opponents = (globalScene.phaseManager.getCurrentPhase() as CommandPhase).getPokemon().getOpponents(); opponents.forEach(opponent => { (opponent as EnemyPokemon).updateEffectiveness(); }); diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index b6c3a9d7b8e..e68cc706aba 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -125,7 +125,7 @@ export default class MenuUiHandler extends MessageUiHandler { const ui = this.getUi(); this.excludedMenus = () => [ { - condition: !!globalScene.getCurrentPhase()?.is("SelectModifierPhase"), + condition: !!globalScene.phaseManager.getCurrentPhase()?.is("SelectModifierPhase"), options: [MenuOptions.EGG_GACHA], }, { condition: bypassLogin, options: [MenuOptions.LOG_OUT] }, diff --git a/src/ui/mystery-encounter-ui-handler.ts b/src/ui/mystery-encounter-ui-handler.ts index 0866ed8788e..83ce88714f5 100644 --- a/src/ui/mystery-encounter-ui-handler.ts +++ b/src/ui/mystery-encounter-ui-handler.ts @@ -156,7 +156,9 @@ export default class MysteryEncounterUiHandler extends UiHandler { ) { success = false; } else { - if ((globalScene.getCurrentPhase() as MysteryEncounterPhase).handleOptionSelect(selected, cursor)) { + if ( + (globalScene.phaseManager.getCurrentPhase() as MysteryEncounterPhase).handleOptionSelect(selected, cursor) + ) { success = true; } else { ui.playError(); diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index e26ecb531e9..6ce192751df 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -750,7 +750,7 @@ export default class PartyUiHandler extends MessageUiHandler { // TODO: This risks hitting the other options (.MOVE_i and ALL) so does it? Do we need an extra check? if ( option >= PartyOption.FORM_CHANGE_ITEM && - globalScene.getCurrentPhase()?.is("SelectModifierPhase") && + globalScene.phaseManager.getCurrentPhase()?.is("SelectModifierPhase") && this.partyUiMode === PartyUiMode.CHECK ) { const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); @@ -804,7 +804,7 @@ export default class PartyUiHandler extends MessageUiHandler { this.partyUiMode === PartyUiMode.SWITCH ) { this.clearOptions(); - (globalScene.getCurrentPhase() as CommandPhase).handleCommand( + (globalScene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand( Command.POKEMON, this.cursor, option === PartyOption.PASS_BATON, @@ -1337,7 +1337,7 @@ export default class PartyUiHandler extends MessageUiHandler { this.addCommonOptions(pokemon); break; case PartyUiMode.CHECK: - if (globalScene.getCurrentPhase()?.is("SelectModifierPhase")) { + if (globalScene.phaseManager.getCurrentPhase()?.is("SelectModifierPhase")) { const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); for (let i = 0; i < formChangeItemModifiers.length; i++) { this.options.push(PartyOption.FORM_CHANGE_ITEM + i); diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts index 5875e3394a2..a81265e1a55 100644 --- a/src/ui/pokedex-page-ui-handler.ts +++ b/src/ui/pokedex-page-ui-handler.ts @@ -666,7 +666,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler { show(args: any[]): boolean { // Allow the use of candies if we are in one of the whitelisted phases this.canUseCandies = ["TitlePhase", "SelectStarterPhase", "CommandPhase"].includes( - globalScene.getCurrentPhase()?.phaseName ?? "", + globalScene.phaseManager.getCurrentPhase()?.phaseName ?? "", ); if (args.length >= 1 && args[0] === "refresh") { diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index b7a7e0f3e6e..973805e4ca1 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -4305,15 +4305,15 @@ export default class StarterSelectUiHandler extends MessageUiHandler { UiMode.CONFIRM, () => { ui.setMode(UiMode.STARTER_SELECT); - globalScene.clearPhaseQueue(); + globalScene.phaseManager.clearPhaseQueue(); if (globalScene.gameMode.isChallenge) { - globalScene.pushPhase(new SelectChallengePhase()); - globalScene.pushPhase(new EncounterPhase()); + globalScene.phaseManager.pushPhase(new SelectChallengePhase()); + globalScene.phaseManager.pushPhase(new EncounterPhase()); } else { - globalScene.pushPhase(new TitlePhase()); + globalScene.phaseManager.pushPhase(new TitlePhase()); } this.clearText(); - globalScene.getCurrentPhase()?.end(); + globalScene.phaseManager.getCurrentPhase()?.end(); }, cancel, null, diff --git a/test/abilities/cud_chew.test.ts b/test/abilities/cud_chew.test.ts index 3c918f01330..8a80c6625cc 100644 --- a/test/abilities/cud_chew.test.ts +++ b/test/abilities/cud_chew.test.ts @@ -67,7 +67,7 @@ describe("Abilities - Cud Chew", () => { }); it("shows ability popup for eating berry, even if berry is useless", async () => { - const abDisplaySpy = vi.spyOn(globalScene, "queueAbilityDisplay"); + const abDisplaySpy = vi.spyOn(globalScene.phaseManager, "queueAbilityDisplay"); game.override.enemyMoveset([MoveId.SPLASH, MoveId.HEAL_PULSE]); await game.classicMode.startBattle([SpeciesId.FARIGIRAF]); @@ -89,7 +89,7 @@ describe("Abilities - Cud Chew", () => { await game.move.selectEnemyMove(MoveId.HEAL_PULSE); await game.phaseInterceptor.to("TurnEndPhase"); - // globalScene.queueAbilityDisplay should be called twice: + // globalScene.phaseManager.queueAbilityDisplay should be called twice: // once to show cud chew text before regurgitating berries, // once to hide ability text after finishing. expect(abDisplaySpy).toBeCalledTimes(2); diff --git a/test/abilities/dancer.test.ts b/test/abilities/dancer.test.ts index 086c69300b4..ae702e4b1e7 100644 --- a/test/abilities/dancer.test.ts +++ b/test/abilities/dancer.test.ts @@ -44,7 +44,7 @@ describe("Abilities - Dancer", () => { await game.phaseInterceptor.to("MovePhase"); // feebas uses swords dance await game.phaseInterceptor.to("MovePhase", false); // oricorio copies swords dance - let currentPhase = game.scene.getCurrentPhase() as MovePhase; + let currentPhase = game.scene.phaseManager.getCurrentPhase() as MovePhase; expect(currentPhase.pokemon).toBe(oricorio); expect(currentPhase.move.moveId).toBe(MoveId.SWORDS_DANCE); @@ -54,7 +54,7 @@ describe("Abilities - Dancer", () => { await game.phaseInterceptor.to("MovePhase"); // magikarp (left) uses victory dance await game.phaseInterceptor.to("MovePhase", false); // oricorio copies magikarp's victory dance - currentPhase = game.scene.getCurrentPhase() as MovePhase; + currentPhase = game.scene.phaseManager.getCurrentPhase() as MovePhase; expect(currentPhase.pokemon).toBe(oricorio); expect(currentPhase.move.moveId).toBe(MoveId.VICTORY_DANCE); @@ -91,13 +91,13 @@ describe("Abilities - Dancer", () => { await game.phaseInterceptor.to("MovePhase"); // shuckle 2 mirror moves oricorio await game.phaseInterceptor.to("MovePhase"); // calls instructed rev dance - let currentPhase = game.scene.getCurrentPhase() as MovePhase; + let currentPhase = game.scene.phaseManager.getCurrentPhase() as MovePhase; expect(currentPhase.pokemon).toBe(shuckle2); expect(currentPhase.move.moveId).toBe(MoveId.REVELATION_DANCE); await game.phaseInterceptor.to("MovePhase"); // shuckle 1 instructs oricorio await game.phaseInterceptor.to("MovePhase"); - currentPhase = game.scene.getCurrentPhase() as MovePhase; + currentPhase = game.scene.phaseManager.getCurrentPhase() as MovePhase; expect(currentPhase.pokemon).toBe(oricorio); expect(currentPhase.move.moveId).toBe(MoveId.REVELATION_DANCE); }); diff --git a/test/abilities/desolate-land.test.ts b/test/abilities/desolate-land.test.ts index c5238a40762..bcd980187ac 100644 --- a/test/abilities/desolate-land.test.ts +++ b/test/abilities/desolate-land.test.ts @@ -146,7 +146,7 @@ describe("Abilities - Desolate Land", () => { vi.spyOn(game.scene.getPlayerPokemon()!, "randBattleSeedInt").mockReturnValue(0); - const commandPhase = game.scene.getCurrentPhase() as CommandPhase; + const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase; commandPhase.handleCommand(Command.RUN, 0); await game.phaseInterceptor.to("BerryPhase"); diff --git a/test/abilities/disguise.test.ts b/test/abilities/disguise.test.ts index dd05c540620..15ded41527a 100644 --- a/test/abilities/disguise.test.ts +++ b/test/abilities/disguise.test.ts @@ -201,7 +201,7 @@ describe("Abilities - Disguise", () => { game.move.select(MoveId.SHADOW_SNEAK); await game.toNextWave(); - expect(game.scene.getCurrentPhase()?.constructor.name).toBe("CommandPhase"); + expect(game.scene.phaseManager.getCurrentPhase()?.constructor.name).toBe("CommandPhase"); expect(game.scene.currentBattle.waveIndex).toBe(2); }); diff --git a/test/abilities/honey_gather.test.ts b/test/abilities/honey_gather.test.ts index e2f87f0af37..a069654ae21 100644 --- a/test/abilities/honey_gather.test.ts +++ b/test/abilities/honey_gather.test.ts @@ -64,7 +64,7 @@ describe("Abilities - Honey Gather", () => { const enemy = game.scene.getEnemyPokemon()!; vi.spyOn(enemy, "scene", "get").mockReturnValue(game.scene); - const commandPhase = game.scene.getCurrentPhase() as CommandPhase; + const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase; commandPhase.handleCommand(Command.RUN, 0); await game.toNextTurn(); diff --git a/test/abilities/imposter.test.ts b/test/abilities/imposter.test.ts index c27e679ec54..e9ae6bd64bb 100644 --- a/test/abilities/imposter.test.ts +++ b/test/abilities/imposter.test.ts @@ -140,7 +140,7 @@ describe("Abilities - Imposter", () => { await game.doKillOpponents(); await game.toNextWave(); - expect(game.scene.getCurrentPhase()?.constructor.name).toBe("CommandPhase"); + expect(game.scene.phaseManager.getCurrentPhase()?.constructor.name).toBe("CommandPhase"); expect(game.scene.currentBattle.waveIndex).toBe(2); await game.reload.reloadSession(); @@ -176,7 +176,7 @@ describe("Abilities - Imposter", () => { await game.doKillOpponents(); await game.toNextWave(); - expect(game.scene.getCurrentPhase()?.constructor.name).toBe("CommandPhase"); + expect(game.scene.phaseManager.getCurrentPhase()?.constructor.name).toBe("CommandPhase"); expect(game.scene.currentBattle.waveIndex).toBe(2); await game.reload.reloadSession(); diff --git a/test/abilities/mycelium_might.test.ts b/test/abilities/mycelium_might.test.ts index 6e4da4bc933..1f236f2c2fe 100644 --- a/test/abilities/mycelium_might.test.ts +++ b/test/abilities/mycelium_might.test.ts @@ -52,7 +52,7 @@ describe("Abilities - Mycelium Might", () => { game.move.select(MoveId.BABY_DOLL_EYES); await game.phaseInterceptor.to(TurnStartPhase, false); - const phase = game.scene.getCurrentPhase() as TurnStartPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as TurnStartPhase; const speedOrder = phase.getSpeedOrder(); const commandOrder = phase.getCommandOrder(); // The opponent Pokemon (without Mycelium Might) goes first despite having lower speed than the player Pokemon. @@ -76,7 +76,7 @@ describe("Abilities - Mycelium Might", () => { game.move.select(MoveId.BABY_DOLL_EYES); await game.phaseInterceptor.to(TurnStartPhase, false); - const phase = game.scene.getCurrentPhase() as TurnStartPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as TurnStartPhase; const speedOrder = phase.getSpeedOrder(); const commandOrder = phase.getCommandOrder(); // The player Pokemon (with M.M.) goes first because its move is still within a higher priority bracket than its opponent. @@ -97,7 +97,7 @@ describe("Abilities - Mycelium Might", () => { game.move.select(MoveId.QUICK_ATTACK); await game.phaseInterceptor.to(TurnStartPhase, false); - const phase = game.scene.getCurrentPhase() as TurnStartPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as TurnStartPhase; const speedOrder = phase.getSpeedOrder(); const commandOrder = phase.getCommandOrder(); // The player Pokemon (with M.M.) goes first because it has a higher speed and did not use a status move. diff --git a/test/abilities/neutralizing_gas.test.ts b/test/abilities/neutralizing_gas.test.ts index 7c78cc9db64..2d1fef29688 100644 --- a/test/abilities/neutralizing_gas.test.ts +++ b/test/abilities/neutralizing_gas.test.ts @@ -166,7 +166,7 @@ describe("Abilities - Neutralizing Gas", () => { vi.spyOn(game.scene.getPlayerPokemon()!, "randBattleSeedInt").mockReturnValue(0); - const commandPhase = game.scene.getCurrentPhase() as CommandPhase; + const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase; commandPhase.handleCommand(Command.RUN, 0); await game.phaseInterceptor.to("BerryPhase"); diff --git a/test/abilities/no_guard.test.ts b/test/abilities/no_guard.test.ts index 1cb5945e9ff..5b127204f9f 100644 --- a/test/abilities/no_guard.test.ts +++ b/test/abilities/no_guard.test.ts @@ -45,7 +45,7 @@ describe("Abilities - No Guard", () => { await game.phaseInterceptor.to(MoveEffectPhase, false); - const moveEffectPhase = game.scene.getCurrentPhase() as MoveEffectPhase; + const moveEffectPhase = game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase; vi.spyOn(moveEffectPhase, "hitCheck"); await game.phaseInterceptor.to(MoveEndPhase); diff --git a/test/abilities/shield_dust.test.ts b/test/abilities/shield_dust.test.ts index e99b7563cb7..03b175c3ca5 100644 --- a/test/abilities/shield_dust.test.ts +++ b/test/abilities/shield_dust.test.ts @@ -51,7 +51,7 @@ describe("Abilities - Shield Dust", () => { await game.phaseInterceptor.to(MoveEffectPhase, false); // Shield Dust negates secondary effect - const phase = game.scene.getCurrentPhase() as MoveEffectPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase; const move = phase.move; expect(move.id).toBe(MoveId.AIR_SLASH); diff --git a/test/abilities/speed_boost.test.ts b/test/abilities/speed_boost.test.ts index 9890f22ffcd..245bf22345d 100644 --- a/test/abilities/speed_boost.test.ts +++ b/test/abilities/speed_boost.test.ts @@ -98,9 +98,9 @@ describe("Abilities - Speed Boost", () => { it("should not trigger if pokemon fails to escape", async () => { await game.classicMode.startBattle([SpeciesId.SHUCKLE]); - const commandPhase = game.scene.getCurrentPhase() as CommandPhase; + const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase; commandPhase.handleCommand(Command.RUN, 0); - const runPhase = game.scene.getCurrentPhase() as AttemptRunPhase; + const runPhase = game.scene.phaseManager.getCurrentPhase() as AttemptRunPhase; runPhase.forceFailEscape = true; await game.phaseInterceptor.to(AttemptRunPhase); await game.toNextTurn(); diff --git a/test/abilities/stall.test.ts b/test/abilities/stall.test.ts index 78e7d49b48b..df40bed3e90 100644 --- a/test/abilities/stall.test.ts +++ b/test/abilities/stall.test.ts @@ -46,7 +46,7 @@ describe("Abilities - Stall", () => { game.move.select(MoveId.QUICK_ATTACK); await game.phaseInterceptor.to(TurnStartPhase, false); - const phase = game.scene.getCurrentPhase() as TurnStartPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as TurnStartPhase; const speedOrder = phase.getSpeedOrder(); const commandOrder = phase.getCommandOrder(); // The player Pokemon (without Stall) goes first despite having lower speed than the opponent. @@ -64,7 +64,7 @@ describe("Abilities - Stall", () => { game.move.select(MoveId.TACKLE); await game.phaseInterceptor.to(TurnStartPhase, false); - const phase = game.scene.getCurrentPhase() as TurnStartPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as TurnStartPhase; const speedOrder = phase.getSpeedOrder(); const commandOrder = phase.getCommandOrder(); // The opponent Pokemon (with Stall) goes first because its move is still within a higher priority bracket than its opponent. @@ -83,7 +83,7 @@ describe("Abilities - Stall", () => { game.move.select(MoveId.TACKLE); await game.phaseInterceptor.to(TurnStartPhase, false); - const phase = game.scene.getCurrentPhase() as TurnStartPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as TurnStartPhase; const speedOrder = phase.getSpeedOrder(); const commandOrder = phase.getCommandOrder(); diff --git a/test/battle/battle-order.test.ts b/test/battle/battle-order.test.ts index 7983f1db1d2..c12760a7f30 100644 --- a/test/battle/battle-order.test.ts +++ b/test/battle/battle-order.test.ts @@ -44,7 +44,7 @@ describe("Battle order", () => { const playerPokemonIndex = playerPokemon.getBattlerIndex(); const enemyPokemonIndex = enemyPokemon.getBattlerIndex(); - const phase = game.scene.getCurrentPhase() as TurnStartPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as TurnStartPhase; const order = phase.getCommandOrder(); expect(order[0]).toBe(enemyPokemonIndex); expect(order[1]).toBe(playerPokemonIndex); @@ -63,7 +63,7 @@ describe("Battle order", () => { const playerPokemonIndex = playerPokemon.getBattlerIndex(); const enemyPokemonIndex = enemyPokemon.getBattlerIndex(); - const phase = game.scene.getCurrentPhase() as TurnStartPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as TurnStartPhase; const order = phase.getCommandOrder(); expect(order[0]).toBe(playerPokemonIndex); expect(order[1]).toBe(enemyPokemonIndex); @@ -85,7 +85,7 @@ describe("Battle order", () => { game.move.select(MoveId.TACKLE, 1); await game.phaseInterceptor.runFrom(SelectTargetPhase).to(TurnStartPhase, false); - const phase = game.scene.getCurrentPhase() as TurnStartPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as TurnStartPhase; const order = phase.getCommandOrder(); expect(order.slice(0, 2).includes(enemyIndices[0])).toBe(true); expect(order.slice(0, 2).includes(enemyIndices[1])).toBe(true); @@ -109,7 +109,7 @@ describe("Battle order", () => { game.move.select(MoveId.TACKLE, 1); await game.phaseInterceptor.runFrom(SelectTargetPhase).to(TurnStartPhase, false); - const phase = game.scene.getCurrentPhase() as TurnStartPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as TurnStartPhase; const order = phase.getCommandOrder(); expect(order[0]).toBe(enemyIndices[1]); expect(order.slice(1, 4).includes(enemyIndices[0])).toBe(true); @@ -134,7 +134,7 @@ describe("Battle order", () => { game.move.select(MoveId.TACKLE, 1); await game.phaseInterceptor.runFrom(SelectTargetPhase).to(TurnStartPhase, false); - const phase = game.scene.getCurrentPhase() as TurnStartPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as TurnStartPhase; const order = phase.getCommandOrder(); expect(order.slice(0, 2).includes(playerIndices[1])).toBe(true); expect(order.slice(0, 2).includes(enemyIndices[1])).toBe(true); diff --git a/test/battle/battle.test.ts b/test/battle/battle.test.ts index 03faf369dd7..b93c913d09a 100644 --- a/test/battle/battle.test.ts +++ b/test/battle/battle.test.ts @@ -87,7 +87,7 @@ describe("Test Battle Phase", () => { it("newGame one-liner", async () => { await game.classicMode.startBattle(); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); + expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("do attack wave 3 - single battle - regular - OHKO", async () => { @@ -196,7 +196,7 @@ describe("Test Battle Phase", () => { game.scene.gameMode = getGameMode(GameModes.CLASSIC); const starters = generateStarter(game.scene); const selectStarterPhase = new SelectStarterPhase(); - game.scene.pushPhase(new EncounterPhase(false)); + game.scene.phaseManager.pushPhase(new EncounterPhase(false)); selectStarterPhase.initBattle(starters); }); await game.phaseInterceptor.runFrom(SelectGenderPhase).to(SummonPhase); @@ -209,7 +209,7 @@ describe("Test Battle Phase", () => { game.override.ability(AbilityId.HYDRATION); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); + expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("1vs1", async () => { @@ -219,7 +219,7 @@ describe("Test Battle Phase", () => { game.override.ability(AbilityId.HYDRATION); await game.classicMode.startBattle([SpeciesId.BLASTOISE]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); + expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("2vs2", async () => { @@ -230,7 +230,7 @@ describe("Test Battle Phase", () => { game.override.startingWave(3); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); + expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("4vs2", async () => { @@ -241,7 +241,7 @@ describe("Test Battle Phase", () => { game.override.startingWave(3); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD, SpeciesId.DARKRAI, SpeciesId.GABITE]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); + expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("kill opponent pokemon", async () => { diff --git a/test/battle/special_battle.test.ts b/test/battle/special_battle.test.ts index c1a948a0759..f0c77668093 100644 --- a/test/battle/special_battle.test.ts +++ b/test/battle/special_battle.test.ts @@ -36,62 +36,62 @@ describe("Test Battle Phase", () => { game.override.battleStyle("single").startingWave(10); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); + expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("startBattle 2vs2 boss", async () => { game.override.battleStyle("double").startingWave(10); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); + expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("startBattle 2vs2 trainer", async () => { game.override.battleStyle("double").startingWave(5); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); + expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("startBattle 2vs1 trainer", async () => { game.override.battleStyle("single").startingWave(5); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); + expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("startBattle 2vs1 rival", async () => { game.override.battleStyle("single").startingWave(8); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); + expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("startBattle 2vs2 rival", async () => { game.override.battleStyle("double").startingWave(8); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); + expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("startBattle 1vs1 trainer", async () => { game.override.battleStyle("single").startingWave(5); await game.classicMode.startBattle([SpeciesId.BLASTOISE]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); + expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("startBattle 2vs2 trainer", async () => { game.override.battleStyle("double").startingWave(5); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); + expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("startBattle 4vs2 trainer", async () => { game.override.battleStyle("double").startingWave(5); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD, SpeciesId.DARKRAI, SpeciesId.GABITE]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); + expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); }); diff --git a/test/battlerTags/octolock.test.ts b/test/battlerTags/octolock.test.ts index 6189bd7febe..63784ed7f1b 100644 --- a/test/battlerTags/octolock.test.ts +++ b/test/battlerTags/octolock.test.ts @@ -28,7 +28,7 @@ describe("BattlerTag - OctolockTag", () => { const subject = new OctolockTag(1); - vi.spyOn(game.scene, "unshiftPhase").mockImplementation(phase => { + vi.spyOn(game.scene.phaseManager, "unshiftPhase").mockImplementation(phase => { expect(phase).toBeInstanceOf(StatStageChangePhase); expect((phase as StatStageChangePhase)["stages"]).toEqual(-1); expect((phase as StatStageChangePhase)["stats"]).toEqual([Stat.DEF, Stat.SPDEF]); @@ -36,7 +36,7 @@ describe("BattlerTag - OctolockTag", () => { subject.lapse(mockPokemon, BattlerTagLapseType.TURN_END); - expect(game.scene.unshiftPhase).toBeCalledTimes(1); + expect(game.scene.phaseManager.unshiftPhase).toBeCalledTimes(1); }); }); diff --git a/test/battlerTags/stockpiling.test.ts b/test/battlerTags/stockpiling.test.ts index 20fade13d92..37873db9eab 100644 --- a/test/battlerTags/stockpiling.test.ts +++ b/test/battlerTags/stockpiling.test.ts @@ -32,11 +32,11 @@ describe("BattlerTag - StockpilingTag", () => { getBattlerIndex: () => 0, } as Pokemon; - vi.spyOn(game.scene, "queueMessage").mockImplementation(() => {}); + vi.spyOn(game.scene.phaseManager, "queueMessage").mockImplementation(() => {}); const subject = new StockpilingTag(1); - vi.spyOn(game.scene, "unshiftPhase").mockImplementation(phase => { + vi.spyOn(game.scene.phaseManager, "unshiftPhase").mockImplementation(phase => { expect(phase).toBeInstanceOf(StatStageChangePhase); expect((phase as StatStageChangePhase)["stages"]).toEqual(1); expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([Stat.DEF, Stat.SPDEF])); @@ -46,7 +46,7 @@ describe("BattlerTag - StockpilingTag", () => { subject.onAdd(mockPokemon); - expect(game.scene.unshiftPhase).toBeCalledTimes(1); + expect(game.scene.phaseManager.unshiftPhase).toBeCalledTimes(1); }); it("unshifts a StatStageChangePhase with expected stat changes on add (one stat maxed)", async () => { @@ -55,14 +55,14 @@ describe("BattlerTag - StockpilingTag", () => { getBattlerIndex: () => 0, } as unknown as Pokemon; - vi.spyOn(game.scene, "queueMessage").mockImplementation(() => {}); + vi.spyOn(game.scene.phaseManager, "queueMessage").mockImplementation(() => {}); mockPokemon.summonData.statStages[Stat.DEF - 1] = 6; mockPokemon.summonData.statStages[Stat.SPD - 1] = 5; const subject = new StockpilingTag(1); - vi.spyOn(game.scene, "unshiftPhase").mockImplementation(phase => { + vi.spyOn(game.scene.phaseManager, "unshiftPhase").mockImplementation(phase => { expect(phase).toBeInstanceOf(StatStageChangePhase); expect((phase as StatStageChangePhase)["stages"]).toEqual(1); expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([Stat.DEF, Stat.SPDEF])); @@ -72,7 +72,7 @@ describe("BattlerTag - StockpilingTag", () => { subject.onAdd(mockPokemon); - expect(game.scene.unshiftPhase).toBeCalledTimes(1); + expect(game.scene.phaseManager.unshiftPhase).toBeCalledTimes(1); }); }); @@ -82,11 +82,11 @@ describe("BattlerTag - StockpilingTag", () => { getBattlerIndex: () => 0, } as Pokemon; - vi.spyOn(game.scene, "queueMessage").mockImplementation(() => {}); + vi.spyOn(game.scene.phaseManager, "queueMessage").mockImplementation(() => {}); const subject = new StockpilingTag(1); - vi.spyOn(game.scene, "unshiftPhase").mockImplementation(phase => { + vi.spyOn(game.scene.phaseManager, "unshiftPhase").mockImplementation(phase => { expect(phase).toBeInstanceOf(StatStageChangePhase); expect((phase as StatStageChangePhase)["stages"]).toEqual(1); expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([Stat.DEF, Stat.SPDEF])); @@ -96,7 +96,7 @@ describe("BattlerTag - StockpilingTag", () => { subject.onOverlap(mockPokemon); - expect(game.scene.unshiftPhase).toBeCalledTimes(1); + expect(game.scene.phaseManager.unshiftPhase).toBeCalledTimes(1); }); }); @@ -107,14 +107,14 @@ describe("BattlerTag - StockpilingTag", () => { getBattlerIndex: () => 0, } as Pokemon; - vi.spyOn(game.scene, "queueMessage").mockImplementation(() => {}); + vi.spyOn(game.scene.phaseManager, "queueMessage").mockImplementation(() => {}); mockPokemon.summonData.statStages[Stat.DEF - 1] = 5; mockPokemon.summonData.statStages[Stat.SPD - 1] = 4; const subject = new StockpilingTag(1); - vi.spyOn(game.scene, "unshiftPhase").mockImplementationOnce(phase => { + vi.spyOn(game.scene.phaseManager, "unshiftPhase").mockImplementationOnce(phase => { expect(phase).toBeInstanceOf(StatStageChangePhase); expect((phase as StatStageChangePhase)["stages"]).toEqual(1); expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([Stat.DEF, Stat.SPDEF])); @@ -126,7 +126,7 @@ describe("BattlerTag - StockpilingTag", () => { subject.onAdd(mockPokemon); expect(subject.stockpiledCount).toBe(1); - vi.spyOn(game.scene, "unshiftPhase").mockImplementationOnce(phase => { + vi.spyOn(game.scene.phaseManager, "unshiftPhase").mockImplementationOnce(phase => { expect(phase).toBeInstanceOf(StatStageChangePhase); expect((phase as StatStageChangePhase)["stages"]).toEqual(1); expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([Stat.DEF, Stat.SPDEF])); @@ -138,7 +138,7 @@ describe("BattlerTag - StockpilingTag", () => { subject.onOverlap(mockPokemon); expect(subject.stockpiledCount).toBe(2); - vi.spyOn(game.scene, "unshiftPhase").mockImplementationOnce(phase => { + vi.spyOn(game.scene.phaseManager, "unshiftPhase").mockImplementationOnce(phase => { expect(phase).toBeInstanceOf(StatStageChangePhase); expect((phase as StatStageChangePhase)["stages"]).toEqual(1); expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([Stat.DEF, Stat.SPDEF])); @@ -149,7 +149,7 @@ describe("BattlerTag - StockpilingTag", () => { subject.onOverlap(mockPokemon); expect(subject.stockpiledCount).toBe(3); - vi.spyOn(game.scene, "unshiftPhase").mockImplementationOnce(_phase => { + vi.spyOn(game.scene.phaseManager, "unshiftPhase").mockImplementationOnce(_phase => { throw new Error("Should not be called a fourth time"); }); @@ -162,14 +162,14 @@ describe("BattlerTag - StockpilingTag", () => { }); // removing tag should reverse stat changes - vi.spyOn(game.scene, "unshiftPhase").mockImplementationOnce(phase => { + vi.spyOn(game.scene.phaseManager, "unshiftPhase").mockImplementationOnce(phase => { expect(phase).toBeInstanceOf(StatStageChangePhase); expect((phase as StatStageChangePhase)["stages"]).toEqual(-2); expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([Stat.SPDEF])); }); subject.onRemove(mockPokemon); - expect(game.scene.unshiftPhase).toHaveBeenCalledOnce(); // note that re-spying each add/overlap has been refreshing call count + expect(game.scene.phaseManager.unshiftPhase).toHaveBeenCalledOnce(); // note that re-spying each add/overlap has been refreshing call count }); }); }); diff --git a/test/battlerTags/substitute.test.ts b/test/battlerTags/substitute.test.ts index ce16cfdbcd9..703e0ae75f6 100644 --- a/test/battlerTags/substitute.test.ts +++ b/test/battlerTags/substitute.test.ts @@ -55,7 +55,7 @@ describe("BattlerTag - SubstituteTag", () => { const subject = new SubstituteTag(MoveId.SUBSTITUTE, mockPokemon.id); vi.spyOn(mockPokemon.scene as BattleScene, "triggerPokemonBattleAnim").mockReturnValue(true); - vi.spyOn(mockPokemon.scene as BattleScene, "queueMessage").mockReturnValue(); + vi.spyOn((mockPokemon.scene as BattleScene).phaseManager, "queueMessage").mockReturnValue(); subject.onAdd(mockPokemon); @@ -72,19 +72,19 @@ describe("BattlerTag - SubstituteTag", () => { }, ); - vi.spyOn(mockPokemon.scene as BattleScene, "queueMessage").mockReturnValue(); + const msgSpy = vi.spyOn((mockPokemon.scene as BattleScene).phaseManager, "queueMessage").mockReturnValue(); subject.onAdd(mockPokemon); expect(subject.sourceInFocus).toBeFalsy(); expect((mockPokemon.scene as BattleScene).triggerPokemonBattleAnim).toHaveBeenCalledTimes(1); - expect((mockPokemon.scene as BattleScene).queueMessage).toHaveBeenCalledTimes(1); + expect(msgSpy).toHaveBeenCalledOnce(); }); it("removes effects that trap the source", async () => { const subject = new SubstituteTag(MoveId.SUBSTITUTE, mockPokemon.id); - vi.spyOn(mockPokemon.scene as BattleScene, "queueMessage").mockReturnValue(); + vi.spyOn((mockPokemon.scene as BattleScene).phaseManager, "queueMessage").mockReturnValue(); subject.onAdd(mockPokemon); expect(mockPokemon.findAndRemoveTags).toHaveBeenCalledTimes(1); @@ -114,12 +114,12 @@ describe("BattlerTag - SubstituteTag", () => { }, ); - vi.spyOn(mockPokemon.scene as BattleScene, "queueMessage").mockReturnValue(); + const msgSpy = vi.spyOn((mockPokemon.scene as BattleScene).phaseManager, "queueMessage").mockReturnValue(); subject.onRemove(mockPokemon); expect((mockPokemon.scene as BattleScene).triggerPokemonBattleAnim).toHaveBeenCalledTimes(1); - expect((mockPokemon.scene as BattleScene).queueMessage).toHaveBeenCalledTimes(1); + expect(msgSpy).toHaveBeenCalledOnce(); }); }); @@ -150,13 +150,12 @@ describe("BattlerTag - SubstituteTag", () => { }, ); - vi.spyOn(mockPokemon.scene as BattleScene, "queueMessage").mockReturnValue(); + vi.spyOn((mockPokemon.scene as BattleScene).phaseManager, "queueMessage").mockReturnValue(); expect(subject.lapse(mockPokemon, BattlerTagLapseType.PRE_MOVE)).toBeTruthy(); expect(subject.sourceInFocus).toBeTruthy(); expect((mockPokemon.scene as BattleScene).triggerPokemonBattleAnim).toHaveBeenCalledTimes(1); - expect((mockPokemon.scene as BattleScene).queueMessage).not.toHaveBeenCalled(); }); it("AFTER_MOVE lapse triggers post-move animation", async () => { @@ -169,13 +168,13 @@ describe("BattlerTag - SubstituteTag", () => { }, ); - vi.spyOn(mockPokemon.scene as BattleScene, "queueMessage").mockReturnValue(); + const msgSpy = vi.spyOn((mockPokemon.scene as BattleScene).phaseManager, "queueMessage").mockReturnValue(); expect(subject.lapse(mockPokemon, BattlerTagLapseType.AFTER_MOVE)).toBeTruthy(); expect(subject.sourceInFocus).toBeFalsy(); expect((mockPokemon.scene as BattleScene).triggerPokemonBattleAnim).toHaveBeenCalledTimes(1); - expect((mockPokemon.scene as BattleScene).queueMessage).not.toHaveBeenCalled(); + expect(msgSpy).not.toHaveBeenCalled(); }); // TODO: Figure out how to mock a MoveEffectPhase correctly for this test @@ -183,27 +182,27 @@ describe("BattlerTag - SubstituteTag", () => { const subject = new SubstituteTag(MoveId.SUBSTITUTE, mockPokemon.id); vi.spyOn(mockPokemon.scene as BattleScene, "triggerPokemonBattleAnim").mockReturnValue(true); - vi.spyOn(mockPokemon.scene as BattleScene, "queueMessage").mockReturnValue(); + const msgSpy = vi.spyOn((mockPokemon.scene as BattleScene).phaseManager, "queueMessage").mockReturnValue(); const moveEffectPhase = { move: allMoves[MoveId.TACKLE], getUserPokemon: vi.fn().mockReturnValue(undefined) as MoveEffectPhase["getUserPokemon"], } as MoveEffectPhase; - vi.spyOn(mockPokemon.scene as BattleScene, "getCurrentPhase").mockReturnValue(moveEffectPhase); + vi.spyOn((mockPokemon.scene as BattleScene).phaseManager, "getCurrentPhase").mockReturnValue(moveEffectPhase); vi.spyOn(allMoves[MoveId.TACKLE], "hitsSubstitute").mockReturnValue(true); expect(subject.lapse(mockPokemon, BattlerTagLapseType.HIT)).toBeTruthy(); expect((mockPokemon.scene as BattleScene).triggerPokemonBattleAnim).not.toHaveBeenCalled(); - expect((mockPokemon.scene as BattleScene).queueMessage).toHaveBeenCalledTimes(1); + expect(msgSpy).toHaveBeenCalledOnce(); }); it("CUSTOM lapse flags the tag for removal", async () => { const subject = new SubstituteTag(MoveId.SUBSTITUTE, mockPokemon.id); vi.spyOn(mockPokemon.scene as BattleScene, "triggerPokemonBattleAnim").mockReturnValue(true); - vi.spyOn(mockPokemon.scene as BattleScene, "queueMessage").mockReturnValue(); + vi.spyOn((mockPokemon.scene as BattleScene).phaseManager, "queueMessage").mockReturnValue(); expect(subject.lapse(mockPokemon, BattlerTagLapseType.CUSTOM)).toBeFalsy(); }); @@ -212,12 +211,12 @@ describe("BattlerTag - SubstituteTag", () => { const subject = new SubstituteTag(MoveId.SUBSTITUTE, mockPokemon.id); vi.spyOn(mockPokemon.scene as BattleScene, "triggerPokemonBattleAnim").mockReturnValue(true); - vi.spyOn(mockPokemon.scene as BattleScene, "queueMessage").mockReturnValue(); + const msgSpy = vi.spyOn((mockPokemon.scene as BattleScene).phaseManager, "queueMessage").mockReturnValue(); expect(subject.lapse(mockPokemon, BattlerTagLapseType.TURN_END)).toBeTruthy(); expect((mockPokemon.scene as BattleScene).triggerPokemonBattleAnim).not.toHaveBeenCalled(); - expect((mockPokemon.scene as BattleScene).queueMessage).not.toHaveBeenCalled(); + expect(msgSpy).not.toHaveBeenCalled(); }); }); }); diff --git a/test/escape-calculations.test.ts b/test/escape-calculations.test.ts index 854274eb948..fd1e411e786 100644 --- a/test/escape-calculations.test.ts +++ b/test/escape-calculations.test.ts @@ -40,11 +40,11 @@ describe("Escape chance calculations", () => { // set enemyPokemon's speed to 100 vi.spyOn(enemyField[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemySpeed]); - const commandPhase = game.scene.getCurrentPhase() as CommandPhase; + const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase; commandPhase.handleCommand(Command.RUN, 0); await game.phaseInterceptor.to(AttemptRunPhase, false); - const phase = game.scene.getCurrentPhase() as AttemptRunPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as AttemptRunPhase; const escapePercentage = new NumberHolder(0); // this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping @@ -113,11 +113,11 @@ describe("Escape chance calculations", () => { // set enemyBPokemon's speed to 30 vi.spyOn(enemyField[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemyBSpeed]); - const commandPhase = game.scene.getCurrentPhase() as CommandPhase; + const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase; commandPhase.handleCommand(Command.RUN, 0); await game.phaseInterceptor.to(AttemptRunPhase, false); - const phase = game.scene.getCurrentPhase() as AttemptRunPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as AttemptRunPhase; const escapePercentage = new NumberHolder(0); // this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping @@ -192,11 +192,11 @@ describe("Escape chance calculations", () => { // set enemyPokemon's speed to 100 vi.spyOn(enemyField[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemySpeed]); - const commandPhase = game.scene.getCurrentPhase() as CommandPhase; + const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase; commandPhase.handleCommand(Command.RUN, 0); await game.phaseInterceptor.to(AttemptRunPhase, false); - const phase = game.scene.getCurrentPhase() as AttemptRunPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as AttemptRunPhase; const escapePercentage = new NumberHolder(0); // this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping @@ -279,11 +279,11 @@ describe("Escape chance calculations", () => { // set enemyBPokemon's speed to 30 vi.spyOn(enemyField[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemyBSpeed]); - const commandPhase = game.scene.getCurrentPhase() as CommandPhase; + const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase; commandPhase.handleCommand(Command.RUN, 0); await game.phaseInterceptor.to(AttemptRunPhase, false); - const phase = game.scene.getCurrentPhase() as AttemptRunPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as AttemptRunPhase; const escapePercentage = new NumberHolder(0); // this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping diff --git a/test/items/lock_capsule.test.ts b/test/items/lock_capsule.test.ts index 15a1b4e73d1..292031a2bf0 100644 --- a/test/items/lock_capsule.test.ts +++ b/test/items/lock_capsule.test.ts @@ -34,7 +34,7 @@ describe("Items - Lock Capsule", () => { it("doesn't set the cost of common tier items to 0", async () => { await game.classicMode.startBattle(); - game.scene.overridePhase( + game.scene.phaseManager.overridePhase( new SelectModifierPhase(0, undefined, { guaranteedModifierTiers: [ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.COMMON], fillRemaining: false, @@ -42,7 +42,7 @@ describe("Items - Lock Capsule", () => { ); game.onNextPrompt("SelectModifierPhase", UiMode.MODIFIER_SELECT, () => { - const selectModifierPhase = game.scene.getCurrentPhase() as SelectModifierPhase; + const selectModifierPhase = game.scene.phaseManager.getCurrentPhase() as SelectModifierPhase; const rerollCost = selectModifierPhase.getRerollCost(true); expect(rerollCost).toBe(150); }); diff --git a/test/moves/after_you.test.ts b/test/moves/after_you.test.ts index adf9cae707a..0e5ee1e7a8f 100644 --- a/test/moves/after_you.test.ts +++ b/test/moves/after_you.test.ts @@ -42,7 +42,7 @@ describe("Moves - After You", () => { await game.phaseInterceptor.to("MoveEffectPhase"); await game.phaseInterceptor.to(MovePhase, false); - const phase = game.scene.getCurrentPhase() as MovePhase; + const phase = game.scene.phaseManager.getCurrentPhase() as MovePhase; expect(phase.pokemon).toBe(game.scene.getPlayerField()[1]); await game.phaseInterceptor.to("MoveEndPhase"); }); diff --git a/test/moves/dynamax_cannon.test.ts b/test/moves/dynamax_cannon.test.ts index ada3361f3ef..6207ef9b6ca 100644 --- a/test/moves/dynamax_cannon.test.ts +++ b/test/moves/dynamax_cannon.test.ts @@ -56,7 +56,7 @@ describe("Moves - Dynamax Cannon", () => { game.move.select(dynamaxCannon.id); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(dynamaxCannon.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(dynamaxCannon.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(100); }, 20000); @@ -68,7 +68,7 @@ describe("Moves - Dynamax Cannon", () => { game.move.select(dynamaxCannon.id); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(dynamaxCannon.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(dynamaxCannon.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(100); }, 20000); @@ -80,7 +80,7 @@ describe("Moves - Dynamax Cannon", () => { game.move.select(dynamaxCannon.id); await game.phaseInterceptor.to(MoveEffectPhase, false); - const phase = game.scene.getCurrentPhase() as MoveEffectPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase; expect(phase.move.id).toBe(dynamaxCannon.id); // Force level cap to be 100 vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100); @@ -95,7 +95,7 @@ describe("Moves - Dynamax Cannon", () => { game.move.select(dynamaxCannon.id); await game.phaseInterceptor.to(MoveEffectPhase, false); - const phase = game.scene.getCurrentPhase() as MoveEffectPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase; expect(phase.move.id).toBe(dynamaxCannon.id); // Force level cap to be 100 vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100); @@ -110,7 +110,7 @@ describe("Moves - Dynamax Cannon", () => { game.move.select(dynamaxCannon.id); await game.phaseInterceptor.to(MoveEffectPhase, false); - const phase = game.scene.getCurrentPhase() as MoveEffectPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase; expect(phase.move.id).toBe(dynamaxCannon.id); // Force level cap to be 100 vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100); @@ -125,7 +125,7 @@ describe("Moves - Dynamax Cannon", () => { game.move.select(dynamaxCannon.id); await game.phaseInterceptor.to(MoveEffectPhase, false); - const phase = game.scene.getCurrentPhase() as MoveEffectPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase; expect(phase.move.id).toBe(dynamaxCannon.id); // Force level cap to be 100 vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100); @@ -140,7 +140,7 @@ describe("Moves - Dynamax Cannon", () => { game.move.select(dynamaxCannon.id); await game.phaseInterceptor.to(MoveEffectPhase, false); - const phase = game.scene.getCurrentPhase() as MoveEffectPhase; + const phase = game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase; expect(phase.move.id).toBe(dynamaxCannon.id); // Force level cap to be 100 vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100); @@ -156,7 +156,7 @@ describe("Moves - Dynamax Cannon", () => { await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(dynamaxCannon.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(dynamaxCannon.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(200); }, 20000); diff --git a/test/moves/focus_punch.test.ts b/test/moves/focus_punch.test.ts index 9734f9f6422..38b57b201c0 100644 --- a/test/moves/focus_punch.test.ts +++ b/test/moves/focus_punch.test.ts @@ -115,8 +115,8 @@ describe("Moves - Focus Punch", () => { await game.phaseInterceptor.to(TurnStartPhase); - expect(game.scene.getCurrentPhase() instanceof SwitchSummonPhase).toBeTruthy(); - expect(game.scene.phaseQueue.find(phase => phase instanceof MoveHeaderPhase)).toBeDefined(); + expect(game.scene.phaseManager.getCurrentPhase() instanceof SwitchSummonPhase).toBeTruthy(); + expect(game.scene.phaseManager.phaseQueue.find(phase => phase instanceof MoveHeaderPhase)).toBeDefined(); }); it("should replace the 'but it failed' text when the user gets hit", async () => { game.override.enemyMoveset([MoveId.TACKLE]); diff --git a/test/moves/fusion_flare_bolt.test.ts b/test/moves/fusion_flare_bolt.test.ts index bceb2c862b5..1a5d9e44bab 100644 --- a/test/moves/fusion_flare_bolt.test.ts +++ b/test/moves/fusion_flare_bolt.test.ts @@ -56,12 +56,12 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200); }, 20000); @@ -76,12 +76,12 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); }, 20000); @@ -96,7 +96,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY]); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100); @@ -106,7 +106,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { await game.phaseInterceptor.runFrom(MovePhase).to(MoveEndPhase); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200); }, 20000); @@ -122,7 +122,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY]); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100); @@ -131,7 +131,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { await game.phaseInterceptor.runFrom(MovePhase).to(MoveEndPhase); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100); }, 20000); @@ -146,12 +146,12 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); }, 20000); @@ -190,22 +190,22 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY]); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); }, 20000); @@ -244,22 +244,22 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY]); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200); await game.phaseInterceptor.to(MoveEffectPhase, false); - expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); + expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); }, 20000); diff --git a/test/moves/instruct.test.ts b/test/moves/instruct.test.ts index 4a701ed6ac5..e0cfa93cf59 100644 --- a/test/moves/instruct.test.ts +++ b/test/moves/instruct.test.ts @@ -56,12 +56,12 @@ describe("Moves - Instruct", () => { await game.phaseInterceptor.to("MovePhase"); // enemy attacks us await game.phaseInterceptor.to("MovePhase", false); // instruct - let currentPhase = game.scene.getCurrentPhase() as MovePhase; + let currentPhase = game.scene.phaseManager.getCurrentPhase() as MovePhase; expect(currentPhase.pokemon).toBe(game.scene.getPlayerPokemon()); await game.phaseInterceptor.to("MoveEndPhase"); await game.phaseInterceptor.to("MovePhase", false); // enemy repeats move - currentPhase = game.scene.getCurrentPhase() as MovePhase; + currentPhase = game.scene.phaseManager.getCurrentPhase() as MovePhase; expect(currentPhase.pokemon).toBe(enemy); expect(currentPhase.move.moveId).toBe(MoveId.SONIC_BOOM); await game.phaseInterceptor.to("TurnEndPhase", false); diff --git a/test/moves/round.test.ts b/test/moves/round.test.ts index c42734bdc41..503ce125582 100644 --- a/test/moves/round.test.ts +++ b/test/moves/round.test.ts @@ -54,7 +54,9 @@ describe("Moves - Round", () => { for (let i = 0; i < 4; i++) { await game.phaseInterceptor.to("MoveEffectPhase", false); - actualTurnOrder.push((game.scene.getCurrentPhase() as MoveEffectPhase).getUserPokemon()!.getBattlerIndex()); + actualTurnOrder.push( + (game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).getUserPokemon()!.getBattlerIndex(), + ); await game.phaseInterceptor.to("MoveEndPhase"); } diff --git a/test/moves/shell_trap.test.ts b/test/moves/shell_trap.test.ts index 4dfc0e72d0a..9a38bf4486b 100644 --- a/test/moves/shell_trap.test.ts +++ b/test/moves/shell_trap.test.ts @@ -50,7 +50,7 @@ describe("Moves - Shell Trap", () => { await game.phaseInterceptor.to(MoveEndPhase); - const movePhase = game.scene.getCurrentPhase(); + const movePhase = game.scene.phaseManager.getCurrentPhase(); expect(movePhase instanceof MovePhase).toBeTruthy(); expect((movePhase as MovePhase).pokemon).toBe(playerPokemon[1]); @@ -73,7 +73,7 @@ describe("Moves - Shell Trap", () => { await game.phaseInterceptor.to(MoveEndPhase); - const movePhase = game.scene.getCurrentPhase(); + const movePhase = game.scene.phaseManager.getCurrentPhase(); expect(movePhase instanceof MovePhase).toBeTruthy(); expect((movePhase as MovePhase).pokemon).not.toBe(playerPokemon[1]); @@ -96,7 +96,7 @@ describe("Moves - Shell Trap", () => { await game.phaseInterceptor.to(MoveEndPhase); - const movePhase = game.scene.getCurrentPhase(); + const movePhase = game.scene.phaseManager.getCurrentPhase(); expect(movePhase instanceof MovePhase).toBeTruthy(); expect((movePhase as MovePhase).pokemon).not.toBe(playerPokemon[1]); @@ -117,7 +117,7 @@ describe("Moves - Shell Trap", () => { await game.phaseInterceptor.to(MoveEndPhase); - const movePhase = game.scene.getCurrentPhase(); + const movePhase = game.scene.phaseManager.getCurrentPhase(); expect(movePhase instanceof MovePhase).toBeTruthy(); expect((movePhase as MovePhase).pokemon).not.toBe(playerPokemon[1]); diff --git a/test/moves/substitute.test.ts b/test/moves/substitute.test.ts index 97296be7d8f..454e729a67b 100644 --- a/test/moves/substitute.test.ts +++ b/test/moves/substitute.test.ts @@ -400,7 +400,7 @@ describe("Moves - Substitute", () => { // Simulate a Baton switch for the player this turn game.onNextPrompt("CommandPhase", UiMode.COMMAND, () => { - (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.POKEMON, 1, true); + (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.POKEMON, 1, true); }); await game.phaseInterceptor.to("MovePhase", false); diff --git a/test/moves/transform.test.ts b/test/moves/transform.test.ts index e8ed133b827..ca326da5748 100644 --- a/test/moves/transform.test.ts +++ b/test/moves/transform.test.ts @@ -132,7 +132,7 @@ describe("Moves - Transform", () => { await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.toNextWave(); - expect(game.scene.getCurrentPhase()?.constructor.name).toBe("CommandPhase"); + expect(game.scene.phaseManager.getCurrentPhase()?.constructor.name).toBe("CommandPhase"); expect(game.scene.currentBattle.waveIndex).toBe(2); await game.reload.reloadSession(); @@ -173,7 +173,7 @@ describe("Moves - Transform", () => { await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.toNextWave(); - expect(game.scene.getCurrentPhase()?.constructor.name).toBe("CommandPhase"); + expect(game.scene.phaseManager.getCurrentPhase()?.constructor.name).toBe("CommandPhase"); expect(game.scene.currentBattle.waveIndex).toBe(2); await game.reload.reloadSession(); diff --git a/test/moves/whirlwind.test.ts b/test/moves/whirlwind.test.ts index 56c4017fbb5..c457bdb67d7 100644 --- a/test/moves/whirlwind.test.ts +++ b/test/moves/whirlwind.test.ts @@ -180,7 +180,7 @@ describe("Moves - Whirlwind", () => { expect(eligibleEnemy.length).toBe(1); // Spy on the queueMessage function - const queueSpy = vi.spyOn(globalScene, "queueMessage"); + const queueSpy = vi.spyOn(globalScene.phaseManager, "queueMessage"); // Player uses Whirlwind; opponent uses Splash game.move.select(MoveId.WHIRLWIND); diff --git a/test/mystery-encounter/encounter-test-utils.ts b/test/mystery-encounter/encounter-test-utils.ts index 977f40bc90e..6954d6212cc 100644 --- a/test/mystery-encounter/encounter-test-utils.ts +++ b/test/mystery-encounter/encounter-test-utils.ts @@ -71,9 +71,9 @@ export async function runMysteryEncounterToEnd( // If a battle is started, fast forward to end of the battle game.onNextPrompt("CommandPhase", UiMode.COMMAND, () => { - game.scene.clearPhaseQueue(); - game.scene.clearPhaseQueueSplice(); - game.scene.unshiftPhase(new VictoryPhase(0)); + game.scene.phaseManager.clearPhaseQueue(); + game.scene.phaseManager.clearPhaseQueueSplice(); + game.scene.phaseManager.unshiftPhase(new VictoryPhase(0)); game.endPhase(); }); @@ -197,14 +197,14 @@ async function handleSecondaryOptionSelect(game: GameManager, pokemonNo: number, * @param runRewardsPhase */ export async function skipBattleRunMysteryEncounterRewardsPhase(game: GameManager, runRewardsPhase = true) { - game.scene.clearPhaseQueue(); - game.scene.clearPhaseQueueSplice(); + game.scene.phaseManager.clearPhaseQueue(); + game.scene.phaseManager.clearPhaseQueueSplice(); game.scene.getEnemyParty().forEach(p => { p.hp = 0; p.status = new Status(StatusEffect.FAINT); game.scene.field.remove(p); }); - game.scene.pushPhase(new VictoryPhase(0)); + game.scene.phaseManager.pushPhase(new VictoryPhase(0)); game.phaseInterceptor.superEndPhase(); game.setMode(UiMode.MESSAGE); await game.phaseInterceptor.to(MysteryEncounterRewardsPhase, runRewardsPhase); diff --git a/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts b/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts index 3c6305a77dc..d713db0aff8 100644 --- a/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts +++ b/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts @@ -107,7 +107,7 @@ describe("A Trainer's Test - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); const enemyField = scene.getEnemyField(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyField.length).toBe(1); expect(scene.currentBattle.trainer).toBeDefined(); expect( @@ -132,7 +132,7 @@ describe("A Trainer's Test - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); const eggsAfter = scene.gameData.eggs; expect(eggsAfter).toBeDefined(); @@ -162,7 +162,7 @@ describe("A Trainer's Test - Mystery Encounter", () => { }); it("Should fully heal the party", async () => { - const phaseSpy = vi.spyOn(scene, "unshiftPhase"); + const phaseSpy = vi.spyOn(scene.phaseManager, "unshiftPhase"); await game.runToMysteryEncounter(MysteryEncounterType.A_TRAINERS_TEST, defaultParty); await runMysteryEncounterToEnd(game, 2); @@ -180,7 +180,7 @@ describe("A Trainer's Test - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 2); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); const eggsAfter = scene.gameData.eggs; expect(eggsAfter).toBeDefined(); diff --git a/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts b/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts index f13f6e0b072..f5bc1a62528 100644 --- a/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts +++ b/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts @@ -129,13 +129,13 @@ describe("Absolute Avarice - Mystery Encounter", () => { }); it("should start battle against Greedent", async () => { - const phaseSpy = vi.spyOn(scene, "pushPhase"); + const phaseSpy = vi.spyOn(scene.phaseManager, "pushPhase"); await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); const enemyField = scene.getEnemyField(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyField.length).toBe(1); expect(enemyField[0].species.speciesId).toBe(SpeciesId.GREEDENT); const moveset = enemyField[0].moveset.map(m => m.moveId); @@ -152,7 +152,7 @@ describe("Absolute Avarice - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); for (const partyPokemon of scene.getPlayerParty()) { const pokemonId = partyPokemon.id; diff --git a/test/mystery-encounter/encounters/berries-abound-encounter.test.ts b/test/mystery-encounter/encounters/berries-abound-encounter.test.ts index c3af2d9fe13..566576995a5 100644 --- a/test/mystery-encounter/encounters/berries-abound-encounter.test.ts +++ b/test/mystery-encounter/encounters/berries-abound-encounter.test.ts @@ -116,7 +116,7 @@ describe("Berries Abound - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); const enemyField = scene.getEnemyField(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyField.length).toBe(1); expect(enemyField[0].species.speciesId).toBe(speciesToSpawn); }); @@ -137,7 +137,7 @@ describe("Berries Abound - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); const berriesAfter = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[]; const berriesAfterCount = berriesAfter.reduce((a, b) => a + b.stackCount, 0); @@ -150,7 +150,7 @@ describe("Berries Abound - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); @@ -190,7 +190,7 @@ describe("Berries Abound - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 2, undefined, true); const enemyField = scene.getEnemyField(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyField.length).toBe(1); expect(enemyField[0].species.speciesId).toBe(speciesToSpawn); @@ -214,7 +214,7 @@ describe("Berries Abound - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 2, undefined, true); const enemyField = scene.getEnemyField(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyField.length).toBe(1); expect(enemyField[0].species.speciesId).toBe(speciesToSpawn); @@ -235,7 +235,7 @@ describe("Berries Abound - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 2); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); diff --git a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts index 0cc990a405a..7e569d0cdf7 100644 --- a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts +++ b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts @@ -232,7 +232,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); const enemyParty = scene.getEnemyParty(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyParty.length).toBe(2); expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN); expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL); @@ -245,7 +245,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); const enemyParty = scene.getEnemyParty(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyParty.length).toBe(3); expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN); expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL); @@ -259,7 +259,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); const enemyParty = scene.getEnemyParty(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyParty.length).toBe(4); expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN); expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL); @@ -274,7 +274,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); const enemyParty = scene.getEnemyParty(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyParty.length).toBe(5); expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN); expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL); @@ -290,7 +290,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); const enemyParty = scene.getEnemyParty(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyParty.length).toBe(5); expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN); expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL); @@ -308,7 +308,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); const enemyParty = scene.getEnemyParty(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyParty.length).toBe(5); expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN); expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL); @@ -326,7 +326,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); const enemyParty = scene.getEnemyParty(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyParty.length).toBe(5); expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN); expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL); @@ -344,7 +344,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); const enemyParty = scene.getEnemyParty(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyParty.length).toBe(5); expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN); expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL); @@ -366,7 +366,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterRewardsPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterRewardsPhase.name); game.phaseInterceptor["prompts"] = []; // Clear out prompt handlers game.onNextPrompt("MysteryEncounterRewardsPhase", UiMode.OPTION_SELECT, () => { game.phaseInterceptor.superEndPhase(); @@ -398,7 +398,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, [SpeciesId.ABRA]); await game.phaseInterceptor.to(MysteryEncounterPhase, false); - const encounterPhase = scene.getCurrentPhase(); + const encounterPhase = scene.phaseManager.getCurrentPhase(); expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase; vi.spyOn(mysteryEncounterPhase, "continueEncounter"); @@ -407,7 +407,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { await runSelectMysteryEncounterOption(game, 2); - expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); @@ -417,7 +417,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, defaultParty); await runMysteryEncounterToEnd(game, 2); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); @@ -436,7 +436,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { ]); await runMysteryEncounterToEnd(game, 2); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); @@ -458,7 +458,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { ]); await runMysteryEncounterToEnd(game, 2); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); @@ -482,7 +482,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { ]); await runMysteryEncounterToEnd(game, 2); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); @@ -534,7 +534,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { await game.phaseInterceptor.to(MysteryEncounterPhase, false); game.scene.modifiers = []; - const encounterPhase = scene.getCurrentPhase(); + const encounterPhase = scene.phaseManager.getCurrentPhase(); expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase; vi.spyOn(mysteryEncounterPhase, "continueEncounter"); @@ -543,7 +543,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { await runSelectMysteryEncounterOption(game, 3); - expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); @@ -558,7 +558,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); diff --git a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index d83f8d0642c..fc0ef0e5a86 100644 --- a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -167,13 +167,13 @@ describe("Clowning Around - Mystery Encounter", () => { }); it("should start double battle against the clown", async () => { - const phaseSpy = vi.spyOn(scene, "pushPhase"); + const phaseSpy = vi.spyOn(scene.phaseManager, "pushPhase"); await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); const enemyField = scene.getEnemyField(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyField.length).toBe(2); expect(enemyField[0].species.speciesId).toBe(SpeciesId.MR_MIME); expect(enemyField[0].moveset).toEqual([ @@ -202,7 +202,7 @@ describe("Clowning Around - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); const abilityToTrain = scene.currentBattle.mysteryEncounter?.misc.ability; @@ -217,7 +217,7 @@ describe("Clowning Around - Mystery Encounter", () => { vi.spyOn(partyUiHandler, "show"); game.endPhase(); await game.phaseInterceptor.to(PostMysteryEncounterPhase); - expect(scene.getCurrentPhase()?.constructor.name).toBe(PostMysteryEncounterPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(PostMysteryEncounterPhase.name); // Wait for Yes/No confirmation to appear await vi.waitFor(() => expect(optionSelectUiHandler.show).toHaveBeenCalled()); diff --git a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts index 501ff0c45e8..76a562b5851 100644 --- a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts +++ b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts @@ -98,7 +98,7 @@ describe("Dancing Lessons - Mystery Encounter", () => { }); it("should start battle against Oricorio", async () => { - const phaseSpy = vi.spyOn(scene, "pushPhase"); + const phaseSpy = vi.spyOn(scene.phaseManager, "pushPhase"); await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty); // Make party lead's level arbitrarily high to not get KOed by move @@ -108,7 +108,7 @@ describe("Dancing Lessons - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); const enemyField = scene.getEnemyField(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyField.length).toBe(1); expect(enemyField[0].species.speciesId).toBe(SpeciesId.ORICORIO); expect(enemyField[0].summonData.statStages).toEqual([1, 1, 1, 1, 0, 0, 0]); @@ -129,7 +129,7 @@ describe("Dancing Lessons - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); @@ -158,7 +158,7 @@ describe("Dancing Lessons - Mystery Encounter", () => { }); it("Should select a pokemon to learn Revelation Dance", async () => { - const phaseSpy = vi.spyOn(scene, "unshiftPhase"); + const phaseSpy = vi.spyOn(scene.phaseManager, "unshiftPhase"); await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty); scene.getPlayerParty()[0].moveset = []; @@ -219,7 +219,7 @@ describe("Dancing Lessons - Mystery Encounter", () => { scene.getPlayerParty().forEach(p => (p.moveset = [])); await game.phaseInterceptor.to(MysteryEncounterPhase, false); - const encounterPhase = scene.getCurrentPhase(); + const encounterPhase = scene.phaseManager.getCurrentPhase(); expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase; vi.spyOn(mysteryEncounterPhase, "continueEncounter"); @@ -229,7 +229,7 @@ describe("Dancing Lessons - Mystery Encounter", () => { await runSelectMysteryEncounterOption(game, 3); const partyCountAfter = scene.getPlayerParty().length; - expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); diff --git a/test/mystery-encounter/encounters/delibirdy-encounter.test.ts b/test/mystery-encounter/encounters/delibirdy-encounter.test.ts index 58496e957c0..0d1094831bc 100644 --- a/test/mystery-encounter/encounters/delibirdy-encounter.test.ts +++ b/test/mystery-encounter/encounters/delibirdy-encounter.test.ts @@ -153,7 +153,7 @@ describe("Delibird-y - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); await game.phaseInterceptor.to(MysteryEncounterPhase, false); - const encounterPhase = scene.getCurrentPhase(); + const encounterPhase = scene.phaseManager.getCurrentPhase(); expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase; vi.spyOn(mysteryEncounterPhase, "continueEncounter"); @@ -162,7 +162,7 @@ describe("Delibird-y - Mystery Encounter", () => { await runSelectMysteryEncounterOption(game, 1); - expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); @@ -308,7 +308,7 @@ describe("Delibird-y - Mystery Encounter", () => { await game.phaseInterceptor.to(MysteryEncounterPhase, false); - const encounterPhase = scene.getCurrentPhase(); + const encounterPhase = scene.phaseManager.getCurrentPhase(); expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase; vi.spyOn(mysteryEncounterPhase, "continueEncounter"); @@ -317,7 +317,7 @@ describe("Delibird-y - Mystery Encounter", () => { await runSelectMysteryEncounterOption(game, 2); - expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); @@ -441,7 +441,7 @@ describe("Delibird-y - Mystery Encounter", () => { await game.phaseInterceptor.to(MysteryEncounterPhase, false); - const encounterPhase = scene.getCurrentPhase(); + const encounterPhase = scene.phaseManager.getCurrentPhase(); expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase; vi.spyOn(mysteryEncounterPhase, "continueEncounter"); @@ -450,7 +450,7 @@ describe("Delibird-y - Mystery Encounter", () => { await runSelectMysteryEncounterOption(game, 3); - expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); diff --git a/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts b/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts index c2974def16e..3feb44bbe91 100644 --- a/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts +++ b/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts @@ -95,7 +95,7 @@ describe("Department Store Sale - Mystery Encounter", () => { it("should have shop with only TMs", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await runMysteryEncounterToEnd(game, 1); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); @@ -132,7 +132,7 @@ describe("Department Store Sale - Mystery Encounter", () => { it("should have shop with only Vitamins", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await runMysteryEncounterToEnd(game, 2); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); @@ -172,7 +172,7 @@ describe("Department Store Sale - Mystery Encounter", () => { it("should have shop with only X Items", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await runMysteryEncounterToEnd(game, 3); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); @@ -212,7 +212,7 @@ describe("Department Store Sale - Mystery Encounter", () => { it("should have shop with only Pokeballs", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await runMysteryEncounterToEnd(game, 4); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); diff --git a/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts b/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts index ba9ea4126da..76b15106ef1 100644 --- a/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts +++ b/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts @@ -157,13 +157,13 @@ describe("Fiery Fallout - Mystery Encounter", () => { }); it("should start battle against 2 Volcarona", async () => { - const phaseSpy = vi.spyOn(scene, "pushPhase"); + const phaseSpy = vi.spyOn(scene.phaseManager, "pushPhase"); await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); const enemyField = scene.getEnemyField(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyField.length).toBe(2); expect(enemyField[0].species.speciesId).toBe(SpeciesId.VOLCARONA); expect(enemyField[1].species.speciesId).toBe(SpeciesId.VOLCARONA); @@ -179,7 +179,7 @@ describe("Fiery Fallout - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); const leadPokemonId = scene.getPlayerParty()?.[0].id; const leadPokemonItems = scene.findModifiers( @@ -268,7 +268,7 @@ describe("Fiery Fallout - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty); await runMysteryEncounterToEnd(game, 3); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); const leadPokemonItems = scene.getPlayerParty()?.[0].getHeldItems() as PokemonHeldItemModifier[]; const item = leadPokemonItems.find(i => i instanceof AttackTypeBoosterModifier); @@ -288,13 +288,13 @@ describe("Fiery Fallout - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, [SpeciesId.MAGIKARP]); await game.phaseInterceptor.to(MysteryEncounterPhase, false); - const encounterPhase = scene.getCurrentPhase(); + const encounterPhase = scene.phaseManager.getCurrentPhase(); expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); const continueEncounterSpy = vi.spyOn(encounterPhase as MysteryEncounterPhase, "continueEncounter"); await runSelectMysteryEncounterOption(game, 3); - expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(continueEncounterSpy).not.toHaveBeenCalled(); }); }); diff --git a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts index 1b5bd9fc649..7038fff3117 100644 --- a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts +++ b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts @@ -111,7 +111,7 @@ describe("Fight or Flight - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); const enemyField = scene.getEnemyField(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyField.length).toBe(1); expect(enemyField[0].species.speciesId).toBe(speciesToSpawn); }); @@ -124,7 +124,7 @@ describe("Fight or Flight - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); @@ -158,7 +158,7 @@ describe("Fight or Flight - Mystery Encounter", () => { scene.getPlayerParty().forEach(p => (p.moveset = [])); await game.phaseInterceptor.to(MysteryEncounterPhase, false); - const encounterPhase = scene.getCurrentPhase(); + const encounterPhase = scene.phaseManager.getCurrentPhase(); expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase; vi.spyOn(mysteryEncounterPhase, "continueEncounter"); @@ -167,7 +167,7 @@ describe("Fight or Flight - Mystery Encounter", () => { await runSelectMysteryEncounterOption(game, 2); - expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); @@ -184,7 +184,7 @@ describe("Fight or Flight - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 2); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); diff --git a/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts b/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts index fcc2eda28f7..4eaf2cef1da 100644 --- a/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts +++ b/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts @@ -123,7 +123,7 @@ describe("Fun And Games! - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty); await game.phaseInterceptor.to(MysteryEncounterPhase, false); - const encounterPhase = scene.getCurrentPhase(); + const encounterPhase = scene.phaseManager.getCurrentPhase(); expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase; vi.spyOn(mysteryEncounterPhase, "continueEncounter"); @@ -132,7 +132,7 @@ describe("Fun And Games! - Mystery Encounter", () => { await runSelectMysteryEncounterOption(game, 1); - expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); @@ -144,7 +144,7 @@ describe("Fun And Games! - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty); await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(scene.getEnemyPokemon()?.species.speciesId).toBe(SpeciesId.WOBBUFFET); expect(scene.getEnemyPokemon()?.ivs).toEqual([0, 0, 0, 0, 0, 0]); expect(scene.getEnemyPokemon()?.nature).toBe(Nature.MILD); @@ -154,19 +154,19 @@ describe("Fun And Games! - Mystery Encounter", () => { }); // Turn 1 - (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); + (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); await game.phaseInterceptor.to(CommandPhase); // Turn 2 - (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); + (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); await game.phaseInterceptor.to(CommandPhase); // Turn 3 - (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); + (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); await game.phaseInterceptor.to(SelectModifierPhase, false); // Rewards - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); }); it("should have no items in rewards if Wubboffet doesn't take enough damage", async () => { @@ -174,18 +174,18 @@ describe("Fun And Games! - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty); await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); game.onNextPrompt("MessagePhase", UiMode.MESSAGE, () => { game.endPhase(); }); // Skip minigame scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0; - (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); + (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); await game.phaseInterceptor.to(SelectModifierPhase, false); // Rewards - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); @@ -201,7 +201,7 @@ describe("Fun And Games! - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty); await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); game.onNextPrompt("MessagePhase", UiMode.MESSAGE, () => { game.endPhase(); }); @@ -210,11 +210,11 @@ describe("Fun And Games! - Mystery Encounter", () => { const wobbuffet = scene.getEnemyPokemon()!; wobbuffet.hp = Math.floor(0.2 * wobbuffet.getMaxHp()); scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0; - (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); + (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); await game.phaseInterceptor.to(SelectModifierPhase, false); // Rewards - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); @@ -231,7 +231,7 @@ describe("Fun And Games! - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty); await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); game.onNextPrompt("MessagePhase", UiMode.MESSAGE, () => { game.endPhase(); }); @@ -240,11 +240,11 @@ describe("Fun And Games! - Mystery Encounter", () => { const wobbuffet = scene.getEnemyPokemon()!; wobbuffet.hp = Math.floor(0.1 * wobbuffet.getMaxHp()); scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0; - (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); + (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); await game.phaseInterceptor.to(SelectModifierPhase, false); // Rewards - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); @@ -261,7 +261,7 @@ describe("Fun And Games! - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty); await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); game.onNextPrompt("MessagePhase", UiMode.MESSAGE, () => { game.endPhase(); }); @@ -270,11 +270,11 @@ describe("Fun And Games! - Mystery Encounter", () => { const wobbuffet = scene.getEnemyPokemon()!; wobbuffet.hp = 1; scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0; - (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); + (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); await game.phaseInterceptor.to(SelectModifierPhase, false); // Rewards - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); diff --git a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts index 73bf3cc4eba..96c4adf67c2 100644 --- a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts +++ b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts @@ -228,7 +228,7 @@ describe("Global Trade System - Mystery Encounter", () => { await scene.updateModifiers(true); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); diff --git a/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts b/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts index 10b96d84667..8f041a14002 100644 --- a/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts +++ b/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts @@ -137,7 +137,7 @@ describe("Lost at Sea - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.LOST_AT_SEA, [SpeciesId.ARCANINE]); await game.phaseInterceptor.to(MysteryEncounterPhase, false); - const encounterPhase = scene.getCurrentPhase(); + const encounterPhase = scene.phaseManager.getCurrentPhase(); expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase; vi.spyOn(mysteryEncounterPhase, "continueEncounter"); @@ -146,7 +146,7 @@ describe("Lost at Sea - Mystery Encounter", () => { await runSelectMysteryEncounterOption(game, 1); - expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); @@ -202,7 +202,7 @@ describe("Lost at Sea - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.LOST_AT_SEA, [SpeciesId.ARCANINE]); await game.phaseInterceptor.to(MysteryEncounterPhase, false); - const encounterPhase = scene.getCurrentPhase(); + const encounterPhase = scene.phaseManager.getCurrentPhase(); expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase; vi.spyOn(mysteryEncounterPhase, "continueEncounter"); @@ -211,7 +211,7 @@ describe("Lost at Sea - Mystery Encounter", () => { await runSelectMysteryEncounterOption(game, 2); - expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); diff --git a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts index b93fbeb2673..948e42547de 100644 --- a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts +++ b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts @@ -155,7 +155,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); }); @@ -165,7 +165,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); @@ -199,7 +199,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty); await runMysteryEncounterToEnd(game, 2, undefined, true); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); }); @@ -209,7 +209,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 2, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); @@ -256,7 +256,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty); await runMysteryEncounterToEnd(game, 3, undefined, true); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); }); @@ -266,7 +266,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 3, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); diff --git a/test/mystery-encounter/encounters/part-timer-encounter.test.ts b/test/mystery-encounter/encounters/part-timer-encounter.test.ts index 1b9d24b5ce6..fa85f373a35 100644 --- a/test/mystery-encounter/encounters/part-timer-encounter.test.ts +++ b/test/mystery-encounter/encounters/part-timer-encounter.test.ts @@ -238,7 +238,7 @@ describe("Part-Timer - Mystery Encounter", () => { scene.getPlayerParty().forEach(p => (p.moveset = [])); await game.phaseInterceptor.to(MysteryEncounterPhase, false); - const encounterPhase = scene.getCurrentPhase(); + const encounterPhase = scene.phaseManager.getCurrentPhase(); expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase; vi.spyOn(mysteryEncounterPhase, "continueEncounter"); @@ -247,7 +247,7 @@ describe("Part-Timer - Mystery Encounter", () => { await runSelectMysteryEncounterOption(game, 3); - expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); diff --git a/test/mystery-encounter/encounters/safari-zone.test.ts b/test/mystery-encounter/encounters/safari-zone.test.ts index c6bde3a7e7a..dcaf25dd512 100644 --- a/test/mystery-encounter/encounters/safari-zone.test.ts +++ b/test/mystery-encounter/encounters/safari-zone.test.ts @@ -115,7 +115,7 @@ describe("Safari Zone - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.SAFARI_ZONE, defaultParty); await game.phaseInterceptor.to(MysteryEncounterPhase, false); - const encounterPhase = scene.getCurrentPhase(); + const encounterPhase = scene.phaseManager.getCurrentPhase(); expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase; vi.spyOn(mysteryEncounterPhase, "continueEncounter"); @@ -124,7 +124,7 @@ describe("Safari Zone - Mystery Encounter", () => { await runSelectMysteryEncounterOption(game, 1); - expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); diff --git a/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts b/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts index 039b1dacf31..6c8daed998c 100644 --- a/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts +++ b/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts @@ -153,7 +153,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, defaultParty); await game.phaseInterceptor.to(MysteryEncounterPhase, false); - const encounterPhase = scene.getCurrentPhase(); + const encounterPhase = scene.phaseManager.getCurrentPhase(); expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase; vi.spyOn(mysteryEncounterPhase, "continueEncounter"); @@ -162,7 +162,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { await runSelectMysteryEncounterOption(game, 1); - expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); @@ -172,7 +172,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); }); it("should transport to a new area", async () => { @@ -225,7 +225,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, [SpeciesId.BLASTOISE]); await game.phaseInterceptor.to(MysteryEncounterPhase, false); - const encounterPhase = scene.getCurrentPhase(); + const encounterPhase = scene.phaseManager.getCurrentPhase(); expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase; vi.spyOn(mysteryEncounterPhase, "continueEncounter"); @@ -234,7 +234,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { await runSelectMysteryEncounterOption(game, 2); - expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); @@ -244,7 +244,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, [SpeciesId.METAGROSS]); await runMysteryEncounterToEnd(game, 2, undefined, true); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); }); it("should transport to a new area", async () => { @@ -305,7 +305,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 3, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); diff --git a/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts b/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts index 14c9287f5f3..0d5f67b8815 100644 --- a/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts @@ -160,7 +160,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { expect(successfullyLoaded).toBe(true); // Check usual battle stuff - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); expect(scene.getPlayerParty().length).toBe(1); @@ -179,7 +179,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); const eggsAfter = scene.gameData.eggs; const commonEggs = scene.currentBattle.mysteryEncounter!.misc.pokemon1CommonEggs; @@ -245,7 +245,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { expect(successfullyLoaded).toBe(true); // Check usual battle stuff - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); expect(scene.getPlayerParty().length).toBe(1); @@ -264,7 +264,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 2, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); const eggsAfter = scene.gameData.eggs; const commonEggs = scene.currentBattle.mysteryEncounter!.misc.pokemon2CommonEggs; @@ -327,7 +327,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { expect(successfullyLoaded).toBe(true); // Check usual battle stuff - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); expect(scene.getPlayerParty().length).toBe(1); @@ -346,7 +346,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 3, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); const eggsAfter = scene.gameData.eggs; const commonEggs = scene.currentBattle.mysteryEncounter!.misc.pokemon3CommonEggs; diff --git a/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts b/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts index 73632990dbf..13d7c2502a6 100644 --- a/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts @@ -175,7 +175,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.THE_POKEMON_SALESMAN, defaultParty); await game.phaseInterceptor.to(MysteryEncounterPhase, false); - const encounterPhase = scene.getCurrentPhase(); + const encounterPhase = scene.phaseManager.getCurrentPhase(); expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase; vi.spyOn(mysteryEncounterPhase, "continueEncounter"); @@ -184,7 +184,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => { await runSelectMysteryEncounterOption(game, 1); - expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); diff --git a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index 2187bad5775..e50a19a0a80 100644 --- a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -189,13 +189,13 @@ describe("The Strong Stuff - Mystery Encounter", () => { }); it("should start battle against Shuckle", async () => { - const phaseSpy = vi.spyOn(scene, "pushPhase"); + const phaseSpy = vi.spyOn(scene.phaseManager, "pushPhase"); await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty); await runMysteryEncounterToEnd(game, 2, undefined, true); const enemyField = scene.getEnemyField(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyField.length).toBe(1); expect(enemyField[0].species.speciesId).toBe(SpeciesId.SHUCKLE); expect(enemyField[0].summonData.statStages).toEqual([0, 1, 0, 1, 0, 0, 0]); @@ -233,7 +233,7 @@ describe("The Strong Stuff - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 2, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); diff --git a/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts b/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts index 57b6e881683..87a3852615d 100644 --- a/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts @@ -265,7 +265,7 @@ describe("The Winstrate Challenge - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.trainer!.config.trainerType).toBe(TrainerType.VICTOR); expect(scene.currentBattle.mysteryEncounter?.enemyPartyConfigs.length).toBe(4); @@ -298,7 +298,7 @@ describe("The Winstrate Challenge - Mystery Encounter", () => { // Should have Macho Brace in the rewards await skipBattleToNextBattle(game, true); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); @@ -328,7 +328,7 @@ describe("The Winstrate Challenge - Mystery Encounter", () => { }); it("Should fully heal the party", async () => { - const phaseSpy = vi.spyOn(scene, "unshiftPhase"); + const phaseSpy = vi.spyOn(scene.phaseManager, "unshiftPhase"); await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty); await runMysteryEncounterToEnd(game, 2); @@ -340,7 +340,7 @@ describe("The Winstrate Challenge - Mystery Encounter", () => { it("should have a Rarer Candy in the rewards", async () => { await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty); await runMysteryEncounterToEnd(game, 2); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); @@ -359,8 +359,8 @@ describe("The Winstrate Challenge - Mystery Encounter", () => { * @param isFinalBattle */ async function skipBattleToNextBattle(game: GameManager, isFinalBattle = false) { - game.scene.clearPhaseQueue(); - game.scene.clearPhaseQueueSplice(); + game.scene.phaseManager.clearPhaseQueue(); + game.scene.phaseManager.clearPhaseQueueSplice(); const commandUiHandler = game.scene.ui.handlers[UiMode.COMMAND]; commandUiHandler.clear(); game.scene.getEnemyParty().forEach(p => { @@ -369,7 +369,7 @@ async function skipBattleToNextBattle(game: GameManager, isFinalBattle = false) game.scene.field.remove(p); }); game.phaseInterceptor["onHold"] = []; - game.scene.pushPhase(new VictoryPhase(0)); + game.scene.phaseManager.pushPhase(new VictoryPhase(0)); game.phaseInterceptor.superEndPhase(); if (isFinalBattle) { await game.phaseInterceptor.to(MysteryEncounterRewardsPhase); diff --git a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index 94011b4b01d..999936c8832 100644 --- a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -173,7 +173,7 @@ describe("Trash to Treasure - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.TRASH_TO_TREASURE, defaultParty); await runMysteryEncounterToEnd(game, 1); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); const leftovers = scene.findModifier(m => m instanceof TurnHealModifier) as TurnHealModifier; expect(leftovers).toBeDefined(); @@ -215,13 +215,13 @@ describe("Trash to Treasure - Mystery Encounter", () => { }); it("should start battle against Garbodor", async () => { - const phaseSpy = vi.spyOn(scene, "pushPhase"); + const phaseSpy = vi.spyOn(scene.phaseManager, "pushPhase"); await game.runToMysteryEncounter(MysteryEncounterType.TRASH_TO_TREASURE, defaultParty); await runMysteryEncounterToEnd(game, 2, undefined, true); const enemyField = scene.getEnemyField(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyField.length).toBe(1); expect(enemyField[0].species.speciesId).toBe(SpeciesId.GARBODOR); expect(enemyField[0].moveset).toEqual([ @@ -243,7 +243,7 @@ describe("Trash to Treasure - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 2, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); diff --git a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts index 4fb0a231853..0c3131de1d2 100644 --- a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts +++ b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts @@ -113,8 +113,8 @@ describe("Uncommon Breed - Mystery Encounter", () => { }); it.skip("should start a fight against the boss below wave 50", async () => { - const phaseSpy = vi.spyOn(scene, "pushPhase"); - const unshiftPhaseSpy = vi.spyOn(scene, "unshiftPhase"); + const phaseSpy = vi.spyOn(scene.phaseManager, "pushPhase"); + const unshiftPhaseSpy = vi.spyOn(scene.phaseManager, "unshiftPhase"); await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty); const config = game.scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]; @@ -123,7 +123,7 @@ describe("Uncommon Breed - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); const enemyField = scene.getEnemyField(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyField.length).toBe(1); expect(enemyField[0].species.speciesId).toBe(speciesToSpawn); @@ -140,8 +140,8 @@ describe("Uncommon Breed - Mystery Encounter", () => { it.skip("should start a fight against the boss above wave 50", async () => { game.override.startingWave(57); - const phaseSpy = vi.spyOn(scene, "pushPhase"); - const unshiftPhaseSpy = vi.spyOn(scene, "unshiftPhase"); + const phaseSpy = vi.spyOn(scene.phaseManager, "pushPhase"); + const unshiftPhaseSpy = vi.spyOn(scene.phaseManager, "unshiftPhase"); await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty); const config = game.scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]; @@ -150,7 +150,7 @@ describe("Uncommon Breed - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); const enemyField = scene.getEnemyField(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyField.length).toBe(1); expect(enemyField[0].species.speciesId).toBe(speciesToSpawn); @@ -193,7 +193,7 @@ describe("Uncommon Breed - Mystery Encounter", () => { await scene.updateModifiers(true); await game.phaseInterceptor.to(MysteryEncounterPhase, false); - const encounterPhase = scene.getCurrentPhase(); + const encounterPhase = scene.phaseManager.getCurrentPhase(); expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase; vi.spyOn(mysteryEncounterPhase, "continueEncounter"); @@ -202,7 +202,7 @@ describe("Uncommon Breed - Mystery Encounter", () => { await runSelectMysteryEncounterOption(game, 2); - expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); @@ -253,7 +253,7 @@ describe("Uncommon Breed - Mystery Encounter", () => { scene.getPlayerParty().forEach(p => (p.moveset = [])); await game.phaseInterceptor.to(MysteryEncounterPhase, false); - const encounterPhase = scene.getCurrentPhase(); + const encounterPhase = scene.phaseManager.getCurrentPhase(); expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase; vi.spyOn(mysteryEncounterPhase, "continueEncounter"); @@ -262,7 +262,7 @@ describe("Uncommon Breed - Mystery Encounter", () => { await runSelectMysteryEncounterOption(game, 3); - expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); diff --git a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts index 163a15a715f..2ad74b48540 100644 --- a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -118,7 +118,7 @@ describe("Weird Dream - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); const pokemonAfter = scene.getPlayerParty(); const bstsAfter = pokemonAfter.map(pokemon => pokemon.getSpeciesForm().getBaseStatTotal()); @@ -141,7 +141,7 @@ describe("Weird Dream - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty); await runMysteryEncounterToEnd(game, 1); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); @@ -187,7 +187,7 @@ describe("Weird Dream - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 2, undefined, true); const enemyField = scene.getEnemyField(); - expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyField.length).toBe(1); expect(scene.getEnemyParty().length).toBe(scene.getPlayerParty().length); }); @@ -197,7 +197,7 @@ describe("Weird Dream - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 2, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.run(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); diff --git a/test/mystery-encounter/mystery-encounter-utils.test.ts b/test/mystery-encounter/mystery-encounter-utils.test.ts index cd29a203e62..80e2fb77f2b 100644 --- a/test/mystery-encounter/mystery-encounter-utils.test.ts +++ b/test/mystery-encounter/mystery-encounter-utils.test.ts @@ -291,8 +291,8 @@ describe("Mystery Encounter Utils", () => { it("queues a message with encounter dialogue tokens", async () => { scene.currentBattle.mysteryEncounter = new MysteryEncounter(null); scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value"); - const spy = vi.spyOn(game.scene, "queueMessage"); - const phaseSpy = vi.spyOn(game.scene, "unshiftPhase"); + const spy = vi.spyOn(game.scene.phaseManager, "queueMessage"); + const phaseSpy = vi.spyOn(game.scene.phaseManager, "unshiftPhase"); queueEncounterMessage("mysteryEncounter:unit_test_dialogue"); expect(spy).toHaveBeenCalledWith("mysteryEncounter:unit_test_dialogue", null, true); diff --git a/test/mystery-encounter/mystery-encounter.test.ts b/test/mystery-encounter/mystery-encounter.test.ts index 19dd17d5381..1f1d0f5826e 100644 --- a/test/mystery-encounter/mystery-encounter.test.ts +++ b/test/mystery-encounter/mystery-encounter.test.ts @@ -35,7 +35,7 @@ describe("Mystery Encounters", () => { ]); await game.phaseInterceptor.to(MysteryEncounterPhase, false); - expect(game.scene.getCurrentPhase()!.constructor.name).toBe(MysteryEncounterPhase.name); + expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(MysteryEncounterPhase.name); }); it("Encounters should not run below wave 10", async () => { diff --git a/test/phases/mystery-encounter-phase.test.ts b/test/phases/mystery-encounter-phase.test.ts index ece5a221e00..d078c2398b4 100644 --- a/test/phases/mystery-encounter-phase.test.ts +++ b/test/phases/mystery-encounter-phase.test.ts @@ -41,7 +41,7 @@ describe("Mystery Encounter Phases", () => { ]); await game.phaseInterceptor.to(MysteryEncounterPhase, false); - expect(game.scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(game.scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); }); it("Runs MysteryEncounterPhase", async () => { @@ -87,7 +87,9 @@ describe("Mystery Encounter Phases", () => { // Waitfor required so that option select messages and preOptionPhase logic are handled await vi.waitFor(() => - expect(game.scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterOptionSelectedPhase.name), + expect(game.scene.phaseManager.getCurrentPhase()?.constructor.name).toBe( + MysteryEncounterOptionSelectedPhase.name, + ), ); expect(ui.getMode()).toBe(UiMode.MESSAGE); expect(ui.showDialogue).toHaveBeenCalledTimes(1); diff --git a/test/phases/phases.test.ts b/test/phases/phases.test.ts index 2483cfb317f..8f7b1a1ea66 100644 --- a/test/phases/phases.test.ts +++ b/test/phases/phases.test.ts @@ -30,7 +30,7 @@ describe("Phases", () => { describe("LoginPhase", () => { it("should start the login phase", async () => { const loginPhase = new LoginPhase(); - scene.unshiftPhase(loginPhase); + scene.phaseManager.unshiftPhase(loginPhase); await game.phaseInterceptor.to(LoginPhase); expect(scene.ui.getMode()).to.equal(UiMode.MESSAGE); }); @@ -39,7 +39,7 @@ describe("Phases", () => { describe("TitlePhase", () => { it("should start the title phase", async () => { const titlePhase = new TitlePhase(); - scene.unshiftPhase(titlePhase); + scene.phaseManager.unshiftPhase(titlePhase); await game.phaseInterceptor.to(TitlePhase); expect(scene.ui.getMode()).to.equal(UiMode.TITLE); }); @@ -48,7 +48,7 @@ describe("Phases", () => { describe("UnavailablePhase", () => { it("should start the unavailable phase", async () => { const unavailablePhase = new UnavailablePhase(); - scene.unshiftPhase(unavailablePhase); + scene.phaseManager.unshiftPhase(unavailablePhase); await game.phaseInterceptor.to(UnavailablePhase); expect(scene.ui.getMode()).to.equal(UiMode.UNAVAILABLE); }, 20000); diff --git a/test/phases/select-modifier-phase.test.ts b/test/phases/select-modifier-phase.test.ts index 72496d5f17b..083b7d16f10 100644 --- a/test/phases/select-modifier-phase.test.ts +++ b/test/phases/select-modifier-phase.test.ts @@ -48,7 +48,7 @@ describe("SelectModifierPhase", () => { it("should start a select modifier phase", async () => { initSceneWithoutEncounterPhase(scene, [SpeciesId.ABRA, SpeciesId.VOLCARONA]); const selectModifierPhase = new SelectModifierPhase(); - scene.unshiftPhase(selectModifierPhase); + scene.phaseManager.unshiftPhase(selectModifierPhase); await game.phaseInterceptor.to(SelectModifierPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); @@ -96,7 +96,7 @@ describe("SelectModifierPhase", () => { await game.phaseInterceptor.to("SelectModifierPhase"); // TODO: nagivate the ui to reroll somehow - //const smphase = scene.getCurrentPhase() as SelectModifierPhase; + //const smphase = scene.phaseManager.getCurrentPhase() as SelectModifierPhase; expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( h => h instanceof ModifierSelectUiHandler, @@ -164,7 +164,7 @@ describe("SelectModifierPhase", () => { ], }; const selectModifierPhase = new SelectModifierPhase(0, undefined, customModifiers); - scene.unshiftPhase(selectModifierPhase); + scene.phaseManager.unshiftPhase(selectModifierPhase); game.move.select(MoveId.SPLASH); await game.phaseInterceptor.to("SelectModifierPhase"); @@ -201,7 +201,7 @@ describe("SelectModifierPhase", () => { scene.getPlayerParty().push(pokemon, pokemon, pokemon, pokemon, pokemon, pokemon); const selectModifierPhase = new SelectModifierPhase(0, undefined, customModifiers); - scene.unshiftPhase(selectModifierPhase); + scene.phaseManager.unshiftPhase(selectModifierPhase); game.move.select(MoveId.SPLASH); await game.phaseInterceptor.to("SelectModifierPhase"); @@ -240,7 +240,7 @@ describe("SelectModifierPhase", () => { guaranteedModifierTiers: [ModifierTier.MASTER, ModifierTier.MASTER], }; const selectModifierPhase = new SelectModifierPhase(0, undefined, customModifiers); - scene.unshiftPhase(selectModifierPhase); + scene.phaseManager.unshiftPhase(selectModifierPhase); game.move.select(MoveId.SPLASH); await game.phaseInterceptor.run(SelectModifierPhase); @@ -264,7 +264,7 @@ describe("SelectModifierPhase", () => { fillRemaining: true, }; const selectModifierPhase = new SelectModifierPhase(0, undefined, customModifiers); - scene.unshiftPhase(selectModifierPhase); + scene.phaseManager.unshiftPhase(selectModifierPhase); game.move.select(MoveId.SPLASH); await game.phaseInterceptor.run(SelectModifierPhase); diff --git a/test/testUtils/gameManager.ts b/test/testUtils/gameManager.ts index 68d218b3988..edf0301447d 100644 --- a/test/testUtils/gameManager.ts +++ b/test/testUtils/gameManager.ts @@ -101,13 +101,13 @@ export default class GameManager { if (!firstTimeScene) { this.scene.reset(false, true); (this.scene.ui.handlers[UiMode.STARTER_SELECT] as StarterSelectUiHandler).clearStarterPreferences(); - this.scene.clearAllPhases(); + this.scene.phaseManager.clearAllPhases(); // Must be run after phase interceptor has been initialized. - this.scene.pushPhase(new LoginPhase()); - this.scene.pushPhase(new TitlePhase()); - this.scene.shiftPhase(); + this.scene.phaseManager.pushPhase(new LoginPhase()); + this.scene.phaseManager.pushPhase(new TitlePhase()); + this.scene.phaseManager.shiftPhase(); this.gameWrapper.scene = this.scene; } @@ -154,7 +154,7 @@ export default class GameManager { * Ends the current phase. */ endPhase() { - this.scene.getCurrentPhase()?.end(); + this.scene.phaseManager.getCurrentPhase()?.end(); } /** @@ -211,7 +211,7 @@ export default class GameManager { this.scene.gameMode = getGameMode(mode); const starters = generateStarter(this.scene, species); const selectStarterPhase = new SelectStarterPhase(); - this.scene.pushPhase(new EncounterPhase(false)); + this.scene.phaseManager.pushPhase(new EncounterPhase(false)); selectStarterPhase.initBattle(starters); }); @@ -247,7 +247,7 @@ export default class GameManager { this.scene.gameMode = getGameMode(GameModes.CLASSIC); const starters = generateStarter(this.scene, species); const selectStarterPhase = new SelectStarterPhase(); - this.scene.pushPhase(new EncounterPhase(false)); + this.scene.phaseManager.pushPhase(new EncounterPhase(false)); selectStarterPhase.initBattle(starters); }, () => this.isCurrentPhase(EncounterPhase), @@ -282,7 +282,7 @@ export default class GameManager { UiMode.TARGET_SELECT, () => { const handler = this.scene.ui.getHandler() as TargetSelectUiHandler; - const move = (this.scene.getCurrentPhase() as SelectTargetPhase) + const move = (this.scene.phaseManager.getCurrentPhase() as SelectTargetPhase) .getPokemon() .getMoveset() [movePosition].getMove(); @@ -397,7 +397,7 @@ export default class GameManager { */ isCurrentPhase(phaseTarget) { const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name; - return this.scene.getCurrentPhase()?.constructor.name === targetName; + return this.scene.phaseManager.getCurrentPhase()?.constructor.name === targetName; } /** @@ -449,7 +449,7 @@ export default class GameManager { async killPokemon(pokemon: PlayerPokemon | EnemyPokemon) { return new Promise(async (resolve, reject) => { pokemon.hp = 0; - this.scene.pushPhase(new FaintPhase(pokemon.getBattlerIndex(), true)); + this.scene.phaseManager.pushPhase(new FaintPhase(pokemon.getBattlerIndex(), true)); await this.phaseInterceptor.to(FaintPhase).catch(e => reject(e)); resolve(); }); @@ -529,7 +529,7 @@ export default class GameManager { async setTurnOrder(order: BattlerIndex[]): Promise { await this.phaseInterceptor.to(TurnStartPhase, false); - vi.spyOn(this.scene.getCurrentPhase() as TurnStartPhase, "getSpeedOrder").mockReturnValue(order); + vi.spyOn(this.scene.phaseManager.getCurrentPhase() as TurnStartPhase, "getSpeedOrder").mockReturnValue(order); } /** diff --git a/test/testUtils/helpers/challengeModeHelper.ts b/test/testUtils/helpers/challengeModeHelper.ts index d0533d3b5ba..f0b4b151d22 100644 --- a/test/testUtils/helpers/challengeModeHelper.ts +++ b/test/testUtils/helpers/challengeModeHelper.ts @@ -45,7 +45,7 @@ export class ChallengeModeHelper extends GameManagerHelper { this.game.scene.gameMode.challenges = this.challenges; const starters = generateStarter(this.game.scene, species); const selectStarterPhase = new SelectStarterPhase(); - this.game.scene.pushPhase(new EncounterPhase(false)); + this.game.scene.phaseManager.pushPhase(new EncounterPhase(false)); selectStarterPhase.initBattle(starters); }); diff --git a/test/testUtils/helpers/classicModeHelper.ts b/test/testUtils/helpers/classicModeHelper.ts index d5f0ceb4072..575000c2193 100644 --- a/test/testUtils/helpers/classicModeHelper.ts +++ b/test/testUtils/helpers/classicModeHelper.ts @@ -30,7 +30,7 @@ export class ClassicModeHelper extends GameManagerHelper { this.game.scene.gameMode = getGameMode(GameModes.CLASSIC); const starters = generateStarter(this.game.scene, species); const selectStarterPhase = new SelectStarterPhase(); - this.game.scene.pushPhase(new EncounterPhase(false)); + this.game.scene.phaseManager.pushPhase(new EncounterPhase(false)); selectStarterPhase.initBattle(starters); }); diff --git a/test/testUtils/helpers/moveHelper.ts b/test/testUtils/helpers/moveHelper.ts index 02b1efd837f..878a265ce84 100644 --- a/test/testUtils/helpers/moveHelper.ts +++ b/test/testUtils/helpers/moveHelper.ts @@ -23,7 +23,7 @@ export class MoveHelper extends GameManagerHelper { */ public async forceHit(): Promise { await this.game.phaseInterceptor.to(MoveEffectPhase, false); - const moveEffectPhase = this.game.scene.getCurrentPhase() as MoveEffectPhase; + const moveEffectPhase = this.game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase; vi.spyOn(moveEffectPhase.move, "calculateBattleAccuracy").mockReturnValue(-1); } @@ -34,7 +34,7 @@ export class MoveHelper extends GameManagerHelper { */ public async forceMiss(firstTargetOnly = false): Promise { await this.game.phaseInterceptor.to(MoveEffectPhase, false); - const moveEffectPhase = this.game.scene.getCurrentPhase() as MoveEffectPhase; + const moveEffectPhase = this.game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase; const accuracy = vi.spyOn(moveEffectPhase.move, "calculateBattleAccuracy"); if (firstTargetOnly) { @@ -54,10 +54,17 @@ export class MoveHelper extends GameManagerHelper { const movePosition = getMovePosition(this.game.scene, pkmIndex, move); this.game.onNextPrompt("CommandPhase", UiMode.COMMAND, () => { - this.game.scene.ui.setMode(UiMode.FIGHT, (this.game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + this.game.scene.ui.setMode( + UiMode.FIGHT, + (this.game.scene.phaseManager.getCurrentPhase() as CommandPhase).getFieldIndex(), + ); }); this.game.onNextPrompt("CommandPhase", UiMode.FIGHT, () => { - (this.game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); + (this.game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand( + Command.FIGHT, + movePosition, + false, + ); }); if (targetIndex !== null) { @@ -79,12 +86,12 @@ export class MoveHelper extends GameManagerHelper { this.game.onNextPrompt("CommandPhase", UiMode.COMMAND, () => { this.game.scene.ui.setMode( UiMode.FIGHT, - (this.game.scene.getCurrentPhase() as CommandPhase).getFieldIndex(), + (this.game.scene.phaseManager.getCurrentPhase() as CommandPhase).getFieldIndex(), Command.TERA, ); }); this.game.onNextPrompt("CommandPhase", UiMode.FIGHT, () => { - (this.game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.TERA, movePosition, false); + (this.game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.TERA, movePosition, false); }); if (targetIndex !== null) { @@ -171,7 +178,9 @@ export class MoveHelper extends GameManagerHelper { // Wait for the next EnemyCommandPhase to start await this.game.phaseInterceptor.to("EnemyCommandPhase", false); const enemy = - this.game.scene.getEnemyField()[(this.game.scene.getCurrentPhase() as EnemyCommandPhase).getFieldIndex()]; + this.game.scene.getEnemyField()[ + (this.game.scene.phaseManager.getCurrentPhase() as EnemyCommandPhase).getFieldIndex() + ]; const legalTargets = getMoveTargets(enemy, moveId); vi.spyOn(enemy, "getNextMove").mockReturnValueOnce({ @@ -205,7 +214,9 @@ export class MoveHelper extends GameManagerHelper { await this.game.phaseInterceptor.to("EnemyCommandPhase", false); const enemy = - this.game.scene.getEnemyField()[(this.game.scene.getCurrentPhase() as EnemyCommandPhase).getFieldIndex()]; + this.game.scene.getEnemyField()[ + (this.game.scene.phaseManager.getCurrentPhase() as EnemyCommandPhase).getFieldIndex() + ]; if ([Overrides.OPP_MOVESET_OVERRIDE].flat().length > 0) { vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([]); diff --git a/test/testUtils/helpers/reloadHelper.ts b/test/testUtils/helpers/reloadHelper.ts index 4a9e5356968..4f9d6c810f8 100644 --- a/test/testUtils/helpers/reloadHelper.ts +++ b/test/testUtils/helpers/reloadHelper.ts @@ -35,7 +35,7 @@ export class ReloadHelper extends GameManagerHelper { const scene = this.game.scene; const titlePhase = new TitlePhase(); - scene.clearPhaseQueue(); + scene.phaseManager.clearPhaseQueue(); // Set the last saved session to the desired session data vi.spyOn(scene.gameData, "getSession").mockReturnValue( @@ -43,7 +43,7 @@ export class ReloadHelper extends GameManagerHelper { resolve(this.sessionData); }), ); - scene.unshiftPhase(titlePhase); + scene.phaseManager.unshiftPhase(titlePhase); this.game.endPhase(); // End the currently ongoing battle // remove all persistent mods before loading diff --git a/test/testUtils/phaseInterceptor.ts b/test/testUtils/phaseInterceptor.ts index b7577550568..34fba2c145d 100644 --- a/test/testUtils/phaseInterceptor.ts +++ b/test/testUtils/phaseInterceptor.ts @@ -301,7 +301,7 @@ export default class PhaseInterceptor { pop() { this.onHold.pop(); - this.scene.shiftPhase(); + this.scene.phaseManager.shiftPhase(); } /** @@ -316,7 +316,7 @@ export default class PhaseInterceptor { shift(shouldRun = false): void { this.onHold.shift(); if (shouldRun) { - this.scene.shiftPhase(); + this.scene.phaseManager.shiftPhase(); } } @@ -345,7 +345,7 @@ export default class PhaseInterceptor { */ startPhase(phase: PhaseClass) { this.log.push(phase.name); - const instance = this.scene.getCurrentPhase(); + const instance = this.scene.phaseManager.getCurrentPhase(); this.onHold.push({ name: phase.name, call: () => { @@ -364,7 +364,7 @@ export default class PhaseInterceptor { * @param phase - The phase to start. */ superEndPhase() { - const instance = this.scene.getCurrentPhase(); + const instance = this.scene.phaseManager.getCurrentPhase(); this.originalSuperEnd.apply(instance); this.inProgress?.callback(); this.inProgress = undefined; @@ -376,7 +376,7 @@ export default class PhaseInterceptor { * @param args - Additional arguments to pass to the original method. */ setMode(mode: UiMode, ...args: unknown[]): Promise { - const currentPhase = this.scene.getCurrentPhase(); + const currentPhase = this.scene.phaseManager.getCurrentPhase(); const instance = this.scene.ui; console.log("setMode", `${UiMode[mode]} (=${mode})`, args); const ret = this.originalSetMode.apply(instance, [mode, ...args]); @@ -413,7 +413,7 @@ export default class PhaseInterceptor { const actionForNextPrompt = this.prompts[0]; const expireFn = actionForNextPrompt.expireFn?.(); const currentMode = this.scene.ui.getMode(); - const currentPhase = this.scene.getCurrentPhase()?.constructor.name; + const currentPhase = this.scene.phaseManager.getCurrentPhase()?.constructor.name; const currentHandler = this.scene.ui.getHandler(); if (expireFn) { this.prompts.shift(); diff --git a/test/ui/starter-select.test.ts b/test/ui/starter-select.test.ts index 10a804b805d..2884323b4ea 100644 --- a/test/ui/starter-select.test.ts +++ b/test/ui/starter-select.test.ts @@ -45,7 +45,7 @@ describe("UI - Starter select", () => { expect(caughtCount).toBe(Object.keys(allSpecies).length); await game.runToTitle(); game.onNextPrompt("TitlePhase", UiMode.TITLE, () => { - const currentPhase = game.scene.getCurrentPhase() as TitlePhase; + const currentPhase = game.scene.phaseManager.getCurrentPhase() as TitlePhase; currentPhase.gameMode = GameModes.CLASSIC; currentPhase.end(); }); @@ -105,7 +105,7 @@ describe("UI - Starter select", () => { expect(caughtCount).toBe(Object.keys(allSpecies).length); await game.runToTitle(); game.onNextPrompt("TitlePhase", UiMode.TITLE, () => { - const currentPhase = game.scene.getCurrentPhase() as TitlePhase; + const currentPhase = game.scene.phaseManager.getCurrentPhase() as TitlePhase; currentPhase.gameMode = GameModes.CLASSIC; currentPhase.end(); }); @@ -167,7 +167,7 @@ describe("UI - Starter select", () => { expect(caughtCount).toBe(Object.keys(allSpecies).length); await game.runToTitle(); game.onNextPrompt("TitlePhase", UiMode.TITLE, () => { - const currentPhase = game.scene.getCurrentPhase() as TitlePhase; + const currentPhase = game.scene.phaseManager.getCurrentPhase() as TitlePhase; currentPhase.gameMode = GameModes.CLASSIC; currentPhase.end(); }); @@ -232,7 +232,7 @@ describe("UI - Starter select", () => { expect(caughtCount).toBe(Object.keys(allSpecies).length); await game.runToTitle(); game.onNextPrompt("TitlePhase", UiMode.TITLE, () => { - const currentPhase = game.scene.getCurrentPhase() as TitlePhase; + const currentPhase = game.scene.phaseManager.getCurrentPhase() as TitlePhase; currentPhase.gameMode = GameModes.CLASSIC; currentPhase.end(); }); @@ -293,7 +293,7 @@ describe("UI - Starter select", () => { expect(caughtCount).toBe(Object.keys(allSpecies).length); await game.runToTitle(); game.onNextPrompt("TitlePhase", UiMode.TITLE, () => { - const currentPhase = game.scene.getCurrentPhase() as TitlePhase; + const currentPhase = game.scene.phaseManager.getCurrentPhase() as TitlePhase; currentPhase.gameMode = GameModes.CLASSIC; currentPhase.end(); }); @@ -353,7 +353,7 @@ describe("UI - Starter select", () => { expect(caughtCount).toBe(Object.keys(allSpecies).length); await game.runToTitle(); game.onNextPrompt("TitlePhase", UiMode.TITLE, () => { - const currentPhase = game.scene.getCurrentPhase() as TitlePhase; + const currentPhase = game.scene.phaseManager.getCurrentPhase() as TitlePhase; currentPhase.gameMode = GameModes.CLASSIC; currentPhase.end(); }); @@ -415,7 +415,7 @@ describe("UI - Starter select", () => { expect(caughtCount).toBe(Object.keys(allSpecies).length); await game.runToTitle(); game.onNextPrompt("TitlePhase", UiMode.TITLE, () => { - const currentPhase = game.scene.getCurrentPhase() as TitlePhase; + const currentPhase = game.scene.phaseManager.getCurrentPhase() as TitlePhase; currentPhase.gameMode = GameModes.CLASSIC; currentPhase.end(); }); @@ -476,7 +476,7 @@ describe("UI - Starter select", () => { expect(caughtCount).toBe(Object.keys(allSpecies).length); await game.runToTitle(); game.onNextPrompt("TitlePhase", UiMode.TITLE, () => { - const currentPhase = game.scene.getCurrentPhase() as TitlePhase; + const currentPhase = game.scene.phaseManager.getCurrentPhase() as TitlePhase; currentPhase.gameMode = GameModes.CLASSIC; currentPhase.end(); }); @@ -540,7 +540,7 @@ describe("UI - Starter select", () => { expect(caughtCount).toBe(Object.keys(allSpecies).length); await game.runToTitle(); game.onNextPrompt("TitlePhase", UiMode.TITLE, () => { - const currentPhase = game.scene.getCurrentPhase() as TitlePhase; + const currentPhase = game.scene.phaseManager.getCurrentPhase() as TitlePhase; currentPhase.gameMode = GameModes.CLASSIC; currentPhase.end(); }); From 09e30070f904c7993fa621a924fb057546591233 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sat, 7 Jun 2025 21:02:25 -0400 Subject: [PATCH 19/51] [Docs] Fixed test helper functions to conform with TSDoc standard; deprecated `runToSummon`/`startBattle` without args (#5912) * Updated doc comments for test-related functions * Marked `classicMode.runToSummon` and `classicMode.startBattle` without species as deprecated Having the species being used depend on daily run RNG is both unintuitive, janky and prone to flaking out (as happened with the Gastro Acid tests) * Fixed the bug * Update field-helper.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- test/testUtils/helpers/classicModeHelper.ts | 25 ++++++++++++++--- test/testUtils/helpers/field-helper.ts | 30 +++++++++++---------- test/testUtils/helpers/moveHelper.ts | 26 +++++++++++------- 3 files changed, 55 insertions(+), 26 deletions(-) diff --git a/test/testUtils/helpers/classicModeHelper.ts b/test/testUtils/helpers/classicModeHelper.ts index 575000c2193..c4f086bd628 100644 --- a/test/testUtils/helpers/classicModeHelper.ts +++ b/test/testUtils/helpers/classicModeHelper.ts @@ -11,14 +11,24 @@ import { generateStarter } from "../gameManagerUtils"; import { GameManagerHelper } from "./gameManagerHelper"; /** - * Helper to handle classic mode specifics + * Helper to handle classic-mode specific operations. */ export class ClassicModeHelper extends GameManagerHelper { /** * Runs the classic game to the summon phase. - * @param species - Optional array of species to summon. + * @param species - An array of {@linkcode Species} to summon. * @returns A promise that resolves when the summon phase is reached. */ + async runToSummon(species: SpeciesId[]): Promise; + /** + * Runs the classic game to the summon phase. + * Selects 3 daily run starters with a fixed seed of "test" + * (see `DailyRunConfig.getDailyRunStarters` in `daily-run.ts` for more info). + * @returns A promise that resolves when the summon phase is reached. + * @deprecated - Specifying the starters helps prevent inconsistencies from internal RNG changes. + */ + async runToSummon(): Promise; + async runToSummon(species: SpeciesId[] | undefined): Promise; async runToSummon(species?: SpeciesId[]): Promise { await this.game.runToTitle(); @@ -42,9 +52,18 @@ export class ClassicModeHelper extends GameManagerHelper { /** * Transitions to the start of a battle. - * @param species - Optional array of species to start the battle with. + * @param species - An array of {@linkcode Species} to start the battle with. * @returns A promise that resolves when the battle is started. */ + async startBattle(species: SpeciesId[]): Promise; + /** + * Transitions to the start of a battle. + * Will select 3 daily run starters with a fixed seed of "test" + * (see `DailyRunConfig.getDailyRunStarters` in `daily-run.ts` for more info). + * @returns A promise that resolves when the battle is started. + * @deprecated - Specifying the starters helps prevent inconsistencies from internal RNG changes. + */ + async startBattle(): Promise; async startBattle(species?: SpeciesId[]): Promise { await this.runToSummon(species); diff --git a/test/testUtils/helpers/field-helper.ts b/test/testUtils/helpers/field-helper.ts index 08b7a210e68..aa01ef7497d 100644 --- a/test/testUtils/helpers/field-helper.ts +++ b/test/testUtils/helpers/field-helper.ts @@ -20,9 +20,9 @@ export class FieldHelper extends GameManagerHelper { * Passthrough for {@linkcode globalScene.getPlayerPokemon} that adds an `undefined` check for * the Pokemon so that the return type for the function doesn't have `undefined`. * This removes the need to add a `!` like when calling `game.scene.getPlayerPokemon()!`. - * @param includeSwitching Whether a pokemon that is currently switching out is valid, default `true` - * @returns The first {@linkcode PlayerPokemon} that is {@linkcode globalScene.getPlayerField on the field} - * and {@linkcode PlayerPokemon.isActive is active} + * @param includeSwitching - Whether a pokemon that is currently switching out is valid, default `true` + * @returns The first {@linkcode PlayerPokemon} that is {@linkcode globalScene.getPlayerField | on the field} + * and {@linkcode PlayerPokemon.isActive | is active} * (aka {@linkcode PlayerPokemon.isAllowedInBattle is allowed in battle}). */ public getPlayerPokemon(includeSwitching = true): PlayerPokemon { @@ -36,9 +36,9 @@ export class FieldHelper extends GameManagerHelper { * the Pokemon so that the return type for the function doesn't have `undefined`. * This removes the need to add a `!` like when calling `game.scene.getEnemyPokemon()!`. * @param includeSwitching Whether a pokemon that is currently switching out is valid, default `true` - * @returns The first {@linkcode EnemyPokemon} that is {@linkcode globalScene.getEnemyField on the field} - * and {@linkcode EnemyPokemon.isActive is active} - * (aka {@linkcode EnemyPokemon.isAllowedInBattle is allowed in battle}). + * @returns The first {@linkcode EnemyPokemon} that is {@linkcode globalScene.getEnemyField | on the field} + * and {@linkcode EnemyPokemon.isActive | is active} + * (aka {@linkcode EnemyPokemon.isAllowedInBattle | is allowed in battle}). */ public getEnemyPokemon(includeSwitching = true): EnemyPokemon { const pokemon = this.game.scene.getEnemyPokemon(includeSwitching); @@ -50,7 +50,9 @@ export class FieldHelper extends GameManagerHelper { * @returns The {@linkcode BattlerIndex | indexes} of Pokemon on the field in order of decreasing Speed. * Speed ties are returned in increasing order of index. * - * Note: Trick Room does not modify the speed of Pokemon on the field. + * @remarks + * This does not account for Trick Room as it does not modify the _speed_ of Pokemon on the field, + * only their turn order. */ public getSpeedOrder(): BattlerIndex[] { return this.game.scene @@ -60,7 +62,8 @@ export class FieldHelper extends GameManagerHelper { } /** - * Mocks a pokemon's ability, overriding its existing ability (takes precedence over global overrides) + * Mocks a pokemon's ability, overriding its existing ability (takes precedence over global overrides). + * Useful for giving exactly 1 Pokemon in a double battle a certain ability (rather than all pokemon). * @param pokemon - The pokemon to mock the ability of * @param ability - The ability to be mocked * @returns A {@linkcode MockInstance} object @@ -72,16 +75,15 @@ export class FieldHelper extends GameManagerHelper { } /** - * Forces a pokemon to be terastallized. Defaults to the pokemon's primary type if not specified. - * - * This function only mocks the Pokemon's tera-related variables; it does NOT activate any tera-related abilities. + * Force a given Pokemon to be terastallized to the given type. * * @param pokemon - The pokemon to terastallize. - * @param teraType - (optional) The {@linkcode PokemonType} to terastallize it as. + * @param teraType - The {@linkcode PokemonType} to terastallize into; defaults to the pokemon's primary type. + * @remarks + * This function only mocks the Pokemon's tera-related variables; it does NOT activate any tera-related abilities. */ - public forceTera(pokemon: Pokemon, teraType?: PokemonType): void { + public forceTera(pokemon: Pokemon, teraType: PokemonType = pokemon.getSpeciesForm(true).type1): void { vi.spyOn(pokemon, "isTerastallized", "get").mockReturnValue(true); - teraType ??= pokemon.getSpeciesForm(true).type1; vi.spyOn(pokemon, "teraType", "get").mockReturnValue(teraType); } } diff --git a/test/testUtils/helpers/moveHelper.ts b/test/testUtils/helpers/moveHelper.ts index 878a265ce84..c7dea05b095 100644 --- a/test/testUtils/helpers/moveHelper.ts +++ b/test/testUtils/helpers/moveHelper.ts @@ -154,7 +154,7 @@ export class MoveHelper extends GameManagerHelper { * Changes a pokemon's moveset to the given move(s). * Used when the normal moveset override can't be used (such as when it's necessary to check or update properties of the moveset). * @param pokemon - The {@linkcode Pokemon} being modified - * @param moveset - The {@linkcode MoveId} (single or array) to change the Pokemon's moveset to + * @param moveset - The {@linkcode MoveId} (single or array) to change the Pokemon's moveset to. */ public changeMoveset(pokemon: Pokemon, moveset: MoveId | MoveId[]): void { if (!Array.isArray(moveset)) { @@ -169,10 +169,14 @@ export class MoveHelper extends GameManagerHelper { } /** - * Forces the next enemy selecting a move to use the given move in its moveset + * Forces the next enemy selecting a move to use the given move _in its moveset_ * against the given target (if applicable). - * @param moveId The {@linkcode MoveId | move} the enemy will use - * @param target (Optional) the {@linkcode BattlerIndex | target} which the enemy will use the given move against + * @param moveId - The {@linkcode Move | move ID} the enemy will be forced to use. + * @param target - The {@linkcode BattlerIndex | target} against which the enemy will use the given move; + * defaults to normal target selection priorities if omitted or not single-target. + * @remarks + * If you do not need to check for changes in the enemy's moveset as part of the test, it may be + * best to use {@linkcode forceEnemyMove} instead. */ public async selectEnemyMove(moveId: MoveId, target?: BattlerIndex) { // Wait for the next EnemyCommandPhase to start @@ -200,14 +204,18 @@ export class MoveHelper extends GameManagerHelper { } /** - * Forces the next enemy selecting a move to use the given move against the given target (if applicable). + * Modify the moveset of the next enemy selecting a move to contain only the given move, and then + * selects it to be used during the next {@linkcode EnemyCommandPhase} against the given targets. * - * Warning: Overwrites the pokemon's moveset and disables the moveset override! + * Does not require the given move to be in the enemy's moveset beforehand, + * but **overwrites the pokemon's moveset** and **disables any prior moveset overrides**! * - * Note: If you need to check for changes in the enemy's moveset as part of the test, it may be + * @param moveId - The {@linkcode Move | move ID} the enemy will be forced to use. + * @param target - The {@linkcode BattlerIndex | target} against which the enemy will use the given move; + * defaults to normal target selection priorities if omitted or not single-target. + * @remarks + * If you need to check for changes in the enemy's moveset as part of the test, it may be * best to use {@linkcode changeMoveset} and {@linkcode selectEnemyMove} instead. - * @param moveId The {@linkcode MoveId | move} the enemy will use - * @param target (Optional) the {@linkcode BattlerIndex | target} which the enemy will use the given move against */ public async forceEnemyMove(moveId: MoveId, target?: BattlerIndex) { // Wait for the next EnemyCommandPhase to start From 35a09af4750229c7fb588affd0c59cf40c6db4f7 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sat, 7 Jun 2025 22:29:10 -0400 Subject: [PATCH 20/51] [Utils] Create utility function `randSeedFloat` (#5767) --- src/battle.ts | 3 ++- src/data/abilities/ability.ts | 6 +++--- src/data/moves/move.ts | 6 +++--- src/data/pokemon-species.ts | 16 ++++++++++++---- src/field/pokemon.ts | 3 ++- src/modifier/modifier.ts | 28 ++++++++++++++-------------- src/utils/common.ts | 31 +++++++++++++++++++++++-------- 7 files changed, 59 insertions(+), 34 deletions(-) diff --git a/src/battle.ts b/src/battle.ts index dbfed57ae41..2ebfb634751 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -8,6 +8,7 @@ import { shiftCharCodes, randSeedItem, randInt, + randSeedFloat, } from "#app/utils/common"; import Trainer, { TrainerVariant } from "./field/trainer"; import type { GameMode } from "./game-mode"; @@ -150,7 +151,7 @@ export default class Battle { randSeedGaussForLevel(value: number): number { let rand = 0; for (let i = value; i > 0; i--) { - rand += Phaser.Math.RND.realInRange(0, 1); + rand += randSeedFloat(); } return rand / value; } diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index e680a12f9fe..8c95364237b 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -1,5 +1,5 @@ import { HitResult, MoveResult, PlayerPokemon } from "#app/field/pokemon"; -import { BooleanHolder, NumberHolder, toDmgValue, isNullOrUndefined, randSeedItem, randSeedInt, type Constructor } from "#app/utils/common"; +import { BooleanHolder, NumberHolder, toDmgValue, isNullOrUndefined, randSeedItem, randSeedInt, type Constructor, randSeedFloat } from "#app/utils/common"; import { getPokemonNameWithAffix } from "#app/messages"; import { BattlerTagLapseType, GroundedTag } from "#app/data/battler-tags"; import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "#app/data/status-effect"; @@ -4092,8 +4092,8 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr { } // Clamp procChance to [0, 1]. Skip if didn't proc (less than pass) - const pass = Phaser.Math.RND.realInRange(0, 1); - return Phaser.Math.Clamp(this.procChance(pokemon), 0, 1) >= pass; + const pass = randSeedFloat(); + return this.procChance(pokemon) >= pass; } override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 94d0ae50523..d44ecbed4cf 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -29,7 +29,7 @@ import { } from "../status-effect"; import { getTypeDamageMultiplier } from "../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 type { ArenaTrapTag } from "../arena-tag"; import { ArenaTagSide, WeakenMoveTypeTag } from "../arena-tag"; @@ -2547,8 +2547,8 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const rand = Phaser.Math.RND.realInRange(0, 1); - if (rand >= this.chance) { + const rand = randSeedFloat(); + if (rand > this.chance) { return false; } diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index c5d798b5841..36a8bbb0520 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -8,7 +8,14 @@ import type { AnySound } from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; import type { GameMode } from "#app/game-mode"; 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 { speciesEggMoves } from "#app/data/balance/egg-moves"; import { GrowthRate } from "#app/data/exp"; @@ -750,7 +757,7 @@ export abstract class PokemonSpeciesForm { let paletteColors: Map = new Map(); const originalRandom = Math.random; - Math.random = Phaser.Math.RND.frac; + Math.random = randSeedFloat; globalScene.executeWithSeedOffset( () => { @@ -773,6 +780,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali readonly mythical: boolean; readonly species: string; readonly growthRate: GrowthRate; + /** The chance (as a decimal) for this Species to be male, or `null` for genderless species */ readonly malePercent: number | null; readonly genderDiffs: boolean; readonly canChangeForm: boolean; @@ -889,7 +897,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali return Gender.GENDERLESS; } - if (Phaser.Math.RND.realInRange(0, 1) <= this.malePercent) { + if (randSeedFloat() <= this.malePercent) { return Gender.MALE; } 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; } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index a6d41074700..5b38419e708 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -65,6 +65,7 @@ import { rgbToHsv, deltaRgb, isBetween, + randSeedFloat, type nil, type Constructor, randSeedIntRange, @@ -5225,7 +5226,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let fusionPaletteColors: Map; const originalRandom = Math.random; - Math.random = () => Phaser.Math.RND.realInRange(0, 1); + Math.random = () => randSeedFloat(); globalScene.executeWithSeedOffset( () => { diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index e1517b3bcde..81d9bf7189c 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -15,7 +15,7 @@ import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import type { VoucherType } from "#app/system/voucher"; import { Command } from "#app/ui/command-ui-handler"; 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 { BerryType } from "#enums/berry-type"; import type { MoveId } from "#enums/move-id"; @@ -3351,7 +3351,7 @@ export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModif } 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 { @@ -3629,7 +3629,7 @@ export class EnemyAttackStatusEffectChanceModifier extends EnemyPersistentModifi * @returns `true` if the {@linkcode Pokemon} was affected */ 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); } @@ -3664,21 +3664,21 @@ export class EnemyStatusEffectHealChanceModifier extends EnemyPersistentModifier } /** - * Applies {@linkcode EnemyStatusEffectHealChanceModifier} - * @param enemyPokemon The {@linkcode Pokemon} to heal + * Applies {@linkcode EnemyStatusEffectHealChanceModifier} to randomly heal status. + * @param enemyPokemon - The {@linkcode Pokemon} to heal * @returns `true` if the {@linkcode Pokemon} was healed */ override apply(enemyPokemon: Pokemon): boolean { - if (enemyPokemon.status && Phaser.Math.RND.realInRange(0, 1) < this.chance * this.getStackCount()) { - globalScene.phaseManager.queueMessage( - getStatusEffectHealText(enemyPokemon.status.effect, getPokemonNameWithAffix(enemyPokemon)), - ); - enemyPokemon.resetStatus(); - enemyPokemon.updateInfo(); - return true; + if (!enemyPokemon.status || randSeedFloat() > this.chance * this.getStackCount()) { + return false; } - return false; + globalScene.phaseManager.queueMessage( + getStatusEffectHealText(enemyPokemon.status.effect, getPokemonNameWithAffix(enemyPokemon)), + ); + enemyPokemon.resetStatus(); + enemyPokemon.updateInfo(); + return true; } getMaxStackCount(): number { @@ -3757,7 +3757,7 @@ export class EnemyFusionChanceModifier extends EnemyPersistentModifier { * @returns `true` if the {@linkcode EnemyPokemon} is a fusion */ override apply(isFusion: BooleanHolder): boolean { - if (Phaser.Math.RND.realInRange(0, 1) >= this.chance * this.getStackCount()) { + if (randSeedFloat() > this.chance * this.getStackCount()) { return false; } diff --git a/src/utils/common.ts b/src/utils/common.ts index 29923d7ddd4..56fa3b5c698 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -58,8 +58,8 @@ export function randSeedGauss(stdev: number, mean = 0): number { if (!stdev) { return 0; } - const u = 1 - Phaser.Math.RND.realInRange(0, 1); - const v = Phaser.Math.RND.realInRange(0, 1); + const u = 1 - randSeedFloat(); + const v = randSeedFloat(); const z = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v); return z * stdev + mean; } @@ -88,9 +88,9 @@ export function randInt(range: number, min = 0): number { } /** - * Generates a random number using the global seed, or the current battle's seed if called via `Battle.randSeedInt` - * @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min} - * @param min The minimum integer to pick, default `0` + * Generate a random integer using the global seed, or the current battle's seed if called via `Battle.randSeedInt` + * @param range - How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min} + * @param min - The minimum integer to pick, default `0` * @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1) */ export function randSeedInt(range: number, min = 0): number { @@ -119,6 +119,14 @@ export function randIntRange(min: number, max: number): number { return randInt(max - min, min); } +/** + * Generate and return a random real number between `0` and `1` using the global seed. + * @returns A random floating-point number between `0` and `1` + */ +export function randSeedFloat(): number { + return Phaser.Math.RND.frac(); +} + export function randItem(items: T[]): T { return items.length === 1 ? items[0] : items[randInt(items.length)]; } @@ -517,12 +525,19 @@ export function capitalizeString(str: string, sep: string, lowerFirstChar = true return null; } -export function isNullOrUndefined(object: any): object is null | undefined { - return object === null || object === undefined; +/** + * Report whether a given value is nullish (`null`/`undefined`). + * @param val - The value whose nullishness is being checked + * @returns `true` if `val` is either `null` or `undefined` + */ +export function isNullOrUndefined(val: any): val is null | undefined { + return val === null || val === undefined; } /** - * Capitalizes the first letter of a string + * Capitalize the first letter of a string. + * @param str - The string whose first letter is being capitalized + * @return The original string with its first letter capitalized */ export function capitalizeFirstLetter(str: string) { return str.charAt(0).toUpperCase() + str.slice(1); From 75beec12a892d28b51d8cbd859a28fc793617736 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sat, 7 Jun 2025 21:43:30 -0700 Subject: [PATCH 21/51] [Dev] Enable Biome checking of `ability.ts` (#5948) --- biome.jsonc | 1 - src/data/abilities/ability.ts | 4084 +++++++++++++++++++++++---------- 2 files changed, 2915 insertions(+), 1170 deletions(-) diff --git a/biome.jsonc b/biome.jsonc index 141c44dc87d..82ce7c308dc 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -31,7 +31,6 @@ "src/overrides.ts", // TODO: these files are too big and complex, ignore them until their respective refactors "src/data/moves/move.ts", - "src/data/abilities/ability.ts", // this file is just too big: "src/data/balance/tms.ts" diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 8c95364237b..9269f84d269 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -1,8 +1,21 @@ -import { HitResult, MoveResult, PlayerPokemon } from "#app/field/pokemon"; -import { BooleanHolder, NumberHolder, toDmgValue, isNullOrUndefined, randSeedItem, randSeedInt, type Constructor, randSeedFloat } from "#app/utils/common"; +import { HitResult, MoveResult } from "#app/field/pokemon"; +import { + BooleanHolder, + NumberHolder, + toDmgValue, + isNullOrUndefined, + randSeedItem, + randSeedInt, + type Constructor, + randSeedFloat, +} from "#app/utils/common"; import { getPokemonNameWithAffix } from "#app/messages"; import { BattlerTagLapseType, GroundedTag } from "#app/data/battler-tags"; -import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "#app/data/status-effect"; +import { + getNonVolatileStatusEffects, + getStatusEffectDescriptor, + getStatusEffectHealText, +} from "#app/data/status-effect"; import { Gender } from "#app/data/gender"; import { AttackMove, @@ -24,7 +37,11 @@ import { allMoves } from "../data-lists"; import { ArenaTagSide } from "#app/data/arena-tag"; import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { TerrainType } from "#app/data/terrain"; -import { SpeciesFormChangeAbilityTrigger, SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "#app/data/pokemon-forms"; +import { + SpeciesFormChangeAbilityTrigger, + SpeciesFormChangeRevertWeatherFormTrigger, + SpeciesFormChangeWeatherTrigger, +} from "#app/data/pokemon-forms"; import i18next from "i18next"; import { Command } from "#app/ui/command-ui-handler"; import { BerryModifierType } from "#app/modifier/modifier-type"; @@ -65,13 +82,19 @@ import { CommonAnim } from "../battle-anims"; import { getBerryEffectFunc } from "../berry"; import { BerryUsedEvent } from "#app/events/battle-scene"; - // Type imports import type { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import type { Weather } from "#app/data/weather"; import type { BattlerTag } from "#app/data/battler-tags"; -import type { AbAttrCondition, PokemonDefendCondition, PokemonStatStageChangeCondition, PokemonAttackCondition, AbAttrApplyFunc, AbAttrSuccessFunc } from "#app/@types/ability-types"; +import type { + AbAttrCondition, + PokemonDefendCondition, + PokemonStatStageChangeCondition, + PokemonAttackCondition, + AbAttrApplyFunc, + AbAttrSuccessFunc, +} from "#app/@types/ability-types"; import type { BattlerIndex } from "#app/battle"; import type Move from "#app/data/moves/move"; import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag"; @@ -83,12 +106,21 @@ export class BlockRecoilDamageAttr extends AbAttr { super(false); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + cancelled: BooleanHolder, + _args: any[], + ): void { cancelled.value = true; } getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]) { - return i18next.t("abilityTriggers:blockRecoilDamage", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName: abilityName }); + return i18next.t("abilityTriggers:blockRecoilDamage", { + pokemonName: getPokemonNameWithAffix(pokemon), + abilityName: abilityName, + }); } } @@ -106,7 +138,13 @@ export class DoubleBattleChanceAbAttr extends AbAttr { * Increases the chance of a double battle occurring * @param args [0] {@linkcode NumberHolder} for double battle chance */ - override apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: any[], + ): void { const doubleBattleChance = args[0] as NumberHolder; // This is divided because the chance is generated as a number from 0 to doubleBattleChance.value using Utils.randSeedInt // A double battle will initiate if the generated number is 0 @@ -115,28 +153,28 @@ export class DoubleBattleChanceAbAttr extends AbAttr { } export class PostBattleInitAbAttr extends AbAttr { - canApplyPostBattleInit(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + canApplyPostBattleInit(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return true; } - applyPostBattleInit(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {} + applyPostBattleInit(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {} } export class PostBattleInitFormChangeAbAttr extends PostBattleInitAbAttr { private formFunc: (p: Pokemon) => number; - constructor(formFunc: ((p: Pokemon) => number)) { + constructor(formFunc: (p: Pokemon) => number) { super(false); this.formFunc = formFunc; } - override canApplyPostBattleInit(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostBattleInit(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): boolean { const formIndex = this.formFunc(pokemon); return formIndex !== pokemon.formIndex && !simulated; } - override applyPostBattleInit(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostBattleInit(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); } } @@ -152,7 +190,13 @@ export class PostTeraFormChangeStatChangeAbAttr extends AbAttr { this.stages = stages; } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder | null, args: any[]): void { + override apply( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + _cancelled: BooleanHolder | null, + _args: any[], + ): void { const statStageChangePhases: StatStageChangePhase[] = []; if (!simulated) { @@ -180,11 +224,17 @@ export class ClearWeatherAbAttr extends AbAttr { this.weather = weather; } - public override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + public override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return globalScene.arena.canSetWeather(WeatherType.NONE); } - public override apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: BooleanHolder, args: any[]): void { + public override apply( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + _cancelled: BooleanHolder, + _args: any[], + ): void { if (!simulated) { globalScene.arena.trySetWeather(WeatherType.NONE, pokemon); } @@ -206,11 +256,17 @@ export class ClearTerrainAbAttr extends AbAttr { this.terrain = terrain; } - public override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + public override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return globalScene.arena.canSetTerrain(TerrainType.NONE); } - public override apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: BooleanHolder, args: any[]): void { + public override apply( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + _cancelled: BooleanHolder, + _args: any[], + ): void { if (!simulated) { globalScene.arena.trySetTerrain(TerrainType.NONE, true, pokemon); } @@ -221,35 +277,56 @@ type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: Move export class PreDefendAbAttr extends AbAttr { canApplyPreDefend( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move | null, - cancelled: BooleanHolder | null, - args: any[]): boolean { + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + _move: Move | null, + _cancelled: BooleanHolder | null, + _args: any[], + ): boolean { return true; } applyPreDefend( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move | null, - cancelled: BooleanHolder | null, - args: any[], + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + _move: Move | null, + _cancelled: BooleanHolder | null, + _args: any[], ): void {} } export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr { - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move | null, cancelled: BooleanHolder | null, args: any[]): boolean { - return pokemon.isFullHp() - && pokemon.getMaxHp() > 1 //Checks if pokemon has wonder_guard (which forces 1hp) - && (args[0] as NumberHolder).value >= pokemon.hp; //Damage >= hp + override canApplyPreDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + _move: Move | null, + _cancelled: BooleanHolder | null, + args: any[], + ): boolean { + return ( + pokemon.isFullHp() && + // Checks if pokemon has wonder_guard (which forces 1hp) + pokemon.getMaxHp() > 1 && + // Damage >= hp + (args[0] as NumberHolder).value >= pokemon.hp + ); } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { + override applyPreDefend( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + _attacker: Pokemon, + _move: Move, + _cancelled: BooleanHolder, + _args: any[], + ): void { if (!simulated) { pokemon.addTag(BattlerTagType.STURDY, 1); } @@ -257,14 +334,20 @@ export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr { } export class BlockItemTheftAbAttr extends AbAttr { - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + cancelled: BooleanHolder, + _args: any[], + ): void { cancelled.value = true; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]) { + getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]) { return i18next.t("abilityTriggers:blockItemTheft", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - abilityName + abilityName, }); } } @@ -274,11 +357,17 @@ export class StabBoostAbAttr extends AbAttr { super(false); } - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { return (args[0] as NumberHolder).value > 1; } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: any[], + ): void { (args[0] as NumberHolder).value += 0.5; } } @@ -287,18 +376,34 @@ export class ReceivedMoveDamageMultiplierAbAttr extends PreDefendAbAttr { protected condition: PokemonDefendCondition; private damageMultiplier: number; - constructor(condition: PokemonDefendCondition, damageMultiplier: number, showAbility: boolean = false) { + constructor(condition: PokemonDefendCondition, damageMultiplier: number, showAbility = false) { super(showAbility); this.condition = condition; this.damageMultiplier = damageMultiplier; } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + move: Move, + _cancelled: BooleanHolder | null, + _args: any[], + ): boolean { return this.condition(pokemon, attacker, move); } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { + override applyPreDefend( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + _move: Move, + _cancelled: BooleanHolder, + args: any[], + ): void { (args[0] as NumberHolder).value = toDmgValue((args[0] as NumberHolder).value * this.damageMultiplier); } } @@ -320,7 +425,15 @@ export class AlliedFieldDamageReductionAbAttr extends PreDefendAbAttr { * @param args * - `[0]` {@linkcode NumberHolder} - The damage being dealt */ - override applyPreDefend(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _attacker: Pokemon, _move: Move, _cancelled: BooleanHolder, args: any[]): void { + override applyPreDefend( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + _move: Move, + _cancelled: BooleanHolder, + args: any[], + ): void { const damage = args[0] as NumberHolder; damage.value = toDmgValue(damage.value * this.damageMultiplier); } @@ -328,7 +441,7 @@ export class AlliedFieldDamageReductionAbAttr extends PreDefendAbAttr { export class ReceivedTypeDamageMultiplierAbAttr extends ReceivedMoveDamageMultiplierAbAttr { constructor(moveType: PokemonType, damageMultiplier: number) { - super((target, user, move) => user.getMoveType(move) === moveType, damageMultiplier, false); + super((_target, user, move) => user.getMoveType(move) === moveType, damageMultiplier, false); } } @@ -342,6 +455,7 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr { private immuneType: PokemonType | null; private condition: AbAttrCondition | null; + // TODO: `immuneType` shouldn't be able to be `null` constructor(immuneType: PokemonType | null, condition?: AbAttrCondition) { super(true); @@ -349,21 +463,41 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr { this.condition = condition ?? null; } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { - return ![ MoveTarget.BOTH_SIDES, MoveTarget.ENEMY_SIDE, MoveTarget.USER_SIDE ].includes(move.moveTarget) && attacker !== pokemon && attacker.getMoveType(move) === this.immuneType; + override canApplyPreDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + move: Move, + _cancelled: BooleanHolder | null, + _args: any[], + ): boolean { + return ( + ![MoveTarget.BOTH_SIDES, MoveTarget.ENEMY_SIDE, MoveTarget.USER_SIDE].includes(move.moveTarget) && + attacker !== pokemon && + attacker.getMoveType(move) === this.immuneType + ); } /** * Applies immunity if this ability grants immunity to the type of the given move. - * @param pokemon {@linkcode Pokemon} The defending Pokemon. - * @param passive - Whether the ability is passive. - * @param attacker {@linkcode Pokemon} The attacking Pokemon. - * @param move {@linkcode Move} The attacking move. - * @param cancelled {@linkcode BooleanHolder} - A holder for a boolean value indicating if the move was cancelled. + * @param _pokemon {@linkcode Pokemon} The defending Pokemon. + * @param _passive - Whether the ability is passive. + * @param _attacker {@linkcode Pokemon} The attacking Pokemon. + * @param _move {@linkcode Move} The attacking move. + * @param _cancelled {@linkcode BooleanHolder} - A holder for a boolean value indicating if the move was cancelled. * @param args [0] {@linkcode NumberHolder} gets set to 0 if move is immuned by an ability. * @param args [1] - Whether the move is simulated. */ - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { + override applyPreDefend( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + _move: Move, + _cancelled: BooleanHolder, + args: any[], + ): void { (args[0] as NumberHolder).value = 0; } @@ -377,13 +511,25 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr { } export class AttackTypeImmunityAbAttr extends TypeImmunityAbAttr { + // biome-ignore lint/complexity/noUselessConstructor: Changes the type of `immuneType` constructor(immuneType: PokemonType, condition?: AbAttrCondition) { super(immuneType, condition); } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { - return move.category !== MoveCategory.STATUS && !move.hasAttr(NeutralDamageAgainstFlyingTypeMultiplierAttr) - && super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); + override canApplyPreDefend( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + cancelled: BooleanHolder | null, + args: any[], + ): boolean { + return ( + move.category !== MoveCategory.STATUS && + !move.hasAttr(NeutralDamageAgainstFlyingTypeMultiplierAttr) && + super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args) + ); } /** @@ -391,27 +537,61 @@ export class AttackTypeImmunityAbAttr extends TypeImmunityAbAttr { * Type immunity abilities that do not give additional benefits (HP recovery, stat boosts, etc) are not immune to status moves of the type * Example: Levitate */ - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { + override applyPreDefend( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + cancelled: BooleanHolder, + args: any[], + ): void { // this is a hacky way to fix the Levitate/Thousand Arrows interaction, but it works for now... super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); } } export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr { + // biome-ignore lint/complexity/noUselessConstructor: Changes the type of `immuneType` constructor(immuneType: PokemonType) { super(immuneType); } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + cancelled: BooleanHolder | null, + args: any[], + ): boolean { return super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { + override applyPreDefend( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + cancelled: BooleanHolder, + args: any[], + ): void { super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); if (!pokemon.isFullHp() && !simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - globalScene.phaseManager.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / 4), i18next.t("abilityTriggers:typeImmunityHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); + globalScene.phaseManager.unshiftPhase( + new PokemonHealPhase( + pokemon.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / 4), + i18next.t("abilityTriggers:typeImmunityHeal", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + abilityName, + }), + true, + ), + ); cancelled.value = true; // Suppresses "No Effect" message } } @@ -428,15 +608,33 @@ class TypeImmunityStatStageChangeAbAttr extends TypeImmunityAbAttr { this.stages = stages; } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + cancelled: BooleanHolder | null, + args: any[], + ): boolean { return super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { + override applyPreDefend( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + cancelled: BooleanHolder, + args: any[], + ): void { super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); cancelled.value = true; // Suppresses "No Effect" message if (!simulated) { - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase(pokemon.getBattlerIndex(), true, [this.stat], this.stages), + ); } } } @@ -452,11 +650,27 @@ class TypeImmunityAddBattlerTagAbAttr extends TypeImmunityAbAttr { this.turnCount = turnCount; } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + cancelled: BooleanHolder | null, + args: any[], + ): boolean { return super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { + override applyPreDefend( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + cancelled: BooleanHolder, + args: any[], + ): void { super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); cancelled.value = true; // Suppresses "No Effect" message if (!simulated) { @@ -470,22 +684,39 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr { super(null, condition); } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { - const modifierValue = args.length > 0 - ? (args[0] as NumberHolder).value - : pokemon.getAttackTypeEffectiveness(attacker.getMoveType(move), attacker, undefined, undefined, move); + override canApplyPreDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + move: Move, + _cancelled: BooleanHolder | null, + args: any[], + ): boolean { + const modifierValue = + args.length > 0 + ? (args[0] as NumberHolder).value + : pokemon.getAttackTypeEffectiveness(attacker.getMoveType(move), attacker, undefined, undefined, move); return move instanceof AttackMove && modifierValue < 2; } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { + override applyPreDefend( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + _move: Move, + cancelled: BooleanHolder, + args: any[], + ): void { cancelled.value = true; // Suppresses "No Effect" message (args[0] as NumberHolder).value = 0; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:nonSuperEffectiveImmunity", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - abilityName + abilityName, }); } } @@ -496,94 +727,128 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr { * @extends PreDefendAbAttr */ export class FullHpResistTypeAbAttr extends PreDefendAbAttr { - - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move | null, cancelled: BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + move: Move | null, + _cancelled: BooleanHolder | null, + args: any[], + ): boolean { const typeMultiplier = args[0]; - return (typeMultiplier && typeMultiplier instanceof NumberHolder) && !(move && move.hasAttr(FixedDamageAttr)) && pokemon.isFullHp() && typeMultiplier.value > 0.5; + return ( + typeMultiplier instanceof NumberHolder && + !move?.hasAttr(FixedDamageAttr) && + pokemon.isFullHp() && + typeMultiplier.value > 0.5 + ); } /** * Reduces a type multiplier to 0.5 if the source is at full HP. * @param pokemon {@linkcode Pokemon} the Pokemon with this ability - * @param passive n/a - * @param simulated n/a (this doesn't change game state) - * @param attacker n/a - * @param move {@linkcode Move} the move being used on the source - * @param cancelled n/a + * @param _passive n/a + * @param _simulated n/a (this doesn't change game state) + * @param _attacker n/a + * @param _move {@linkcode Move} the move being used on the source + * @param _cancelled n/a * @param args `[0]` a container for the move's current type effectiveness multiplier */ override applyPreDefend( pokemon: Pokemon, - passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move | null, - cancelled: BooleanHolder | null, - args: any[]): void { + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + _move: Move | null, + _cancelled: BooleanHolder | null, + args: any[], + ): void { const typeMultiplier = args[0]; typeMultiplier.value = 0.5; pokemon.turnData.moveEffectiveness = 0.5; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:fullHpResistType", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }); } } export class PostDefendAbAttr extends AbAttr { canApplyPostDefend( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - hitResult: HitResult | null, - args: any[]): boolean { + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + _move: Move, + _hitResult: HitResult | null, + _args: any[], + ): boolean { return true; } applyPostDefend( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - hitResult: HitResult | null, - args: any[], + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + _move: Move, + _hitResult: HitResult | null, + _args: any[], ): void {} } export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr { - - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { - return !(move.moveTarget === MoveTarget.USER || move.moveTarget === MoveTarget.NEAR_ALLY) && move.getPriority(attacker) > 0 && !move.isMultiTarget(); + override canApplyPreDefend( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + move: Move, + _cancelled: BooleanHolder | null, + _args: any[], + ): boolean { + return ( + !(move.moveTarget === MoveTarget.USER || move.moveTarget === MoveTarget.NEAR_ALLY) && + move.getPriority(attacker) > 0 && + !move.isMultiTarget() + ); } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { + override applyPreDefend( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + _move: Move, + cancelled: BooleanHolder, + _args: any[], + ): void { cancelled.value = true; } } export class PostStatStageChangeAbAttr extends AbAttr { canApplyPostStatStageChange( - pokemon: Pokemon, - simulated: boolean, - statsChanged: BattleStat[], - stagesChanged: number, - selfTarget: boolean, - args: any[]): boolean { + _pokemon: Pokemon, + _simulated: boolean, + _statsChanged: BattleStat[], + _stagesChanged: number, + _selfTarget: boolean, + _args: any[], + ): boolean { return true; } applyPostStatStageChange( - pokemon: Pokemon, - simulated: boolean, - statsChanged: BattleStat[], - stagesChanged: number, - selfTarget: boolean, - args: any[], + _pokemon: Pokemon, + _simulated: boolean, + _statsChanged: BattleStat[], + _stagesChanged: number, + _selfTarget: boolean, + _args: any[], ): void {} } @@ -596,15 +861,31 @@ export class MoveImmunityAbAttr extends PreDefendAbAttr { this.immuneCondition = immuneCondition; } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + move: Move, + _cancelled: BooleanHolder | null, + _args: any[], + ): boolean { return this.immuneCondition(pokemon, attacker, move); } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { + override applyPreDefend( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + _move: Move, + cancelled: BooleanHolder, + _args: any[], + ): void { cancelled.value = true; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:moveImmunity", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); } } @@ -616,17 +897,32 @@ export class MoveImmunityAbAttr extends PreDefendAbAttr { * @extends PreDefendAbAttr */ export class WonderSkinAbAttr extends PreDefendAbAttr { - constructor() { super(false); } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + move: Move, + _cancelled: BooleanHolder | null, + args: any[], + ): boolean { const moveAccuracy = args[0] as NumberHolder; return move.category === MoveCategory.STATUS && moveAccuracy.value >= 50; } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { + override applyPreDefend( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + _move: Move, + _cancelled: BooleanHolder, + args: any[], + ): void { const moveAccuracy = args[0] as NumberHolder; moveAccuracy.value = 50; } @@ -642,13 +938,31 @@ export class MoveImmunityStatStageChangeAbAttr extends MoveImmunityAbAttr { this.stages = stages; } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + cancelled: BooleanHolder | null, + args: any[], + ): boolean { return !simulated && super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { + override applyPreDefend( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + cancelled: BooleanHolder, + args: any[], + ): void { super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase(pokemon.getBattlerIndex(), true, [this.stat], this.stages), + ); } } /** @@ -657,8 +971,15 @@ export class MoveImmunityStatStageChangeAbAttr extends MoveImmunityAbAttr { * @see {@linkcode applyPostDefend} */ export class ReverseDrainAbAttr extends PostDefendAbAttr { - - override canApplyPostDefend(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _attacker: Pokemon, move: Move, _hitResult: HitResult | null, args: any[]): boolean { + override canApplyPostDefend( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + move: Move, + _hitResult: HitResult | null, + _args: any[], + ): boolean { return move.hasAttr(HitHealAttr); } @@ -666,16 +987,26 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr { * Determines if a damage and draining move was used to check if this ability should stop the healing. * Examples include: Absorb, Draining Kiss, Bitter Blade, etc. * Also displays a message to show this ability was activated. - * @param pokemon {@linkcode Pokemon} with this ability + * @param _pokemon {@linkcode Pokemon} with this ability * @param _passive N/A * @param attacker {@linkcode Pokemon} that is attacking this Pokemon - * @param move {@linkcode PokemonMove} that is being used + * @param _move {@linkcode PokemonMove} that is being used * @param _hitResult N/A * @param _args N/A */ - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { + override applyPostDefend( + _pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + attacker: Pokemon, + _move: Move, + _hitResult: HitResult, + _args: any[], + ): void { if (!simulated) { - globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) })); + globalScene.phaseManager.queueMessage( + i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) }), + ); } } } @@ -687,7 +1018,13 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr { private selfTarget: boolean; private allOthers: boolean; - constructor(condition: PokemonDefendCondition, stat: BattleStat, stages: number, selfTarget = true, allOthers = false) { + constructor( + condition: PokemonDefendCondition, + stat: BattleStat, + stages: number, + selfTarget = true, + allOthers = false, + ) { super(true); this.condition = condition; @@ -697,23 +1034,48 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr { this.allOthers = allOthers; } - override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { + override canApplyPostDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + move: Move, + _hitResult: HitResult | null, + _args: any[], + ): boolean { return this.condition(pokemon, attacker, move); } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { + override applyPostDefend( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + attacker: Pokemon, + _move: Move, + _hitResult: HitResult, + _args: any[], + ): void { if (simulated) { return; } if (this.allOthers) { const ally = pokemon.getAlly(); - const otherPokemon = !isNullOrUndefined(ally) ? pokemon.getOpponents().concat([ ally ]) : pokemon.getOpponents(); + const otherPokemon = !isNullOrUndefined(ally) ? pokemon.getOpponents().concat([ally]) : pokemon.getOpponents(); for (const other of otherPokemon) { - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase((other).getBattlerIndex(), false, [ this.stat ], this.stages)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase(other.getBattlerIndex(), false, [this.stat], this.stages), + ); } } else { - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase((this.selfTarget ? pokemon : attacker).getBattlerIndex(), this.selfTarget, [ this.stat ], this.stages)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase( + (this.selfTarget ? pokemon : attacker).getBattlerIndex(), + this.selfTarget, + [this.stat], + this.stages, + ), + ); } } } @@ -725,7 +1087,13 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr { private stages: number; private selfTarget: boolean; - constructor(condition: PokemonDefendCondition, hpGate: number, stats: BattleStat[], stages: number, selfTarget = true) { + constructor( + condition: PokemonDefendCondition, + hpGate: number, + stats: BattleStat[], + stages: number, + selfTarget = true, + ) { super(true); this.condition = condition; @@ -735,16 +1103,41 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr { this.selfTarget = selfTarget; } - override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { + override canApplyPostDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + move: Move, + _hitResult: HitResult | null, + _args: any[], + ): boolean { const hpGateFlat: number = Math.ceil(pokemon.getMaxHp() * this.hpGate); const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1]; const damageReceived = lastAttackReceived?.damage || 0; - return this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat); + return ( + this.condition(pokemon, attacker, move) && pokemon.hp <= hpGateFlat && pokemon.hp + damageReceived > hpGateFlat + ); } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { + override applyPostDefend( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + attacker: Pokemon, + _move: Move, + _hitResult: HitResult, + _args: any[], + ): void { if (!simulated) { - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase((this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.stages)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase( + (this.selfTarget ? pokemon : attacker).getBattlerIndex(), + true, + this.stats, + this.stages, + ), + ); } } } @@ -760,15 +1153,38 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr { this.tagType = tagType; } - override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { + override canApplyPostDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + move: Move, + _hitResult: HitResult | null, + _args: any[], + ): boolean { const tag = globalScene.arena.getTag(this.tagType) as ArenaTrapTag; - return (this.condition(pokemon, attacker, move)) - && (!globalScene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers); + return ( + this.condition(pokemon, attacker, move) && (!globalScene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers) + ); } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { + override applyPostDefend( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + _attacker: Pokemon, + _move: Move, + _hitResult: HitResult, + _args: any[], + ): void { if (!simulated) { - globalScene.arena.addTag(this.tagType, 0, undefined, pokemon.id, pokemon.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER); + globalScene.arena.addTag( + this.tagType, + 0, + undefined, + pokemon.id, + pokemon.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER, + ); } } } @@ -783,14 +1199,35 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr { this.tagType = tagType; } - override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { + override canApplyPostDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + move: Move, + _hitResult: HitResult | null, + _args: any[], + ): boolean { return this.condition(pokemon, attacker, move); } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { + override applyPostDefend( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + _attacker: Pokemon, + move: Move, + _hitResult: HitResult, + _args: any[], + ): void { if (!pokemon.getTag(this.tagType) && !simulated) { pokemon.addTag(this.tagType, undefined, undefined, pokemon.id); - globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name })); + globalScene.phaseManager.queueMessage( + i18next.t("abilityTriggers:windPowerCharged", { + pokemonName: getPokemonNameWithAffix(pokemon), + moveName: move.name, + }), + ); } } } @@ -798,22 +1235,38 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr { export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr { private type: PokemonType; - override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + override canApplyPostDefend( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + hitResult: HitResult, + _args: any[], + ): boolean { this.type = attacker.getMoveType(move); const pokemonTypes = pokemon.getTypes(true); return hitResult < HitResult.NO_EFFECT && (simulated || pokemonTypes.length !== 1 || pokemonTypes[0] !== this.type); } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): void { + override applyPostDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + move: Move, + _hitResult: HitResult, + _args: any[], + ): void { const type = attacker.getMoveType(move); - pokemon.summonData.types = [ type ]; + pokemon.summonData.types = [type]; } override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:postDefendTypeChange", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, - typeName: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`) + typeName: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`), }); } } @@ -827,11 +1280,27 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr { this.terrainType = terrainType; } - override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + override canApplyPostDefend( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + _move: Move, + hitResult: HitResult, + _args: any[], + ): boolean { return hitResult < HitResult.NO_EFFECT && globalScene.arena.canSetTerrain(this.terrainType); } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): void { + override applyPostDefend( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + _attacker: Pokemon, + _move: Move, + _hitResult: HitResult, + _args: any[], + ): void { if (!simulated) { globalScene.arena.trySetTerrain(this.terrainType, false, pokemon); } @@ -849,15 +1318,36 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr { this.effects = effects; } - override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { - const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)]; - return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && !attacker.status - && (this.chance === -1 || pokemon.randBattleSeedInt(100) < this.chance) - && attacker.canSetStatus(effect, true, false, pokemon); + override canApplyPostDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + move: Move, + _hitResult: HitResult | null, + _args: any[], + ): boolean { + const effect = + this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)]; + return ( + move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) && + !attacker.status && + (this.chance === -1 || pokemon.randBattleSeedInt(100) < this.chance) && + attacker.canSetStatus(effect, true, false, pokemon) + ); } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { - const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)]; + override applyPostDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + _move: Move, + _hitResult: HitResult, + _args: any[], + ): void { + const effect = + this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)]; attacker.trySetStatus(effect, true, pokemon); } } @@ -867,12 +1357,30 @@ export class EffectSporeAbAttr extends PostDefendContactApplyStatusEffectAbAttr super(10, StatusEffect.POISON, StatusEffect.PARALYSIS, StatusEffect.SLEEP); } - override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { - return !(attacker.hasAbility(AbilityId.OVERCOAT) || attacker.isOfType(PokemonType.GRASS)) - && super.canApplyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args); + override canApplyPostDefend( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + hitResult: HitResult | null, + args: any[], + ): boolean { + return ( + !(attacker.hasAbility(AbilityId.OVERCOAT) || attacker.isOfType(PokemonType.GRASS)) && + super.canApplyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args) + ); } - override applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): void { + override applyPostDefend( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + hitResult: HitResult, + args: any[], + ): void { super.applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args); } } @@ -890,12 +1398,31 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr { this.turnCount = turnCount; } - override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { - return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && pokemon.randBattleSeedInt(100) < this.chance - && attacker.canAddTag(this.tagType); + override canApplyPostDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + move: Move, + _hitResult: HitResult | null, + _args: any[], + ): boolean { + return ( + move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) && + pokemon.randBattleSeedInt(100) < this.chance && + attacker.canAddTag(this.tagType) + ); } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { + override applyPostDefend( + _pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + _hitResult: HitResult, + _args: any[], + ): void { if (!simulated) { attacker.addTag(this.tagType, this.turnCount, move.id, attacker.id); } @@ -913,14 +1440,26 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr { this.stages = stages; } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { + override applyPostDefend( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + _attacker: Pokemon, + _move: Move, + _hitResult: HitResult, + _args: any[], + ): void { if (!simulated) { - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase(pokemon.getBattlerIndex(), true, [this.stat], this.stages), + ); } } override getCondition(): AbAttrCondition { - return (pokemon: Pokemon) => pokemon.turnData.attacksReceived.length !== 0 && pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1].critical; + return (pokemon: Pokemon) => + pokemon.turnData.attacksReceived.length !== 0 && + pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1].critical; } } @@ -933,12 +1472,31 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr { this.damageRatio = damageRatio; } - override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { - return !simulated && move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) - && !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr); + override canApplyPostDefend( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + _hitResult: HitResult | null, + _args: any[], + ): boolean { + return ( + !simulated && + move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) && + !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) + ); } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { + override applyPostDefend( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + _move: Move, + _hitResult: HitResult, + _args: any[], + ): void { attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), { result: HitResult.INDIRECT }); attacker.turnData.damageTaken += toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)); } @@ -946,7 +1504,7 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr { override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:postDefendContactDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - abilityName + abilityName, }); } } @@ -966,11 +1524,30 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr { this.turns = turns; } - override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { - return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && !attacker.getTag(BattlerTagType.PERISH_SONG); + override canApplyPostDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + move: Move, + _hitResult: HitResult | null, + _args: any[], + ): boolean { + return ( + move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) && + !attacker.getTag(BattlerTagType.PERISH_SONG) + ); } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { + override applyPostDefend( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + attacker: Pokemon, + _move: Move, + _hitResult: HitResult, + _args: any[], + ): void { if (!simulated) { attacker.addTag(BattlerTagType.PERISH_SONG, this.turns); pokemon.addTag(BattlerTagType.PERISH_SONG, this.turns); @@ -978,7 +1555,10 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr { } override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { - return i18next.t("abilityTriggers:perishBody", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName: abilityName }); + return i18next.t("abilityTriggers:perishBody", { + pokemonName: getPokemonNameWithAffix(pokemon), + abilityName: abilityName, + }); } } @@ -993,12 +1573,31 @@ export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr { this.condition = condition; } - override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { - return (!(this.condition && !this.condition(pokemon, attacker, move)) - && !globalScene.arena.weather?.isImmutable() && globalScene.arena.canSetWeather(this.weatherType)); + override canApplyPostDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + move: Move, + _hitResult: HitResult | null, + _args: any[], + ): boolean { + return ( + !(this.condition && !this.condition(pokemon, attacker, move)) && + !globalScene.arena.weather?.isImmutable() && + globalScene.arena.canSetWeather(this.weatherType) + ); } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { + override applyPostDefend( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + _attacker: Pokemon, + _move: Move, + _hitResult: HitResult, + _args: any[], + ): void { if (!simulated) { globalScene.arena.trySetWeather(this.weatherType, pokemon); } @@ -1006,16 +1605,30 @@ export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr { } export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr { - constructor() { - super(); + override canApplyPostDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + move: Move, + _hitResult: HitResult | null, + _args: any[], + ): boolean { + return ( + move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) && + attacker.getAbility().isSwappable + ); } - override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { - return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) - && attacker.getAbility().isSwappable; - } - - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, args: any[]): void { + override applyPostDefend( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + attacker: Pokemon, + _move: Move, + _hitResult: HitResult, + _args: any[], + ): void { if (!simulated) { const tempAbility = attacker.getAbility(); attacker.setTempAbility(pokemon.getAbility()); @@ -1024,7 +1637,9 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr { } override getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string { - return i18next.t("abilityTriggers:postDefendAbilitySwap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); + return i18next.t("abilityTriggers:postDefendAbilitySwap", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + }); } } @@ -1036,12 +1651,31 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr { this.ability = ability; } - override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { - return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && attacker.getAbility().isSuppressable - && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr); + override canApplyPostDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + move: Move, + _hitResult: HitResult | null, + _args: any[], + ): boolean { + return ( + move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) && + attacker.getAbility().isSuppressable && + !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr) + ); } - override applyPostDefend(_pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { + override applyPostDefend( + _pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + attacker: Pokemon, + _move: Move, + _hitResult: HitResult, + _args: any[], + ): void { if (!simulated) { attacker.setTempAbility(allAbilities[this.ability]); } @@ -1050,7 +1684,7 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr { override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:postDefendAbilityGive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - abilityName + abilityName, }); } } @@ -1066,12 +1700,31 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { this.chance = chance; } - override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { - return attacker.getTag(BattlerTagType.DISABLED) === null - && move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && (this.chance === -1 || pokemon.randBattleSeedInt(100) < this.chance); + override canApplyPostDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + move: Move, + _hitResult: HitResult | null, + _args: any[], + ): boolean { + return ( + attacker.getTag(BattlerTagType.DISABLED) === null && + move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) && + (this.chance === -1 || pokemon.randBattleSeedInt(100) < this.chance) + ); } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { + override applyPostDefend( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + _hitResult: HitResult, + _args: any[], + ): void { if (!simulated) { this.attacker = attacker; this.move = move; @@ -1093,35 +1746,52 @@ export class PostStatStageChangeStatStageChangeAbAttr extends PostStatStageChang this.stages = stages; } - override canApplyPostStatStageChange(pokemon: Pokemon, simulated: boolean, statStagesChanged: BattleStat[], stagesChanged: integer, selfTarget: boolean, args: any[]): boolean { + override canApplyPostStatStageChange( + pokemon: Pokemon, + _simulated: boolean, + statStagesChanged: BattleStat[], + stagesChanged: number, + selfTarget: boolean, + _args: any[], + ): boolean { return this.condition(pokemon, statStagesChanged, stagesChanged) && !selfTarget; } - override applyPostStatStageChange(pokemon: Pokemon, simulated: boolean, statStagesChanged: BattleStat[], stagesChanged: number, selfTarget: boolean, args: any[]): void { + override applyPostStatStageChange( + pokemon: Pokemon, + simulated: boolean, + _statStagesChanged: BattleStat[], + _stagesChanged: number, + _selfTarget: boolean, + _args: any[], + ): void { if (!simulated) { - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase((pokemon).getBattlerIndex(), true, this.statsToChange, this.stages)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.statsToChange, this.stages), + ); } } } export class PreAttackAbAttr extends AbAttr { canApplyPreAttack( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - defender: Pokemon | null, - move: Move, - args: any[]): boolean { + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _defender: Pokemon | null, + _move: Move, + _args: any[], + ): boolean { return true; } applyPreAttack( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - defender: Pokemon | null, - move: Move, - args: any[], + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _defender: Pokemon | null, + _move: Move, + _args: any[], ): void {} } @@ -1138,8 +1808,8 @@ export class MoveEffectChanceMultiplierAbAttr extends AbAttr { this.chanceMultiplier = chanceMultiplier; } - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const exceptMoves = [ MoveId.ORDER_UP, MoveId.ELECTRO_SHOT ]; + override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { + const exceptMoves = [MoveId.ORDER_UP, MoveId.ELECTRO_SHOT]; return !((args[0] as NumberHolder).value <= 0 || exceptMoves.includes((args[1] as Move).id)); } @@ -1147,7 +1817,13 @@ export class MoveEffectChanceMultiplierAbAttr extends AbAttr { * @param args [0]: {@linkcode NumberHolder} Move additional effect chance. Has to be higher than or equal to 0. * [1]: {@linkcode MoveId } Move used by the ability user. */ - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: any[], + ): void { (args[0] as NumberHolder).value *= this.chanceMultiplier; (args[0] as NumberHolder).value = Math.min((args[0] as NumberHolder).value, 100); } @@ -1159,35 +1835,58 @@ export class MoveEffectChanceMultiplierAbAttr extends AbAttr { * @see {@linkcode applyPreDefend} */ export class IgnoreMoveEffectsAbAttr extends PreDefendAbAttr { - constructor(showAbility: boolean = false) { + constructor(showAbility = false) { super(showAbility); } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move | null, cancelled: BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + _move: Move | null, + _cancelled: BooleanHolder | null, + args: any[], + ): boolean { return (args[0] as NumberHolder).value > 0; } /** * @param args [0]: {@linkcode NumberHolder} Move additional effect chance. */ - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { + override applyPreDefend( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + _move: Move, + _cancelled: BooleanHolder, + args: any[], + ): void { (args[0] as NumberHolder).value = 0; } } export class VariableMovePowerAbAttr extends PreAttackAbAttr { - override canApplyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean { + override canApplyPreAttack( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _defender: Pokemon, + _move: Move, + _args: any[], + ): boolean { return true; } } export class FieldPreventExplosiveMovesAbAttr extends AbAttr { override apply( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, cancelled: BooleanHolder, - args: any[], + _args: any[], ): void { cancelled.value = true; } @@ -1213,33 +1912,56 @@ export class FieldMultiplyStatAbAttr extends AbAttr { this.canStack = canStack; } - canApplyFieldStat(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: Stat, statValue: NumberHolder, checkedPokemon: Pokemon, hasApplied: BooleanHolder, args: any[]): boolean { - return this.canStack || !hasApplied.value - && this.stat === stat && checkedPokemon.getAbilityAttrs(FieldMultiplyStatAbAttr).every(attr => (attr as FieldMultiplyStatAbAttr).stat !== stat); + canApplyFieldStat( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + stat: Stat, + _statValue: NumberHolder, + checkedPokemon: Pokemon, + hasApplied: BooleanHolder, + _args: any[], + ): boolean { + return ( + this.canStack || + (!hasApplied.value && + this.stat === stat && + checkedPokemon + .getAbilityAttrs(FieldMultiplyStatAbAttr) + .every(attr => (attr as FieldMultiplyStatAbAttr).stat !== stat)) + ); } /** * applyFieldStat: Tries to multiply a Pokemon's Stat - * @param pokemon {@linkcode Pokemon} the Pokemon using this ability - * @param passive {@linkcode boolean} unused - * @param stat {@linkcode Stat} the type of the checked stat + * @param _pokemon {@linkcode Pokemon} the Pokemon using this ability + * @param _passive {@linkcode boolean} unused + * @param _stat {@linkcode Stat} the type of the checked stat * @param statValue {@linkcode NumberHolder} the value of the checked stat - * @param checkedPokemon {@linkcode Pokemon} the Pokemon this ability is targeting + * @param _checkedPokemon {@linkcode Pokemon} the Pokemon this ability is targeting * @param hasApplied {@linkcode BooleanHolder} whether or not another multiplier has been applied to this stat - * @param args {any[]} unused + * @param _args {any[]} unused */ - applyFieldStat(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: Stat, statValue: NumberHolder, checkedPokemon: Pokemon, hasApplied: BooleanHolder, args: any[]): void { + applyFieldStat( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _stat: Stat, + statValue: NumberHolder, + _checkedPokemon: Pokemon, + hasApplied: BooleanHolder, + _args: any[], + ): void { statValue.value *= this.multiplier; hasApplied.value = true; } - } export class MoveTypeChangeAbAttr extends PreAttackAbAttr { constructor( private newType: PokemonType, private powerMultiplier: number, - private condition?: PokemonAttackCondition + private condition?: PokemonAttackCondition, ) { super(false); } @@ -1260,23 +1982,41 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr { * @param _args - args[0] holds the type that the move is changed to, args[1] holds the multiplier * @returns whether the move type change attribute can be applied */ - override canApplyPreAttack(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _defender: Pokemon | null, move: Move, _args: [NumberHolder?, NumberHolder?, ...any]): boolean { - return (!this.condition || this.condition(pokemon, _defender, move)) && - !noAbilityTypeOverrideMoves.has(move.id) && - (!pokemon.isTerastallized || - (move.id !== MoveId.TERA_BLAST && - (move.id !== MoveId.TERA_STARSTORM || pokemon.getTeraType() !== PokemonType.STELLAR || !pokemon.hasSpecies(SpeciesId.TERAPAGOS)))); + override canApplyPreAttack( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _defender: Pokemon | null, + move: Move, + _args: [NumberHolder?, NumberHolder?, ...any], + ): boolean { + return ( + (!this.condition || this.condition(pokemon, _defender, move)) && + !noAbilityTypeOverrideMoves.has(move.id) && + (!pokemon.isTerastallized || + (move.id !== MoveId.TERA_BLAST && + (move.id !== MoveId.TERA_STARSTORM || + pokemon.getTeraType() !== PokemonType.STELLAR || + !pokemon.hasSpecies(SpeciesId.TERAPAGOS)))) + ); } /** - * @param pokemon - The pokemon that has the move type changing ability and is using the attacking move - * @param passive - Unused - * @param simulated - Unused - * @param defender - The pokemon being attacked (unused) - * @param move - The move being used + * @param _pokemon - The pokemon that has the move type changing ability and is using the attacking move + * @param _passive - Unused + * @param _simulated - Unused + * @param _defender - The pokemon being attacked (unused) + * @param _move - The move being used * @param args - args[0] holds the type that the move is changed to, args[1] holds the multiplier */ - override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: [NumberHolder?, NumberHolder?, ...any]): void { + override applyPreAttack( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _defender: Pokemon, + _move: Move, + args: [NumberHolder?, NumberHolder?, ...any], + ): void { if (args[0] && args[0] instanceof NumberHolder) { args[0].value = this.newType; } @@ -1294,20 +2034,31 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr { super(true); } - override canApplyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon | null, move: Move, args: any[]): boolean { - if (!pokemon.isTerastallized && - move.id !== MoveId.STRUGGLE && - /** - * Skip moves that call other moves because these moves generate a following move that will trigger this ability attribute - * @see {@link https://bulbapedia.bulbagarden.net/wiki/Category:Moves_that_call_other_moves} - */ - !move.findAttr((attr) => - attr instanceof RandomMovesetMoveAttr || - attr instanceof RandomMoveAttr || - attr instanceof NaturePowerAttr || - attr instanceof CopyMoveAttr)) { + override canApplyPreAttack( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _defender: Pokemon | null, + move: Move, + _args: any[], + ): boolean { + if ( + !pokemon.isTerastallized && + move.id !== MoveId.STRUGGLE && + /** + * Skip moves that call other moves because these moves generate a following move that will trigger this ability attribute + * @see {@link https://bulbapedia.bulbagarden.net/wiki/Category:Moves_that_call_other_moves} + */ + !move.findAttr( + attr => + attr instanceof RandomMovesetMoveAttr || + attr instanceof RandomMoveAttr || + attr instanceof NaturePowerAttr || + attr instanceof CopyMoveAttr, + ) + ) { const moveType = pokemon.getMoveType(move); - if (pokemon.getTypes().some((t) => t !== moveType)) { + if (pokemon.getTypes().some(t => t !== moveType)) { this.moveType = moveType; return true; } @@ -1315,17 +2066,24 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr { return false; } - override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void { + override applyPreAttack( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + _defender: Pokemon, + move: Move, + _args: any[], + ): void { const moveType = pokemon.getMoveType(move); if (!simulated) { this.moveType = moveType; - pokemon.summonData.types = [ moveType ]; + pokemon.summonData.types = [moveType]; pokemon.updateInfo(); } } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:pokemonTypeChange", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveType: i18next.t(`pokemonInfo:Type.${PokemonType[this.moveType]}`), @@ -1346,7 +2104,14 @@ export class AddSecondStrikeAbAttr extends PreAttackAbAttr { this.damageMultiplier = damageMultiplier; } - override canApplyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon | null, move: Move, args: any[]): boolean { + override canApplyPreAttack( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _defender: Pokemon | null, + move: Move, + _args: any[], + ): boolean { return move.canBeMultiStrikeEnhanced(pokemon, true); } @@ -1354,14 +2119,21 @@ export class AddSecondStrikeAbAttr extends PreAttackAbAttr { * If conditions are met, this doubles the move's hit count (via args[1]) * or multiplies the damage of secondary strikes (via args[2]) * @param pokemon the {@linkcode Pokemon} using the move - * @param passive n/a - * @param defender n/a - * @param move the {@linkcode Move} used by the ability source + * @param _passive n/a + * @param _defender n/a + * @param _move the {@linkcode Move} used by the ability source * @param args Additional arguments: * - `[0]` the number of strikes this move currently has ({@linkcode NumberHolder}) * - `[1]` the damage multiplier for the current strike ({@linkcode NumberHolder}) */ - override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void { + override applyPreAttack( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _defender: Pokemon, + _move: Move, + args: any[], + ): void { const hitCount = args[0] as NumberHolder; const multiplier = args[1] as NumberHolder; if (hitCount?.value) { @@ -1390,19 +2162,33 @@ export class DamageBoostAbAttr extends PreAttackAbAttr { this.condition = condition; } - override canApplyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon | null, move: Move, args: any[]): boolean { + override canApplyPreAttack( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + defender: Pokemon | null, + move: Move, + _args: any[], + ): boolean { return this.condition(pokemon, defender, move); } /** * - * @param pokemon the attacker pokemon - * @param passive N/A - * @param defender the target pokemon - * @param move the move used by the attacker pokemon + * @param _pokemon the attacker pokemon + * @param _passive N/A + * @param _defender the target pokemon + * @param _move the move used by the attacker pokemon * @param args Utils.NumberHolder as damage */ - override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void { + override applyPreAttack( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _defender: Pokemon, + _move: Move, + args: any[], + ): void { const power = args[0] as NumberHolder; power.value = toDmgValue(power.value * this.damageMultiplier); } @@ -1412,34 +2198,49 @@ export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr { private condition: PokemonAttackCondition; private powerMultiplier: number; - constructor(condition: PokemonAttackCondition, powerMultiplier: number, showAbility: boolean = false) { + constructor(condition: PokemonAttackCondition, powerMultiplier: number, showAbility = false) { super(showAbility); this.condition = condition; this.powerMultiplier = powerMultiplier; } - override canApplyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon | null, move: Move, args: any[]): boolean { + override canApplyPreAttack( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + defender: Pokemon | null, + move: Move, + _args: any[], + ): boolean { return this.condition(pokemon, defender, move); } - override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void { + override applyPreAttack( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _defender: Pokemon, + _move: Move, + args: any[], + ): void { (args[0] as NumberHolder).value *= this.powerMultiplier; } } export class MoveTypePowerBoostAbAttr extends MovePowerBoostAbAttr { constructor(boostedType: PokemonType, powerMultiplier?: number) { - super((pokemon, defender, move) => pokemon?.getMoveType(move) === boostedType, powerMultiplier || 1.5, false); + super((pokemon, _defender, move) => pokemon?.getMoveType(move) === boostedType, powerMultiplier || 1.5, false); } } export class LowHpMoveTypePowerBoostAbAttr extends MoveTypePowerBoostAbAttr { + // biome-ignore lint/complexity/noUselessConstructor: Changes the constructor params constructor(boostedType: PokemonType) { super(boostedType); } getCondition(): AbAttrCondition { - return (pokemon) => pokemon.getHpRatio() <= 0.33; + return pokemon => pokemon.getHpRatio() <= 0.33; } } @@ -1460,11 +2261,25 @@ export class VariableMovePowerBoostAbAttr extends VariableMovePowerAbAttr { this.mult = mult; } - override canApplyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean { + override canApplyPreAttack( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + defender: Pokemon, + move: Move, + _args: any[], + ): boolean { return this.mult(pokemon, defender, move) !== 1; } - override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void { + override applyPreAttack( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + defender: Pokemon, + move: Move, + args: any[], + ): void { const multiplier = this.mult(pokemon, defender, move); (args[0] as NumberHolder).value *= multiplier; } @@ -1489,11 +2304,25 @@ export class FieldMovePowerBoostAbAttr extends AbAttr { this.powerMultiplier = powerMultiplier; } - canApplyPreAttack(pokemon: Pokemon | null, passive: boolean | null, simulated: boolean, defender: Pokemon | null, move: Move, args: any[]): boolean { + canApplyPreAttack( + _pokemon: Pokemon | null, + _passive: boolean | null, + _simulated: boolean, + _defender: Pokemon | null, + _move: Move, + _args: any[], + ): boolean { return true; // logic for this attr is handled in move.ts instead of normally } - applyPreAttack(pokemon: Pokemon | null, passive: boolean | null, simulated: boolean, defender: Pokemon | null, move: Move, args: any[]): void { + applyPreAttack( + pokemon: Pokemon | null, + _passive: boolean | null, + _simulated: boolean, + defender: Pokemon | null, + move: Move, + args: any[], + ): void { if (this.condition(pokemon, defender, move)) { (args[0] as NumberHolder).value *= this.powerMultiplier; } @@ -1510,7 +2339,7 @@ export class PreAttackFieldMoveTypePowerBoostAbAttr extends FieldMovePowerBoostA * @param powerMultiplier - The multiplier to apply to the move's power, defaults to 1.5 if not provided. */ constructor(boostedType: PokemonType, powerMultiplier?: number) { - super((pokemon, defender, move) => pokemon?.getMoveType(move) === boostedType, powerMultiplier || 1.5); + super((pokemon, _defender, move) => pokemon?.getMoveType(move) === boostedType, powerMultiplier || 1.5); } } @@ -1518,13 +2347,13 @@ export class PreAttackFieldMoveTypePowerBoostAbAttr extends FieldMovePowerBoostA * Boosts the power of a specific type of move for all Pokemon in the field. * @extends PreAttackFieldMoveTypePowerBoostAbAttr */ -export class FieldMoveTypePowerBoostAbAttr extends PreAttackFieldMoveTypePowerBoostAbAttr { } +export class FieldMoveTypePowerBoostAbAttr extends PreAttackFieldMoveTypePowerBoostAbAttr {} /** * Boosts the power of a specific type of move for the user and its allies. * @extends PreAttackFieldMoveTypePowerBoostAbAttr */ -export class UserFieldMoveTypePowerBoostAbAttr extends PreAttackFieldMoveTypePowerBoostAbAttr { } +export class UserFieldMoveTypePowerBoostAbAttr extends PreAttackFieldMoveTypePowerBoostAbAttr {} /** * Boosts the power of moves in specified categories. @@ -1536,7 +2365,7 @@ export class AllyMoveCategoryPowerBoostAbAttr extends FieldMovePowerBoostAbAttr * @param powerMultiplier - The multiplier to apply to the move's power. */ constructor(boostedCategories: MoveCategory[], powerMultiplier: number) { - super((pokemon, defender, move) => boostedCategories.includes(move.category), powerMultiplier); + super((_pokemon, _defender, move) => boostedCategories.includes(move.category), powerMultiplier); } } @@ -1556,21 +2385,23 @@ export class StatMultiplierAbAttr extends AbAttr { canApplyStatStage( pokemon: Pokemon, _passive: boolean, - simulated: boolean, + _simulated: boolean, stat: BattleStat, - statValue: NumberHolder, - args: any[]): boolean { - const move = (args[0] as Move); + _statValue: NumberHolder, + args: any[], + ): boolean { + const move = args[0] as Move; return stat === this.stat && (!this.condition || this.condition(pokemon, null, move)); } applyStatStage( - pokemon: Pokemon, + _pokemon: Pokemon, _passive: boolean, - simulated: boolean, - stat: BattleStat, + _simulated: boolean, + _stat: BattleStat, statValue: NumberHolder, - args: any[]): void { + _args: any[], + ): void { statValue.value *= this.multiplier; } } @@ -1579,7 +2410,10 @@ export class PostAttackAbAttr extends AbAttr { private attackCondition: PokemonAttackCondition; /** The default attackCondition requires that the selected move is a damaging move */ - constructor(attackCondition: PokemonAttackCondition = (user, target, move) => (move.category !== MoveCategory.STATUS), showAbility = true) { + constructor( + attackCondition: PokemonAttackCondition = (_user, _target, move) => move.category !== MoveCategory.STATUS, + showAbility = true, + ) { super(showAbility); this.attackCondition = attackCondition; @@ -1592,25 +2426,27 @@ export class PostAttackAbAttr extends AbAttr { */ canApplyPostAttack( pokemon: Pokemon, - passive: boolean, - simulated: boolean, + _passive: boolean, + _simulated: boolean, defender: Pokemon, move: Move, - hitResult: HitResult | null, - args: any[]): boolean { + _hitResult: HitResult | null, + _args: any[], + ): boolean { // When attackRequired is true, we require the move to be an attack move and to deal damage before checking secondary requirements. // If attackRequired is false, we always defer to the secondary requirements. return this.attackCondition(pokemon, defender, move); } applyPostAttack( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - defender: Pokemon, - move: Move, - hitResult: HitResult | null, - args: any[]): void {} + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _defender: Pokemon, + _move: Move, + _hitResult: HitResult | null, + _args: any[], + ): void {} } /** @@ -1628,7 +2464,7 @@ export class AllyStatMultiplierAbAttr extends AbAttr { * @param multipler - The multiplier to apply to the stat * @param ignorable - Whether the multiplier can be ignored by mold breaker-like moves and abilities */ - constructor(stat: BattleStat, multiplier: number, ignorable: boolean = true) { + constructor(stat: BattleStat, multiplier: number, ignorable = true) { super(false); this.stat = stat; @@ -1648,23 +2484,41 @@ export class AllyStatMultiplierAbAttr extends AbAttr { * @param _args - unused * @returns `true` if this changed the checked stat, `false` otherwise. */ - applyAllyStat(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _stat: BattleStat, statValue: NumberHolder, _checkedPokemon: Pokemon, _ignoreAbility: boolean, _args: any[]) { + applyAllyStat( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _stat: BattleStat, + statValue: NumberHolder, + _checkedPokemon: Pokemon, + _ignoreAbility: boolean, + _args: any[], + ) { statValue.value *= this.multiplier; } /** * Check if this ability can apply to the checked stat. - * @param pokemon - The ally {@linkcode Pokemon} with the ability (unused) + * @param _pokemon - The ally {@linkcode Pokemon} with the ability (unused) * @param passive - unused - * @param simulated - Whether the ability is being simulated (unused) + * @param _simulated - Whether the ability is being simulated (unused) * @param stat - The type of the checked {@linkcode Stat} - * @param statValue - {@linkcode NumberHolder} containing the value of the checked stat - * @param checkedPokemon - The {@linkcode Pokemon} this ability is targeting (unused) + * @param _statValue - {@linkcode NumberHolder} containing the value of the checked stat + * @param _checkedPokemon - The {@linkcode Pokemon} this ability is targeting (unused) * @param ignoreAbility - Whether the ability should be ignored if possible - * @param args - unused + * @param _args - unused * @returns `true` if this can apply to the checked stat, `false` otherwise. */ - canApplyAllyStat(pokemon: Pokemon, _passive: boolean, simulated: boolean, stat: BattleStat, statValue: NumberHolder, checkedPokemon: Pokemon, ignoreAbility: boolean, args: any[]): boolean { + canApplyAllyStat( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + stat: BattleStat, + _statValue: NumberHolder, + _checkedPokemon: Pokemon, + ignoreAbility: boolean, + _args: any[], + ): boolean { return stat === this.stat && !(ignoreAbility && this.ignorable); } } @@ -1675,7 +2529,7 @@ export class AllyStatMultiplierAbAttr extends AbAttr { */ export class GorillaTacticsAbAttr extends PostAttackAbAttr { constructor() { - super((user, target, move) => true, false); + super((_user, _target, _move) => true, false); } override canApplyPostAttack( @@ -1685,29 +2539,33 @@ export class GorillaTacticsAbAttr extends PostAttackAbAttr { defender: Pokemon, move: Move, hitResult: HitResult | null, - args: any[]): boolean { - return super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args) - && simulated || !pokemon.getTag(BattlerTagType.GORILLA_TACTICS); + args: any[], + ): boolean { + return ( + (super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args) && simulated) || + !pokemon.getTag(BattlerTagType.GORILLA_TACTICS) + ); } /** * * @param {Pokemon} pokemon the {@linkcode Pokemon} with this ability - * @param passive n/a + * @param _passive n/a * @param simulated whether the ability is being simulated - * @param defender n/a - * @param move n/a - * @param hitResult n/a - * @param args n/a + * @param _defender n/a + * @param _move n/a + * @param _hitResult n/a + * @param _args n/a */ override applyPostAttack( pokemon: Pokemon, - passive: boolean, + _passive: boolean, simulated: boolean, - defender: Pokemon, - move: Move, - hitResult: HitResult | null, - args: any[]): void { + _defender: Pokemon, + _move: Move, + _hitResult: HitResult | null, + _args: any[], + ): void { if (!simulated) { pokemon.addTag(BattlerTagType.GORILLA_TACTICS); } @@ -1731,14 +2589,15 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { defender: Pokemon, move: Move, hitResult: HitResult, - args: any[]): boolean { + args: any[], + ): boolean { if ( super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args) && !simulated && hitResult < HitResult.NO_EFFECT && (!this.stealCondition || this.stealCondition(pokemon, defender, move)) ) { - const heldItems = this.getTargetHeldItems(defender).filter((i) => i.isTransferable); + const heldItems = this.getTargetHeldItems(defender).filter(i => i.isTransferable); if (heldItems.length) { // Ensure that the stolen item in testing is the same as when the effect is applied this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; @@ -1753,14 +2612,14 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { override applyPostAttack( pokemon: Pokemon, - passive: boolean, - simulated: boolean, + _passive: boolean, + _simulated: boolean, defender: Pokemon, - move: Move, - hitResult: HitResult, - args: any[], + _move: Move, + _hitResult: HitResult, + _args: any[], ): void { - const heldItems = this.getTargetHeldItems(defender).filter((i) => i.isTransferable); + const heldItems = this.getTargetHeldItems(defender).filter(i => i.isTransferable); if (!this.stolenItem) { this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; } @@ -1777,8 +2636,10 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { } getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { - return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier - && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[]; + return globalScene.findModifiers( + m => m instanceof PokemonHeldItemModifier && m.pokemonId === target.id, + target.isPlayer(), + ) as PokemonHeldItemModifier[]; } } @@ -1795,21 +2656,44 @@ export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr { this.effects = effects; } - override canApplyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { + override canApplyPostAttack( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + hitResult: HitResult | null, + args: any[], + ): boolean { if ( - super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args) - && (simulated || !attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker - && (!this.contactRequired || move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon})) && pokemon.randBattleSeedInt(100) < this.chance && !pokemon.status) + super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args) && + (simulated || + (!attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && + pokemon !== attacker && + (!this.contactRequired || + move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon })) && + pokemon.randBattleSeedInt(100) < this.chance && + !pokemon.status)) ) { - const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)]; + const effect = + this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)]; return simulated || attacker.canSetStatus(effect, true, false, pokemon); } return false; } - applyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): void { - const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)]; + applyPostAttack( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + _move: Move, + _hitResult: HitResult, + _args: any[], + ): void { + const effect = + this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)]; attacker.trySetStatus(effect, true, pokemon); } } @@ -1825,8 +2709,11 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr { private chance: (user: Pokemon, target: Pokemon, move: Move) => number; private effects: BattlerTagType[]; - - constructor(contactRequired: boolean, chance: (user: Pokemon, target: Pokemon, move: Move) => number, ...effects: BattlerTagType[]) { + constructor( + contactRequired: boolean, + chance: (user: Pokemon, target: Pokemon, move: Move) => number, + ...effects: BattlerTagType[] + ) { super(undefined, false); this.contactRequired = contactRequired; @@ -1834,17 +2721,39 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr { this.effects = effects; } - override canApplyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { + override canApplyPostAttack( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + hitResult: HitResult | null, + args: any[], + ): boolean { /**Battler tags inflicted by abilities post attacking are also considered additional effects.*/ - return super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args) && - !attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker && - (!this.contactRequired || move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon})) && - pokemon.randBattleSeedInt(100) < this.chance(attacker, pokemon, move) && !pokemon.status; + return ( + super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args) && + !attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && + pokemon !== attacker && + (!this.contactRequired || + move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon })) && + pokemon.randBattleSeedInt(100) < this.chance(attacker, pokemon, move) && + !pokemon.status + ); } - override applyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): void { + override applyPostAttack( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + attacker: Pokemon, + _move: Move, + _hitResult: HitResult, + _args: any[], + ): void { if (!simulated) { - const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)]; + const effect = + this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)]; attacker.addTag(effect); } } @@ -1860,13 +2769,17 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { this.condition = condition; } - override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if ( - !simulated && - hitResult < HitResult.NO_EFFECT && - (!this.condition || this.condition(pokemon, attacker, move)) - ) { - const heldItems = this.getTargetHeldItems(attacker).filter((i) => i.isTransferable); + override canApplyPostDefend( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + attacker: Pokemon, + move: Move, + hitResult: HitResult, + _args: any[], + ): boolean { + if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) { + const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable); if (heldItems.length) { this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; if (globalScene.canTransferHeldItemModifier(this.stolenItem, pokemon)) { @@ -1880,14 +2793,13 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { override applyPostDefend( pokemon: Pokemon, _passive: boolean, - simulated: boolean, + _simulated: boolean, attacker: Pokemon, - move: Move, - hitResult: HitResult, + _move: Move, + _hitResult: HitResult, _args: any[], ): void { - - const heldItems = this.getTargetHeldItems(attacker).filter((i) => i.isTransferable); + const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable); if (!this.stolenItem) { this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; } @@ -1904,8 +2816,10 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { } getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { - return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier - && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[]; + return globalScene.findModifiers( + m => m instanceof PokemonHeldItemModifier && m.pokemonId === target.id, + target.isPlayer(), + ) as PokemonHeldItemModifier[]; } } @@ -1915,30 +2829,31 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { */ export class PostSetStatusAbAttr extends AbAttr { canApplyPostSetStatus( - pokemon: Pokemon, - sourcePokemon: Pokemon | null = null, - passive: boolean, - effect: StatusEffect, - simulated: boolean, - rgs: any[]): boolean { + _pokemon: Pokemon, + _sourcePokemon: Pokemon | null = null, + _passive: boolean, + _effect: StatusEffect, + _simulated: boolean, + _rgs: any[], + ): boolean { return true; } /** * Does nothing after a status condition is set. - * @param pokemon {@linkcode Pokemon} that status condition was set on. - * @param sourcePokemon {@linkcode Pokemon} that that set the status condition. Is `null` if status was not set by a Pokemon. - * @param passive Whether this ability is a passive. - * @param effect {@linkcode StatusEffect} that was set. - * @param args Set of unique arguments needed by this attribute. + * @param _pokemon {@linkcode Pokemon} that status condition was set on. + * @param _sourcePokemon {@linkcode Pokemon} that that set the status condition. Is `null` if status was not set by a Pokemon. + * @param _passive Whether this ability is a passive. + * @param _effect {@linkcode StatusEffect} that was set. + * @param _args Set of unique arguments needed by this attribute. */ applyPostSetStatus( - pokemon: Pokemon, - sourcePokemon: Pokemon | null = null, - passive: boolean, - effect: StatusEffect, - simulated: boolean, - args: any[], + _pokemon: Pokemon, + _sourcePokemon: Pokemon | null = null, + _passive: boolean, + _effect: StatusEffect, + _simulated: boolean, + _args: any[], ): void {} } @@ -1948,17 +2863,24 @@ export class PostSetStatusAbAttr extends AbAttr { * ability attribute. For Synchronize ability. */ export class SynchronizeStatusAbAttr extends PostSetStatusAbAttr { - override canApplyPostSetStatus(pokemon: Pokemon, sourcePokemon: (Pokemon | null) | undefined, passive: boolean, effect: StatusEffect, simulated: boolean, args: any[]): boolean { + override canApplyPostSetStatus( + _pokemon: Pokemon, + sourcePokemon: (Pokemon | null) | undefined, + _passive: boolean, + effect: StatusEffect, + _simulated: boolean, + _args: any[], + ): boolean { /** Synchronizable statuses */ const syncStatuses = new Set([ StatusEffect.BURN, StatusEffect.PARALYSIS, StatusEffect.POISON, - StatusEffect.TOXIC + StatusEffect.TOXIC, ]); // synchronize does not need to check canSetStatus because the ability shows even if it fails to set the status - return ((sourcePokemon ?? false) && syncStatuses.has(effect)); + return (sourcePokemon ?? false) && syncStatuses.has(effect); } /** @@ -1966,11 +2888,18 @@ export class SynchronizeStatusAbAttr extends PostSetStatusAbAttr { * was set by a source Pokemon, set the source Pokemon's status to the same `StatusEffect`. * @param pokemon {@linkcode Pokemon} that status condition was set on. * @param sourcePokemon {@linkcode Pokemon} that that set the status condition. Is null if status was not set by a Pokemon. - * @param passive Whether this ability is a passive. + * @param _passive Whether this ability is a passive. * @param effect {@linkcode StatusEffect} that was set. - * @param args Set of unique arguments needed by this attribute. + * @param _args Set of unique arguments needed by this attribute. */ - override applyPostSetStatus(pokemon: Pokemon, sourcePokemon: Pokemon | null = null, passive: boolean, effect: StatusEffect, simulated: boolean, args: any[]): void { + override applyPostSetStatus( + pokemon: Pokemon, + sourcePokemon: Pokemon | null = null, + _passive: boolean, + effect: StatusEffect, + simulated: boolean, + _args: any[], + ): void { if (!simulated && sourcePokemon) { sourcePokemon.trySetStatus(effect, true, pokemon); } @@ -1978,11 +2907,11 @@ export class SynchronizeStatusAbAttr extends PostSetStatusAbAttr { } export class PostVictoryAbAttr extends AbAttr { - canApplyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + canApplyPostVictory(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return true; } - applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {} + applyPostVictory(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {} } class PostVictoryStatStageChangeAbAttr extends PostVictoryAbAttr { @@ -1996,10 +2925,12 @@ class PostVictoryStatStageChangeAbAttr extends PostVictoryAbAttr { this.stages = stages; } - override applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostVictory(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { const stat = typeof this.stat === "function" ? this.stat(pokemon) : this.stat; if (!simulated) { - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ stat ], this.stages)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase(pokemon.getBattlerIndex(), true, [stat], this.stages), + ); } } } @@ -2007,18 +2938,18 @@ class PostVictoryStatStageChangeAbAttr extends PostVictoryAbAttr { export class PostVictoryFormChangeAbAttr extends PostVictoryAbAttr { private formFunc: (p: Pokemon) => number; - constructor(formFunc: ((p: Pokemon) => number)) { + constructor(formFunc: (p: Pokemon) => number) { super(true); this.formFunc = formFunc; } - override canApplyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostVictory(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { const formIndex = this.formFunc(pokemon); return formIndex !== pokemon.formIndex; } - override applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostVictory(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); } @@ -2026,11 +2957,23 @@ export class PostVictoryFormChangeAbAttr extends PostVictoryAbAttr { } export class PostKnockOutAbAttr extends AbAttr { - canApplyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): boolean { + canApplyPostKnockOut( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _knockedOut: Pokemon, + _args: any[], + ): boolean { return true; } - applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): void {} + applyPostKnockOut( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _knockedOut: Pokemon, + _args: any[], + ): void {} } export class PostKnockOutStatStageChangeAbAttr extends PostKnockOutAbAttr { @@ -2044,27 +2987,48 @@ export class PostKnockOutStatStageChangeAbAttr extends PostKnockOutAbAttr { this.stages = stages; } - override applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): void { + override applyPostKnockOut( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + _knockedOut: Pokemon, + _args: any[], + ): void { const stat = typeof this.stat === "function" ? this.stat(pokemon) : this.stat; if (!simulated) { - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ stat ], this.stages)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase(pokemon.getBattlerIndex(), true, [stat], this.stages), + ); } } } export class CopyFaintedAllyAbilityAbAttr extends PostKnockOutAbAttr { - constructor() { - super(); - } - - override canApplyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): boolean { + override canApplyPostKnockOut( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + knockedOut: Pokemon, + _args: any[], + ): boolean { return pokemon.isPlayer() === knockedOut.isPlayer() && knockedOut.getAbility().isCopiable; } - override applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): void { + override applyPostKnockOut( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + knockedOut: Pokemon, + _args: any[], + ): void { if (!simulated) { pokemon.setTempAbility(knockedOut.getAbility()); - globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:copyFaintedAllyAbility", { pokemonNameWithAffix: getPokemonNameWithAffix(knockedOut), abilityName: allAbilities[knockedOut.getAbility().id].name })); + globalScene.phaseManager.queueMessage( + i18next.t("abilityTriggers:copyFaintedAllyAbility", { + pokemonNameWithAffix: getPokemonNameWithAffix(knockedOut), + abilityName: allAbilities[knockedOut.getAbility().id].name, + }), + ); } } } @@ -2082,7 +3046,7 @@ export class IgnoreOpponentStatStagesAbAttr extends AbAttr { this.stats = stats ?? BATTLE_STATS; } - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { return this.stats.includes(args[0]); } @@ -2090,11 +3054,17 @@ export class IgnoreOpponentStatStagesAbAttr extends AbAttr { * Modifies a BooleanHolder and returns the result to see if a stat is ignored or not * @param _pokemon n/a * @param _passive n/a - * @param simulated n/a + * @param _simulated n/a * @param _cancelled n/a * @param args A BooleanHolder that represents whether or not to ignore a stat's stat changes */ - override apply(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: any[], + ): void { (args[1] as BooleanHolder).value = true; } } @@ -2104,14 +3074,20 @@ export class IntimidateImmunityAbAttr extends AbAttr { super(false); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + cancelled: BooleanHolder, + _args: any[], + ): void { cancelled.value = true; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:intimidateImmunity", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - abilityName + abilityName, }); } } @@ -2128,9 +3104,17 @@ export class PostIntimidateStatStageChangeAbAttr extends AbAttr { this.overwrites = !!overwrites; } - override apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + cancelled: BooleanHolder, + _args: any[], + ): void { if (!simulated) { - globalScene.phaseManager.pushPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages)); + globalScene.phaseManager.pushPhase( + new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages), + ); } cancelled.value = this.overwrites; } @@ -2156,17 +3140,17 @@ export class PostSummonAbAttr extends AbAttr { return this.activateOnGain; } - canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + canApplyPostSummon(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return true; } /** * Applies ability post summon (after switching in) - * @param pokemon {@linkcode Pokemon} with this ability - * @param passive Whether this ability is a passive - * @param args Set of unique arguments needed by this attribute + * @param _pokemon {@linkcode Pokemon} with this ability + * @param _passive Whether this ability is a passive + * @param _args Set of unique arguments needed by this attribute */ - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {} + applyPostSummon(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {} } /** @@ -2189,11 +3173,11 @@ export class PostSummonRemoveArenaTagAbAttr extends PostSummonAbAttr { this.arenaTags = arenaTags; } - override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostSummon(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return globalScene.arena.tags.some(tag => this.arenaTags.includes(tag.tagType)); } - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostSummon(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { for (const arenaTag of this.arenaTags) { globalScene.arena.removeTag(arenaTag); @@ -2212,7 +3196,6 @@ export class PostSummonAddArenaTagAbAttr extends PostSummonAbAttr { private readonly quiet?: boolean; private sourceId: number; - constructor(showAbility: boolean, tagType: ArenaTagType, turnCount: number, side?: ArenaTagSide, quiet?: boolean) { super(showAbility); this.tagType = tagType; @@ -2221,7 +3204,7 @@ export class PostSummonAddArenaTagAbAttr extends PostSummonAbAttr { this.quiet = quiet; } - public override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + public override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { this.sourceId = pokemon.id; if (!simulated) { globalScene.arena.addTag(this.tagType, this.turnCount, undefined, this.sourceId, this.side, this.quiet); @@ -2238,7 +3221,7 @@ export class PostSummonMessageAbAttr extends PostSummonAbAttr { this.messageFunc = messageFunc; } - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { globalScene.phaseManager.queueMessage(this.messageFunc(pokemon)); } @@ -2255,7 +3238,7 @@ export class PostSummonUnnamedMessageAbAttr extends PostSummonAbAttr { this.message = message; } - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostSummon(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { globalScene.phaseManager.queueMessage(this.message); } @@ -2273,11 +3256,11 @@ export class PostSummonAddBattlerTagAbAttr extends PostSummonAbAttr { this.turnCount = turnCount; } - override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return pokemon.canAddTag(this.tagType); } - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { pokemon.addTag(this.tagType, this.turnCount); } @@ -2300,11 +3283,11 @@ export class PostSummonRemoveBattlerTagAbAttr extends PostSummonRemoveEffectAbAt this.immuneTags = immuneTags; } - public override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + public override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return this.immuneTags.some(tagType => !!pokemon.getTag(tagType)); } - public override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + public override applyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { this.immuneTags.forEach(tagType => pokemon.removeTag(tagType)); } } @@ -2324,7 +3307,7 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { this.intimidate = !!intimidate; } - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (simulated) { return; } @@ -2332,7 +3315,9 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { if (this.selfTarget) { // we unshift the StatStageChangePhase to put it right after the showAbility and not at the end of the // phase list (which could be after CommandPhase for example) - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages), + ); } else { for (const opponent of pokemon.getOpponents()) { const cancelled = new BooleanHolder(false); @@ -2345,7 +3330,9 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { } } if (!cancelled.value) { - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(opponent.getBattlerIndex(), false, this.stats, this.stages)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase(opponent.getBattlerIndex(), false, this.stats, this.stages), + ); } } } @@ -2363,15 +3350,25 @@ export class PostSummonAllyHealAbAttr extends PostSummonAbAttr { this.showAnim = showAnim; } - override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return pokemon.getAlly()?.isActive(true) ?? false; } - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { const target = pokemon.getAlly(); if (!simulated && !isNullOrUndefined(target)) { - globalScene.phaseManager.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / this.healRatio), i18next.t("abilityTriggers:postSummonAllyHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(target), pokemonName: pokemon.name }), true, !this.showAnim)); + globalScene.phaseManager.unshiftPhase( + new PokemonHealPhase( + target.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / this.healRatio), + i18next.t("abilityTriggers:postSummonAllyHeal", { + pokemonNameWithAffix: getPokemonNameWithAffix(target), + pokemonName: pokemon.name, + }), + true, + !this.showAnim, + ), + ); } } } @@ -2385,22 +3382,22 @@ export class PostSummonAllyHealAbAttr extends PostSummonAbAttr { * @returns if the move was successful */ export class PostSummonClearAllyStatStagesAbAttr extends PostSummonAbAttr { - constructor() { - super(); - } - - override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return pokemon.getAlly()?.isActive(true) ?? false; } - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { const target = pokemon.getAlly(); if (!simulated && !isNullOrUndefined(target)) { for (const s of BATTLE_STATS) { target.setStatStage(s, 0); } - globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:postSummonClearAllyStats", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); + globalScene.phaseManager.queueMessage( + i18next.t("abilityTriggers:postSummonClearAllyStats", { + pokemonNameWithAffix: getPokemonNameWithAffix(target), + }), + ); } } } @@ -2418,7 +3415,7 @@ export class DownloadAbAttr extends PostSummonAbAttr { private enemyCountTally: number; private stats: BattleStat[]; - override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { this.enemyDef = 0; this.enemySpDef = 0; this.enemyCountTally = 0; @@ -2437,14 +3434,14 @@ export class DownloadAbAttr extends PostSummonAbAttr { * Checks to see if it is the opening turn (starting a new game), if so, Download won't work. This is because Download takes into account * vitamins and items, so it needs to use the Stat and the stat alone. * @param {Pokemon} pokemon Pokemon that is using the move, as well as seeing the opposing pokemon. - * @param {boolean} passive N/A - * @param {any[]} args N/A + * @param {boolean} _passive N/A + * @param {any[]} _args N/A */ - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (this.enemyDef < this.enemySpDef) { - this.stats = [ Stat.ATK ]; + this.stats = [Stat.ATK]; } else { - this.stats = [ Stat.SPATK ]; + this.stats = [Stat.SPATK]; } if (!simulated) { @@ -2462,14 +3459,16 @@ export class PostSummonWeatherChangeAbAttr extends PostSummonAbAttr { this.weatherType = weatherType; } - override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const weatherReplaceable = (this.weatherType === WeatherType.HEAVY_RAIN || + override canApplyPostSummon(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + const weatherReplaceable = + this.weatherType === WeatherType.HEAVY_RAIN || this.weatherType === WeatherType.HARSH_SUN || - this.weatherType === WeatherType.STRONG_WINDS) || !globalScene.arena.weather?.isImmutable(); + this.weatherType === WeatherType.STRONG_WINDS || + !globalScene.arena.weather?.isImmutable(); return weatherReplaceable && globalScene.arena.canSetWeather(this.weatherType); } - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { globalScene.arena.trySetWeather(this.weatherType, pokemon); } @@ -2485,11 +3484,11 @@ export class PostSummonTerrainChangeAbAttr extends PostSummonAbAttr { this.terrainType = terrainType; } - override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostSummon(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return globalScene.arena.canSetTerrain(this.terrainType); } - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { globalScene.arena.trySetTerrain(this.terrainType, false, pokemon); } @@ -2511,12 +3510,12 @@ export class PostSummonHealStatusAbAttr extends PostSummonRemoveEffectAbAttr { this.immuneEffects = immuneEffects; } - public override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + public override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { const status = pokemon.status?.effect; - return !isNullOrUndefined(status) && (this.immuneEffects.length < 1 || this.immuneEffects.includes(status)) + return !isNullOrUndefined(status) && (this.immuneEffects.length < 1 || this.immuneEffects.includes(status)); } - public override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + public override applyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { const status = pokemon.status?.effect; if (!isNullOrUndefined(status)) { this.statusHealed = status; @@ -2536,17 +3535,17 @@ export class PostSummonHealStatusAbAttr extends PostSummonRemoveEffectAbAttr { export class PostSummonFormChangeAbAttr extends PostSummonAbAttr { private formFunc: (p: Pokemon) => number; - constructor(formFunc: ((p: Pokemon) => number)) { + constructor(formFunc: (p: Pokemon) => number) { super(true); this.formFunc = formFunc; } - override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return this.formFunc(pokemon) !== pokemon.formIndex; } - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); } @@ -2558,7 +3557,7 @@ export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr { private target: Pokemon; private targetAbilityName: string; - override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { const targets = pokemon.getOpponents(); if (!targets.length) { return false; @@ -2566,7 +3565,7 @@ export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr { let target: Pokemon; if (targets.length > 1) { - globalScene.executeWithSeedOffset(() => target = randSeedItem(targets), globalScene.currentBattle.waveIndex); + globalScene.executeWithSeedOffset(() => (target = randSeedItem(targets)), globalScene.currentBattle.waveIndex); } else { target = targets[0]; } @@ -2584,7 +3583,7 @@ export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr { return true; } - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { pokemon.setTempAbility(this.target!.getAbility()); setAbilityRevealed(this.target!); @@ -2592,7 +3591,7 @@ export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr { } } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:trace", { pokemonName: getPokemonNameWithAffix(pokemon), targetName: getPokemonNameWithAffix(this.target), @@ -2616,7 +3615,7 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt this.statusEffect = statusEffect; } - override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { const party = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); return party.filter(p => p.isAllowedInBattle()).length > 0; } @@ -2625,17 +3624,19 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt * Removes supplied status effect from the user's field when user of the ability is summoned. * * @param pokemon - The Pokémon that triggered the ability. - * @param passive - n/a - * @param args - n/a + * @param _passive - n/a + * @param _args - n/a */ - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { const party = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); const allowedParty = party.filter(p => p.isAllowedInBattle()); if (!simulated) { for (const pokemon of allowedParty) { if (pokemon.status && this.statusEffect.includes(pokemon.status.effect)) { - globalScene.phaseManager.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); + globalScene.phaseManager.queueMessage( + getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)), + ); pokemon.resetStatus(false); pokemon.updateInfo(); } @@ -2644,10 +3645,9 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt } } - /** Attempt to copy the stat changes on an ally pokemon */ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { - override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { if (!globalScene.currentBattle.double) { return false; } @@ -2656,7 +3656,7 @@ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { return !(isNullOrUndefined(ally) || ally.getStatStages().every(s => s === 0)); } - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { const ally = pokemon.getAlly(); if (!simulated && !isNullOrUndefined(ally)) { for (const s of BATTLE_STATS) { @@ -2666,7 +3666,7 @@ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { } } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:costar", { pokemonName: getPokemonNameWithAffix(pokemon), allyName: getPokemonNameWithAffix(pokemon.getAlly()), @@ -2691,7 +3691,8 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { if (targets[0].fusionSpecies) { target = targets[1]; return; - } else if (targets[1].fusionSpecies) { + } + if (targets[1].fusionSpecies) { target = targets[0]; return; } @@ -2706,7 +3707,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { return target; } - override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): boolean { const targets = pokemon.getOpponents(); const target = this.getTarget(targets); @@ -2722,11 +3723,12 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { return !(this.getTarget(targets).fusionSpecies || pokemon.fusionSpecies); } - override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override applyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { const target = this.getTarget(pokemon.getOpponents()); - globalScene.phaseManager.unshiftPhase(new PokemonTransformPhase(pokemon.getBattlerIndex(), target.getBattlerIndex(), true)); - + globalScene.phaseManager.unshiftPhase( + new PokemonTransformPhase(pokemon.getBattlerIndex(), target.getBattlerIndex(), true), + ); } } @@ -2736,17 +3738,17 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { * @extends PostSummonAbAttr */ export class PostSummonWeatherSuppressedFormChangeAbAttr extends PostSummonAbAttr { - override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostSummon(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return getPokemonWithWeatherBasedForms().length > 0; } /** * Triggers {@linkcode Arena.triggerWeatherBasedFormChangesToNormal | triggerWeatherBasedFormChangesToNormal} - * @param {Pokemon} pokemon the Pokemon with this ability - * @param passive n/a - * @param args n/a + * @param {Pokemon} _pokemon the Pokemon with this ability + * @param _passive n/a + * @param _args n/a */ - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostSummon(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { globalScene.arena.triggerWeatherBasedFormChangesToNormal(); } @@ -2767,9 +3769,11 @@ export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr { this.ability = ability; } - override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const isCastformWithForecast = (pokemon.species.speciesId === SpeciesId.CASTFORM && this.ability === AbilityId.FORECAST); - const isCherrimWithFlowerGift = (pokemon.species.speciesId === SpeciesId.CHERRIM && this.ability === AbilityId.FLOWER_GIFT); + override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + const isCastformWithForecast = + pokemon.species.speciesId === SpeciesId.CASTFORM && this.ability === AbilityId.FORECAST; + const isCherrimWithFlowerGift = + pokemon.species.speciesId === SpeciesId.CHERRIM && this.ability === AbilityId.FLOWER_GIFT; return isCastformWithForecast || isCherrimWithFlowerGift; } @@ -2779,10 +3783,10 @@ export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr { * {@linkcode SpeciesFormChange.SpeciesFormChangeWeatherTrigger | SpeciesFormChangeRevertWeatherFormTrigger} if it * is the specific Pokemon and ability * @param {Pokemon} pokemon the Pokemon with this ability - * @param passive n/a - * @param args n/a + * @param _passive n/a + * @param _args n/a */ - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeWeatherTrigger); globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeRevertWeatherFormTrigger); @@ -2801,17 +3805,21 @@ export class CommanderAbAttr extends AbAttr { super(true); } - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { // If the ally Dondozo is fainted or was previously "commanded" by // another Pokemon, this effect cannot apply. // TODO: Should this work with X + Dondozo fusions? const ally = pokemon.getAlly(); - return globalScene.currentBattle?.double && !isNullOrUndefined(ally) && ally.species.speciesId === SpeciesId.DONDOZO - && !(ally.isFainted() || ally.getTag(BattlerTagType.COMMANDED)); + return ( + globalScene.currentBattle?.double && + !isNullOrUndefined(ally) && + ally.species.speciesId === SpeciesId.DONDOZO && + !(ally.isFainted() || ally.getTag(BattlerTagType.COMMANDED)) + ); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: null, args: any[]): void { + override apply(pokemon: Pokemon, _passive: boolean, simulated: boolean, _cancelled: null, _args: any[]): void { if (!simulated) { // Lapse the source's semi-invulnerable tags (to avoid visual inconsistencies) pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); @@ -2820,21 +3828,21 @@ export class CommanderAbAttr extends AbAttr { // Apply boosts from this effect to the ally Dondozo pokemon.getAlly()?.addTag(BattlerTagType.COMMANDED, 0, MoveId.NONE, pokemon.id); // Cancel the source Pokemon's next move (if a move is queued) - globalScene.phaseManager.tryRemovePhase((phase) => phase.is("MovePhase") && phase.pokemon === pokemon); + globalScene.phaseManager.tryRemovePhase(phase => phase.is("MovePhase") && phase.pokemon === pokemon); } } } export class PreSwitchOutAbAttr extends AbAttr { - constructor(showAbility: boolean = true) { + constructor(showAbility = true) { super(showAbility); } - canApplyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + canApplyPreSwitchOut(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return true; } - applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {} + applyPreSwitchOut(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {} } export class PreSwitchOutResetStatusAbAttr extends PreSwitchOutAbAttr { @@ -2842,11 +3850,11 @@ export class PreSwitchOutResetStatusAbAttr extends PreSwitchOutAbAttr { super(false); } - override canApplyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPreSwitchOut(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return !isNullOrUndefined(pokemon.status); } - override applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPreSwitchOut(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { pokemon.resetStatus(); pokemon.updateInfo(); @@ -2860,11 +3868,11 @@ export class PreSwitchOutResetStatusAbAttr extends PreSwitchOutAbAttr { export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { /** * @param pokemon The {@linkcode Pokemon} with the ability - * @param passive N/A - * @param args N/A + * @param _passive N/A + * @param _args N/A * @returns {boolean} Returns true if the weather clears, otherwise false. */ - override applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override applyPreSwitchOut(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): boolean { const weatherType = globalScene.arena.weather?.weatherType; let turnOffWeather = false; @@ -2875,8 +3883,8 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { pokemon.hasAbility(AbilityId.DESOLATE_LAND) && globalScene .getField(true) - .filter((p) => p !== pokemon) - .filter((p) => p.hasAbility(AbilityId.DESOLATE_LAND)).length === 0 + .filter(p => p !== pokemon) + .filter(p => p.hasAbility(AbilityId.DESOLATE_LAND)).length === 0 ) { turnOffWeather = true; } @@ -2886,8 +3894,8 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { pokemon.hasAbility(AbilityId.PRIMORDIAL_SEA) && globalScene .getField(true) - .filter((p) => p !== pokemon) - .filter((p) => p.hasAbility(AbilityId.PRIMORDIAL_SEA)).length === 0 + .filter(p => p !== pokemon) + .filter(p => p.hasAbility(AbilityId.PRIMORDIAL_SEA)).length === 0 ) { turnOffWeather = true; } @@ -2897,8 +3905,8 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { pokemon.hasAbility(AbilityId.DELTA_STREAM) && globalScene .getField(true) - .filter((p) => p !== pokemon) - .filter((p) => p.hasAbility(AbilityId.DELTA_STREAM)).length === 0 + .filter(p => p !== pokemon) + .filter(p => p.hasAbility(AbilityId.DELTA_STREAM)).length === 0 ) { turnOffWeather = true; } @@ -2919,11 +3927,11 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { } export class PreSwitchOutHealAbAttr extends PreSwitchOutAbAttr { - override canApplyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPreSwitchOut(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return !pokemon.isFullHp(); } - override applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPreSwitchOut(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { const healAmount = toDmgValue(pokemon.getMaxHp() * 0.33); pokemon.heal(healAmount); @@ -2940,62 +3948,75 @@ export class PreSwitchOutHealAbAttr extends PreSwitchOutAbAttr { export class PreSwitchOutFormChangeAbAttr extends PreSwitchOutAbAttr { private formFunc: (p: Pokemon) => number; - constructor(formFunc: ((p: Pokemon) => number)) { + constructor(formFunc: (p: Pokemon) => number) { super(); this.formFunc = formFunc; } - override canApplyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPreSwitchOut(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return this.formFunc(pokemon) !== pokemon.formIndex; } /** * On switch out, trigger the form change to the one defined in the ability * @param pokemon The pokemon switching out and changing form {@linkcode Pokemon} - * @param passive N/A - * @param args N/A + * @param _passive N/A + * @param _args N/A */ - override applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPreSwitchOut(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); } } - } export class PreLeaveFieldAbAttr extends AbAttr { - canApplyPreLeaveField(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + canApplyPreLeaveField(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return true; } - applyPreLeaveField(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {} + applyPreLeaveField(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {} } /** * Clears Desolate Land/Primordial Sea/Delta Stream upon the Pokemon switching out. */ export class PreLeaveFieldClearWeatherAbAttr extends PreLeaveFieldAbAttr { - - override canApplyPreLeaveField(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPreLeaveField(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { const weatherType = globalScene.arena.weather?.weatherType; // Clear weather only if user's ability matches the weather and no other pokemon has the ability. switch (weatherType) { - case (WeatherType.HARSH_SUN): - if (pokemon.hasAbility(AbilityId.DESOLATE_LAND) - && globalScene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(AbilityId.DESOLATE_LAND)).length === 0) { + case WeatherType.HARSH_SUN: + if ( + pokemon.hasAbility(AbilityId.DESOLATE_LAND) && + globalScene + .getField(true) + .filter(p => p !== pokemon) + .filter(p => p.hasAbility(AbilityId.DESOLATE_LAND)).length === 0 + ) { return true; } break; - case (WeatherType.HEAVY_RAIN): - if (pokemon.hasAbility(AbilityId.PRIMORDIAL_SEA) - && globalScene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(AbilityId.PRIMORDIAL_SEA)).length === 0) { + case WeatherType.HEAVY_RAIN: + if ( + pokemon.hasAbility(AbilityId.PRIMORDIAL_SEA) && + globalScene + .getField(true) + .filter(p => p !== pokemon) + .filter(p => p.hasAbility(AbilityId.PRIMORDIAL_SEA)).length === 0 + ) { return true; } break; - case (WeatherType.STRONG_WINDS): - if (pokemon.hasAbility(AbilityId.DELTA_STREAM) - && globalScene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(AbilityId.DELTA_STREAM)).length === 0) { + case WeatherType.STRONG_WINDS: + if ( + pokemon.hasAbility(AbilityId.DELTA_STREAM) && + globalScene + .getField(true) + .filter(p => p !== pokemon) + .filter(p => p.hasAbility(AbilityId.DELTA_STREAM)).length === 0 + ) { return true; } break; @@ -3004,11 +4025,11 @@ export class PreLeaveFieldClearWeatherAbAttr extends PreLeaveFieldAbAttr { } /** - * @param pokemon The {@linkcode Pokemon} with the ability - * @param passive N/A - * @param args N/A + * @param _pokemon The {@linkcode Pokemon} with the ability + * @param _passive N/A + * @param _args N/A */ - override applyPreLeaveField(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPreLeaveField(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { globalScene.arena.trySetWeather(WeatherType.NONE); } @@ -3023,11 +4044,16 @@ export class PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr extends PreLeaveFi super(false); } - public override canApplyPreLeaveField(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + public override canApplyPreLeaveField( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _args: any[], + ): boolean { return !!globalScene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS); } - public override applyPreLeaveField(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + public override applyPreLeaveField(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { const suppressTag = globalScene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag; suppressTag.onSourceLeave(globalScene.arena); } @@ -3035,22 +4061,23 @@ export class PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr extends PreLeaveFi export class PreStatStageChangeAbAttr extends AbAttr { canApplyPreStatStageChange( - pokemon: Pokemon | null, - passive: boolean, - simulated: boolean, - stat: BattleStat, - cancelled: BooleanHolder, - args: any[]): boolean { + _pokemon: Pokemon | null, + _passive: boolean, + _simulated: boolean, + _stat: BattleStat, + _cancelled: BooleanHolder, + _args: any[], + ): boolean { return true; } applyPreStatStageChange( - pokemon: Pokemon | null, - passive: boolean, - simulated: boolean, - stat: BattleStat, - cancelled: BooleanHolder, - args: any[], + _pokemon: Pokemon | null, + _passive: boolean, + _simulated: boolean, + _stat: BattleStat, + _cancelled: BooleanHolder, + _args: any[], ): void {} } @@ -3060,7 +4087,7 @@ export class PreStatStageChangeAbAttr extends AbAttr { */ export class ReflectStatStageChangeAbAttr extends PreStatStageChangeAbAttr { /** {@linkcode BattleStat} to reflect */ - private reflectedStat? : BattleStat; + private reflectedStat?: BattleStat; /** * Apply the {@linkcode ReflectStatStageChangeAbAttr} to an interaction @@ -3071,12 +4098,21 @@ export class ReflectStatStageChangeAbAttr extends PreStatStageChangeAbAttr { * @param cancelled The {@linkcode BooleanHolder} that will be set to true due to reflection * @param args */ - override applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, simulated: boolean, stat: BattleStat, cancelled: BooleanHolder, args: any[]): void { + override applyPreStatStageChange( + _pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + stat: BattleStat, + cancelled: BooleanHolder, + args: any[], + ): void { const attacker: Pokemon = args[0]; const stages = args[1]; this.reflectedStat = stat; if (!simulated) { - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(attacker.getBattlerIndex(), false, [ stat ], stages, true, false, true, null, true)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase(attacker.getBattlerIndex(), false, [stat], stages, true, false, true, null, true), + ); } cancelled.value = true; } @@ -3085,7 +4121,7 @@ export class ReflectStatStageChangeAbAttr extends PreStatStageChangeAbAttr { return i18next.t("abilityTriggers:protectStat", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, - statName: this.reflectedStat ? i18next.t(getStatKey(this.reflectedStat)) : i18next.t("battle:stats") + statName: this.reflectedStat ? i18next.t(getStatKey(this.reflectedStat)) : i18next.t("battle:stats"), }); } } @@ -3103,7 +4139,14 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr { this.protectedStat = protectedStat; } - override canApplyPreStatStageChange(pokemon: Pokemon | null, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: BooleanHolder, args: any[]): boolean { + override canApplyPreStatStageChange( + _pokemon: Pokemon | null, + _passive: boolean, + _simulated: boolean, + stat: BattleStat, + _cancelled: BooleanHolder, + _args: any[], + ): boolean { return isNullOrUndefined(this.protectedStat) || stat === this.protectedStat; } @@ -3112,11 +4155,18 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr { * @param _pokemon * @param _passive * @param simulated - * @param stat the {@linkcode BattleStat} being affected + * @param _stat the {@linkcode BattleStat} being affected * @param cancelled The {@linkcode BooleanHolder} that will be set to true if the stat is protected * @param _args */ - override applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, stat: BattleStat, cancelled: BooleanHolder, _args: any[]): void { + override applyPreStatStageChange( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _stat: BattleStat, + cancelled: BooleanHolder, + _args: any[], + ): void { cancelled.value = true; } @@ -3124,7 +4174,7 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr { return i18next.t("abilityTriggers:protectStat", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, - statName: this.protectedStat ? i18next.t(getStatKey(this.protectedStat)) : i18next.t("battle:stats") + statName: this.protectedStat ? i18next.t(getStatKey(this.protectedStat)) : i18next.t("battle:stats"), }); } } @@ -3142,26 +4192,45 @@ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr { constructor(...effects: StatusEffect[]) { /** This effect does not require a damaging move */ - super((user, target, move) => true); + super((_user, _target, _move) => true); this.effects = effects; } - override canApplyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { - return super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args) - && this.effects.indexOf(args[0]) > -1 && !defender.isFainted() && defender.canAddTag(BattlerTagType.CONFUSED); + override canApplyPostAttack( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + defender: Pokemon, + move: Move, + hitResult: HitResult | null, + args: any[], + ): boolean { + return ( + super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args) && + this.effects.indexOf(args[0]) > -1 && + !defender.isFainted() && + defender.canAddTag(BattlerTagType.CONFUSED) + ); } - /** * Applies confusion to the target pokemon. * @param pokemon {@link Pokemon} attacking - * @param passive N/A + * @param _passive N/A * @param defender {@link Pokemon} defending * @param move {@link Move} used to apply status effect and confusion - * @param hitResult N/A - * @param args [0] {@linkcode StatusEffect} applied by move + * @param _hitResult N/A + * @param _args [0] {@linkcode StatusEffect} applied by move */ - override applyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): void { + override applyPostAttack( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + defender: Pokemon, + move: Move, + _hitResult: HitResult, + _args: any[], + ): void { if (!simulated) { defender.addTag(BattlerTagType.CONFUSED, pokemon.randBattleSeedIntRange(2, 5), move.id, defender.id); } @@ -3171,22 +4240,23 @@ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr { export class PreSetStatusAbAttr extends AbAttr { /** Return whether the ability attribute can be applied */ canApplyPreSetStatus( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - effect: StatusEffect | undefined, - cancelled: BooleanHolder, - args: any[]): boolean { + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _effect: StatusEffect | undefined, + _cancelled: BooleanHolder, + _args: any[], + ): boolean { return true; } applyPreSetStatus( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - effect: StatusEffect | undefined, - cancelled: BooleanHolder, - args: any[], + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _effect: StatusEffect | undefined, + _cancelled: BooleanHolder, + _args: any[], ): void {} } @@ -3206,35 +4276,49 @@ export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr { this.immuneEffects = immuneEffects; } - override canApplyPreSetStatus(pokemon: Pokemon, passive: boolean, simulated: boolean, effect: StatusEffect, cancelled: BooleanHolder, args: any[]): boolean { - return effect !== StatusEffect.FAINT && this.immuneEffects.length < 1 || this.immuneEffects.includes(effect); + override canApplyPreSetStatus( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + effect: StatusEffect, + _cancelled: BooleanHolder, + _args: any[], + ): boolean { + return (effect !== StatusEffect.FAINT && this.immuneEffects.length < 1) || this.immuneEffects.includes(effect); } /** * Applies immunity to supplied status effects. * - * @param pokemon - The Pokémon to which the status is being applied. - * @param passive - n/a + * @param _pokemon - The Pokémon to which the status is being applied. + * @param _passive - n/a * @param effect - The status effect being applied. * @param cancelled - A holder for a boolean value indicating if the status application was cancelled. - * @param args - n/a + * @param _args - n/a */ - override applyPreSetStatus(pokemon: Pokemon, passive: boolean, simulated: boolean, effect: StatusEffect, cancelled: BooleanHolder, args: any[]): void { + override applyPreSetStatus( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + effect: StatusEffect, + cancelled: BooleanHolder, + _args: any[], + ): void { cancelled.value = true; this.lastEffect = effect; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { - return this.immuneEffects.length ? - i18next.t("abilityTriggers:statusEffectImmunityWithName", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - abilityName, - statusEffectName: getStatusEffectDescriptor(this.lastEffect) - }) : - i18next.t("abilityTriggers:statusEffectImmunity", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - abilityName - }); + getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { + return this.immuneEffects.length + ? i18next.t("abilityTriggers:statusEffectImmunityWithName", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + abilityName, + statusEffectName: getStatusEffectDescriptor(this.lastEffect), + }) + : i18next.t("abilityTriggers:statusEffectImmunity", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + abilityName, + }); } } @@ -3242,13 +4326,13 @@ export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr { * Provides immunity to status effects to the user. * @extends PreSetStatusEffectImmunityAbAttr */ -export class StatusEffectImmunityAbAttr extends PreSetStatusEffectImmunityAbAttr { } +export class StatusEffectImmunityAbAttr extends PreSetStatusEffectImmunityAbAttr {} /** * Provides immunity to status effects to the user's field. * @extends PreSetStatusEffectImmunityAbAttr */ -export class UserFieldStatusEffectImmunityAbAttr extends PreSetStatusEffectImmunityAbAttr { } +export class UserFieldStatusEffectImmunityAbAttr extends PreSetStatusEffectImmunityAbAttr {} /** * Conditionally provides immunity to status effects to the user's field. @@ -3267,16 +4351,27 @@ export class ConditionalUserFieldStatusEffectImmunityAbAttr extends UserFieldSta /** * Evaluate the condition to determine if the {@linkcode ConditionalUserFieldStatusEffectImmunityAbAttr} can be applied. - * @param pokemon The pokemon with the ability - * @param passive unused - * @param simulated Whether the ability is being simulated + * @param _pokemon The pokemon with the ability + * @param _passive unused + * @param _simulated Whether the ability is being simulated * @param effect The status effect being applied * @param cancelled Holds whether the status effect was cancelled by a prior effect * @param args `Args[0]` is the target of the status effect, `Args[1]` is the source. * @returns Whether the ability can be applied to cancel the status effect. */ - override canApplyPreSetStatus(pokemon: Pokemon, passive: boolean, simulated: boolean, effect: StatusEffect, cancelled: BooleanHolder, args: [Pokemon, Pokemon | null, ...any]): boolean { - return (!cancelled.value && effect !== StatusEffect.FAINT && this.immuneEffects.length < 1 || this.immuneEffects.includes(effect)) && this.condition(args[0], args[1]); + override canApplyPreSetStatus( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + effect: StatusEffect, + cancelled: BooleanHolder, + args: [Pokemon, Pokemon | null, ...any], + ): boolean { + return ( + ((!cancelled.value && effect !== StatusEffect.FAINT && this.immuneEffects.length < 1) || + this.immuneEffects.includes(effect)) && + this.condition(args[0], args[1]) + ); } constructor(condition: (target: Pokemon, source: Pokemon | null) => boolean, ...immuneEffects: StatusEffect[]) { @@ -3298,27 +4393,38 @@ export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbA /** If the method evaluates to true, the stat will be protected. */ protected condition: (target: Pokemon) => boolean; - constructor(condition: (target: Pokemon) => boolean, protectedStat?: BattleStat) { + constructor(condition: (target: Pokemon) => boolean, _protectedStat?: BattleStat) { super(); this.condition = condition; } /** * Determine whether the {@linkcode ConditionalUserFieldProtectStatAbAttr} can be applied. - * @param pokemon The pokemon with the ability - * @param passive unused - * @param simulated Unused + * @param _pokemon The pokemon with the ability + * @param _passive unused + * @param _simulated Unused * @param stat The stat being affected * @param cancelled Holds whether the stat change was already prevented. * @param args Args[0] is the target pokemon of the stat change. * @returns */ - override canApplyPreStatStageChange(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: BooleanHolder, args: [Pokemon, ...any]): boolean { + override canApplyPreStatStageChange( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + stat: BattleStat, + cancelled: BooleanHolder, + args: [Pokemon, ...any], + ): boolean { const target = args[0]; if (!target) { return false; } - return !cancelled.value && (isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) && this.condition(target); + return ( + !cancelled.value && + (isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) && + this.condition(target) + ); } /** @@ -3330,31 +4436,37 @@ export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbA * @param cancelled Will be set to true if the stat change is prevented * @param _args unused */ - override applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _stat: BattleStat, cancelled: BooleanHolder, _args: any[]): void { + override applyPreStatStageChange( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _stat: BattleStat, + cancelled: BooleanHolder, + _args: any[], + ): void { cancelled.value = true; } } - export class PreApplyBattlerTagAbAttr extends AbAttr { canApplyPreApplyBattlerTag( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - tag: BattlerTag, - cancelled: BooleanHolder, - args: any[], + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _tag: BattlerTag, + _cancelled: BooleanHolder, + _args: any[], ): boolean { return true; } applyPreApplyBattlerTag( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - tag: BattlerTag, - cancelled: BooleanHolder, - args: any[], + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _tag: BattlerTag, + _cancelled: BooleanHolder, + _args: any[], ): void {} } @@ -3368,24 +4480,38 @@ export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr { constructor(immuneTagTypes: BattlerTagType | BattlerTagType[]) { super(true); - this.immuneTagTypes = Array.isArray(immuneTagTypes) ? immuneTagTypes : [ immuneTagTypes ]; + this.immuneTagTypes = Array.isArray(immuneTagTypes) ? immuneTagTypes : [immuneTagTypes]; } - override canApplyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, simulated: boolean, tag: BattlerTag, cancelled: BooleanHolder, args: any[]): boolean { + override canApplyPreApplyBattlerTag( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + tag: BattlerTag, + cancelled: BooleanHolder, + _args: any[], + ): boolean { this.battlerTag = tag; return !cancelled.value && this.immuneTagTypes.includes(tag.tagType); } - override applyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, simulated: boolean, tag: BattlerTag, cancelled: BooleanHolder, args: any[]): void { + override applyPreApplyBattlerTag( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _tag: BattlerTag, + cancelled: BooleanHolder, + _args: any[], + ): void { cancelled.value = true; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:battlerTagImmunity", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, - battlerTagName: this.battlerTag.getDescriptor() + battlerTagName: this.battlerTag.getDescriptor(), }); } } @@ -3394,13 +4520,13 @@ export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr { * Provides immunity to BattlerTags {@linkcode BattlerTag} to the user. * @extends PreApplyBattlerTagImmunityAbAttr */ -export class BattlerTagImmunityAbAttr extends PreApplyBattlerTagImmunityAbAttr { } +export class BattlerTagImmunityAbAttr extends PreApplyBattlerTagImmunityAbAttr {} /** * Provides immunity to BattlerTags {@linkcode BattlerTag} to the user's field. * @extends PreApplyBattlerTagImmunityAbAttr */ -export class UserFieldBattlerTagImmunityAbAttr extends PreApplyBattlerTagImmunityAbAttr { } +export class UserFieldBattlerTagImmunityAbAttr extends PreApplyBattlerTagImmunityAbAttr {} export class ConditionalUserFieldBattlerTagImmunityAbAttr extends UserFieldBattlerTagImmunityAbAttr { private condition: (target: Pokemon) => boolean; @@ -3415,8 +4541,17 @@ export class ConditionalUserFieldBattlerTagImmunityAbAttr extends UserFieldBattl * @param args Args[0] is the target that the tag is attempting to be applied to * @returns Whether the ability can be used to cancel the battler tag */ - override canApplyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, simulated: boolean, tag: BattlerTag, cancelled: BooleanHolder, args: [Pokemon, ...any]): boolean { - return super.canApplyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args) && this.condition(args[0]); + override canApplyPreApplyBattlerTag( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + tag: BattlerTag, + cancelled: BooleanHolder, + args: [Pokemon, ...any], + ): boolean { + return ( + super.canApplyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args) && this.condition(args[0]) + ); } constructor(condition: (target: Pokemon) => boolean, immuneTagTypes: BattlerTagType | BattlerTagType[]) { @@ -3435,8 +4570,14 @@ export class BlockCritAbAttr extends AbAttr { * Apply the block crit ability by setting the value in the provided boolean holder to false * @param args - [0] is a boolean holder representing whether the attack can crit */ - override apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder, args: [BooleanHolder, ...any]): void { - (args[0]).value = false; + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: [BooleanHolder, ...any], + ): void { + args[0].value = false; } } @@ -3448,13 +4589,19 @@ export class BonusCritAbAttr extends AbAttr { /** * Apply the bonus crit ability by increasing the value in the provided number holder by 1 * - * @param pokemon The pokemon with the BonusCrit ability (unused) - * @param passive Unused - * @param simulated Unused - * @param cancelled Unused + * @param _pokemon The pokemon with the BonusCrit ability (unused) + * @param _passive Unused + * @param _simulated Unused + * @param _cancelled Unused * @param args Args[0] is a number holder containing the crit stage. */ - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: [NumberHolder, ...any]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: [NumberHolder, ...any], + ): void { (args[0] as NumberHolder).value += 1; } } @@ -3468,12 +4615,18 @@ export class MultCritAbAttr extends AbAttr { this.multAmount = multAmount; } - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { const critMult = args[0] as NumberHolder; return critMult.value > 1; } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: any[], + ): void { const critMult = args[0] as NumberHolder; critMult.value *= this.multAmount; } @@ -3487,25 +4640,31 @@ export class MultCritAbAttr extends AbAttr { export class ConditionalCritAbAttr extends AbAttr { private condition: PokemonAttackCondition; - constructor(condition: PokemonAttackCondition, checkUser?: boolean) { + constructor(condition: PokemonAttackCondition, _checkUser?: boolean) { super(false); this.condition = condition; } - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const target = (args[1] as Pokemon); - const move = (args[2] as Move); + override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { + const target = args[1] as Pokemon; + const move = args[2] as Move; return this.condition(pokemon, target, move); } /** - * @param pokemon {@linkcode Pokemon} user. + * @param _pokemon {@linkcode Pokemon} user. * @param args [0] {@linkcode BooleanHolder} If true critical hit is guaranteed. * [1] {@linkcode Pokemon} Target. * [2] {@linkcode Move} used by ability user. */ - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: any[], + ): void { (args[0] as BooleanHolder).value = true; } } @@ -3515,7 +4674,13 @@ export class BlockNonDirectDamageAbAttr extends AbAttr { super(false); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + cancelled: BooleanHolder, + _args: any[], + ): void { cancelled.value = true; } } @@ -3535,23 +4700,35 @@ export class BlockStatusDamageAbAttr extends AbAttr { this.effects = effects; } - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return !!pokemon.status?.effect && this.effects.includes(pokemon.status.effect); } /** - * @param {Pokemon} pokemon The pokemon with the ability - * @param {boolean} passive N/A + * @param {Pokemon} _pokemon The pokemon with the ability + * @param {boolean} _passive N/A * @param {BooleanHolder} cancelled Whether to cancel the status damage - * @param {any[]} args N/A + * @param {any[]} _args N/A */ - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + cancelled: BooleanHolder, + _args: any[], + ): void { cancelled.value = true; } } export class BlockOneHitKOAbAttr extends AbAttr { - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + cancelled: BooleanHolder, + _args: any[], + ): void { cancelled.value = true; } } @@ -3576,39 +4753,46 @@ export class ChangeMovePriorityAbAttr extends AbAttr { this.changeAmount = changeAmount; } - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { return this.moveFunc(pokemon, args[0] as Move); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: any[], + ): void { (args[1] as NumberHolder).value += this.changeAmount; } } -export class IgnoreContactAbAttr extends AbAttr { } +export class IgnoreContactAbAttr extends AbAttr {} export class PreWeatherEffectAbAttr extends AbAttr { canApplyPreWeatherEffect( - pokemon: Pokemon, - passive: Boolean, - simulated: boolean, - weather: Weather | null, - cancelled: BooleanHolder, - args: any[]): boolean { + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _weather: Weather | null, + _cancelled: BooleanHolder, + _args: any[], + ): boolean { return true; } applyPreWeatherEffect( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - weather: Weather | null, - cancelled: BooleanHolder, - args: any[], + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _weather: Weather | null, + _cancelled: BooleanHolder, + _args: any[], ): void {} } -export class PreWeatherDamageAbAttr extends PreWeatherEffectAbAttr { } +export class PreWeatherDamageAbAttr extends PreWeatherEffectAbAttr {} export class BlockWeatherDamageAttr extends PreWeatherDamageAbAttr { private weatherTypes: WeatherType[]; @@ -3619,11 +4803,25 @@ export class BlockWeatherDamageAttr extends PreWeatherDamageAbAttr { this.weatherTypes = weatherTypes; } - override canApplyPreWeatherEffect(pokemon: Pokemon, passive: Boolean, simulated: boolean, weather: Weather, cancelled: BooleanHolder, args: any[]): boolean { + override canApplyPreWeatherEffect( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + weather: Weather, + _cancelled: BooleanHolder, + _args: any[], + ): boolean { return !this.weatherTypes.length || this.weatherTypes.indexOf(weather?.weatherType) > -1; } - override applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, cancelled: BooleanHolder, args: any[]): void { + override applyPreWeatherEffect( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _weather: Weather, + cancelled: BooleanHolder, + _args: any[], + ): void { cancelled.value = true; } } @@ -3637,11 +4835,25 @@ export class SuppressWeatherEffectAbAttr extends PreWeatherEffectAbAttr { this.affectsImmutable = !!affectsImmutable; } - override canApplyPreWeatherEffect(pokemon: Pokemon, passive: Boolean, simulated: boolean, weather: Weather, cancelled: BooleanHolder, args: any[]): boolean { + override canApplyPreWeatherEffect( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + weather: Weather, + _cancelled: BooleanHolder, + _args: any[], + ): boolean { return this.affectsImmutable || weather.isImmutable(); } - override applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, cancelled: BooleanHolder, args: any[]): void { + override applyPreWeatherEffect( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _weather: Weather, + cancelled: BooleanHolder, + _args: any[], + ): void { cancelled.value = true; } } @@ -3665,7 +4877,8 @@ function getSheerForceHitDisableAbCondition(): AbAttrCondition { } /** `true` if the last move's chance is above 0 and the last attacker's ability is sheer force */ - const SheerForceAffected = allMoves[lastReceivedAttack.move].chance >= 0 && lastAttacker.hasAbility(AbilityId.SHEER_FORCE); + const SheerForceAffected = + allMoves[lastReceivedAttack.move].chance >= 0 && lastAttacker.hasAbility(AbilityId.SHEER_FORCE); return !SheerForceAffected; }; @@ -3693,7 +4906,10 @@ function getAnticipationCondition(): AbAttrCondition { continue; } // the move's base type (not accounting for variable type changes) is super effective - if (move.getMove() instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.getMove().type, opponent, true, undefined, move.getMove()) >= 2) { + if ( + move.getMove() instanceof AttackMove && + pokemon.getAttackTypeEffectiveness(move.getMove().type, opponent, true, undefined, move.getMove()) >= 2 + ) { return true; } // move is a OHKO @@ -3702,18 +4918,35 @@ function getAnticipationCondition(): AbAttrCondition { } // edge case for hidden power, type is computed if (move.getMove().id === MoveId.HIDDEN_POWER) { - const iv_val = Math.floor(((opponent.ivs[Stat.HP] & 1) - + (opponent.ivs[Stat.ATK] & 1) * 2 - + (opponent.ivs[Stat.DEF] & 1) * 4 - + (opponent.ivs[Stat.SPD] & 1) * 8 - + (opponent.ivs[Stat.SPATK] & 1) * 16 - + (opponent.ivs[Stat.SPDEF] & 1) * 32) * 15 / 63); + const iv_val = Math.floor( + (((opponent.ivs[Stat.HP] & 1) + + (opponent.ivs[Stat.ATK] & 1) * 2 + + (opponent.ivs[Stat.DEF] & 1) * 4 + + (opponent.ivs[Stat.SPD] & 1) * 8 + + (opponent.ivs[Stat.SPATK] & 1) * 16 + + (opponent.ivs[Stat.SPDEF] & 1) * 32) * + 15) / + 63, + ); const type = [ - PokemonType.FIGHTING, PokemonType.FLYING, PokemonType.POISON, PokemonType.GROUND, - PokemonType.ROCK, PokemonType.BUG, PokemonType.GHOST, PokemonType.STEEL, - PokemonType.FIRE, PokemonType.WATER, PokemonType.GRASS, PokemonType.ELECTRIC, - PokemonType.PSYCHIC, PokemonType.ICE, PokemonType.DRAGON, PokemonType.DARK ][iv_val]; + PokemonType.FIGHTING, + PokemonType.FLYING, + PokemonType.POISON, + PokemonType.GROUND, + PokemonType.ROCK, + PokemonType.BUG, + PokemonType.GHOST, + PokemonType.STEEL, + PokemonType.FIRE, + PokemonType.WATER, + PokemonType.GRASS, + PokemonType.ELECTRIC, + PokemonType.PSYCHIC, + PokemonType.ICE, + PokemonType.DRAGON, + PokemonType.DARK, + ][iv_val]; if (pokemon.getAttackTypeEffectiveness(type, opponent) >= 2) { return true; @@ -3743,7 +4976,7 @@ export class ForewarnAbAttr extends PostSummonAbAttr { super(true); } - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { let maxPowerSeen = 0; let maxMove = ""; let movePower = 0; @@ -3753,7 +4986,11 @@ export class ForewarnAbAttr extends PostSummonAbAttr { movePower = 1; } else if (move?.getMove().hasAttr(OneHitKOAttr)) { movePower = 150; - } else if (move?.getMove().id === MoveId.COUNTER || move?.getMove().id === MoveId.MIRROR_COAT || move?.getMove().id === MoveId.METAL_BURST) { + } else if ( + move?.getMove().id === MoveId.COUNTER || + move?.getMove().id === MoveId.MIRROR_COAT || + move?.getMove().id === MoveId.METAL_BURST + ) { movePower = 120; } else if (move?.getMove().power === -1) { movePower = 80; @@ -3768,7 +5005,12 @@ export class ForewarnAbAttr extends PostSummonAbAttr { } } if (!simulated) { - globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:forewarn", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: maxMove })); + globalScene.phaseManager.queueMessage( + i18next.t("abilityTriggers:forewarn", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + moveName: maxMove, + }), + ); } } } @@ -3778,10 +5020,16 @@ export class FriskAbAttr extends PostSummonAbAttr { super(true); } - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { for (const opponent of pokemon.getOpponents()) { - globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:frisk", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), opponentName: opponent.name, opponentAbilityName: opponent.getAbility().name })); + globalScene.phaseManager.queueMessage( + i18next.t("abilityTriggers:frisk", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + opponentName: opponent.name, + opponentAbilityName: opponent.getAbility().name, + }), + ); setAbilityRevealed(opponent); } } @@ -3789,11 +5037,23 @@ export class FriskAbAttr extends PostSummonAbAttr { } export class PostWeatherChangeAbAttr extends AbAttr { - canApplyPostWeatherChange(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: WeatherType, args: any[]): boolean { + canApplyPostWeatherChange( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _weather: WeatherType, + _args: any[], + ): boolean { return true; } - applyPostWeatherChange(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: WeatherType, args: any[]): void {} + applyPostWeatherChange( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _weather: WeatherType, + _args: any[], + ): void {} } /** @@ -3812,9 +5072,17 @@ export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr { this.formRevertingWeathers = formRevertingWeathers; } - override canApplyPostWeatherChange(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: WeatherType, args: any[]): boolean { - const isCastformWithForecast = (pokemon.species.speciesId === SpeciesId.CASTFORM && this.ability === AbilityId.FORECAST); - const isCherrimWithFlowerGift = (pokemon.species.speciesId === SpeciesId.CHERRIM && this.ability === AbilityId.FLOWER_GIFT); + override canApplyPostWeatherChange( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _weather: WeatherType, + _args: any[], + ): boolean { + const isCastformWithForecast = + pokemon.species.speciesId === SpeciesId.CASTFORM && this.ability === AbilityId.FORECAST; + const isCherrimWithFlowerGift = + pokemon.species.speciesId === SpeciesId.CHERRIM && this.ability === AbilityId.FLOWER_GIFT; return isCastformWithForecast || isCherrimWithFlowerGift; } @@ -3822,12 +5090,18 @@ export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr { /** * Calls {@linkcode Arena.triggerWeatherBasedFormChangesToNormal | triggerWeatherBasedFormChangesToNormal} when the * weather changed to form-reverting weather, otherwise calls {@linkcode Arena.triggerWeatherBasedFormChanges | triggerWeatherBasedFormChanges} - * @param {Pokemon} pokemon the Pokemon with this ability - * @param passive n/a - * @param weather n/a - * @param args n/a + * @param {Pokemon} _pokemon the Pokemon with this ability + * @param _passive n/a + * @param _weather n/a + * @param _args n/a */ - override applyPostWeatherChange(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: WeatherType, args: any[]): void { + override applyPostWeatherChange( + _pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + _weather: WeatherType, + _args: any[], + ): void { if (simulated) { return; } @@ -3855,11 +5129,23 @@ export class PostWeatherChangeAddBattlerTagAttr extends PostWeatherChangeAbAttr this.weatherTypes = weatherTypes; } - override canApplyPostWeatherChange(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: WeatherType, args: any[]): boolean { + override canApplyPostWeatherChange( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + weather: WeatherType, + _args: any[], + ): boolean { return !!this.weatherTypes.find(w => weather === w) && pokemon.canAddTag(this.tagType); } - override applyPostWeatherChange(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: WeatherType, args: any[]): void { + override applyPostWeatherChange( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + _weather: WeatherType, + _args: any[], + ): void { if (!simulated) { pokemon.addTag(this.tagType, this.turnCount); } @@ -3876,20 +5162,21 @@ export class PostWeatherLapseAbAttr extends AbAttr { } canApplyPostWeatherLapse( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - weather: Weather | null, - args: any[]): boolean { + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _weather: Weather | null, + _args: any[], + ): boolean { return true; } applyPostWeatherLapse( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - weather: Weather | null, - args: any[], + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _weather: Weather | null, + _args: any[], ): void {} getCondition(): AbAttrCondition { @@ -3906,15 +5193,36 @@ export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr { this.healFactor = healFactor; } - override canApplyPostWeatherLapse(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather | null, args: any[]): boolean { + override canApplyPostWeatherLapse( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _weather: Weather | null, + _args: any[], + ): boolean { return !pokemon.isFullHp(); } - override applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, args: any[]): void { + override applyPostWeatherLapse( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + _weather: Weather, + _args: any[], + ): void { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; if (!simulated) { - globalScene.phaseManager.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / (16 / this.healFactor)), i18next.t("abilityTriggers:postWeatherLapseHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); + globalScene.phaseManager.unshiftPhase( + new PokemonHealPhase( + pokemon.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / (16 / this.healFactor)), + i18next.t("abilityTriggers:postWeatherLapseHeal", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + abilityName, + }), + true, + ), + ); } } } @@ -3928,25 +5236,56 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr { this.damageFactor = damageFactor; } - override canApplyPostWeatherLapse(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather | null, args: any[]): boolean { + override canApplyPostWeatherLapse( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _weather: Weather | null, + _args: any[], + ): boolean { return !pokemon.hasAbilityWithAttr(BlockNonDirectDamageAbAttr); } - override applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, args: any[]): void { + override applyPostWeatherLapse( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + _weather: Weather, + _args: any[], + ): void { if (!simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:postWeatherLapseDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName })); - pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / (16 / this.damageFactor)), { result: HitResult.INDIRECT }); + globalScene.phaseManager.queueMessage( + i18next.t("abilityTriggers:postWeatherLapseDamage", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + abilityName, + }), + ); + pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / (16 / this.damageFactor)), { + result: HitResult.INDIRECT, + }); } } } export class PostTerrainChangeAbAttr extends AbAttr { - canApplyPostTerrainChange(pokemon: Pokemon, passive: boolean, simulated: boolean, terrain: TerrainType, args: any[]): boolean { + canApplyPostTerrainChange( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _terrain: TerrainType, + _args: any[], + ): boolean { return true; } - applyPostTerrainChange(pokemon: Pokemon, passive: boolean, simulated: boolean, terrain: TerrainType, args: any[]): void {} + applyPostTerrainChange( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _terrain: TerrainType, + _args: any[], + ): void {} } export class PostTerrainChangeAddBattlerTagAttr extends PostTerrainChangeAbAttr { @@ -3962,11 +5301,23 @@ export class PostTerrainChangeAddBattlerTagAttr extends PostTerrainChangeAbAttr this.terrainTypes = terrainTypes; } - override canApplyPostTerrainChange(pokemon: Pokemon, passive: boolean, simulated: boolean, terrain: TerrainType, args: any[]): boolean { + override canApplyPostTerrainChange( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + terrain: TerrainType, + _args: any[], + ): boolean { return !!this.terrainTypes.find(t => t === terrain) && pokemon.canAddTag(this.tagType); } - override applyPostTerrainChange(pokemon: Pokemon, passive: boolean, simulated: boolean, terrain: TerrainType, args: any[]): void { + override applyPostTerrainChange( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + _terrain: TerrainType, + _args: any[], + ): void { if (!simulated) { pokemon.addTag(this.tagType, this.turnCount); } @@ -3974,18 +5325,18 @@ export class PostTerrainChangeAddBattlerTagAttr extends PostTerrainChangeAbAttr } function getTerrainCondition(...terrainTypes: TerrainType[]): AbAttrCondition { - return (pokemon: Pokemon) => { + return (_pokemon: Pokemon) => { const terrainType = globalScene.arena.terrain?.terrainType; return !!terrainType && terrainTypes.indexOf(terrainType) > -1; }; } export class PostTurnAbAttr extends AbAttr { - canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + canApplyPostTurn(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return true; } - applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {} + applyPostTurn(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {} } /** @@ -4003,20 +5354,26 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr { this.effects = effects; } - override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return !isNullOrUndefined(pokemon.status) && this.effects.includes(pokemon.status.effect) && !pokemon.isFullHp(); } /** * @param {Pokemon} pokemon The pokemon with the ability that will receive the healing * @param {Boolean} passive N/A - * @param {any[]} args N/A + * @param {any[]} _args N/A */ - override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - globalScene.phaseManager.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / 8), i18next.t("abilityTriggers:poisonHeal", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName }), true)); + globalScene.phaseManager.unshiftPhase( + new PokemonHealPhase( + pokemon.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / 8), + i18next.t("abilityTriggers:poisonHeal", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName }), + true, + ), + ); } } } @@ -4034,7 +5391,7 @@ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr { this.allyTarget = allyTarget; } - override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { if (this.allyTarget) { this.target = pokemon.getAlly(); } else { @@ -4045,9 +5402,11 @@ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr { return !!effect && effect !== StatusEffect.FAINT; } - override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostTurn(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated && this.target?.status) { - globalScene.phaseManager.queueMessage(getStatusEffectHealText(this.target.status?.effect, getPokemonNameWithAffix(this.target))); + globalScene.phaseManager.queueMessage( + getStatusEffectHealText(this.target.status?.effect, getPokemonNameWithAffix(this.target)), + ); this.target.resetStatus(false); this.target.updateInfo(); } @@ -4063,29 +5422,26 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr { * Array containing all {@linkcode BerryType | BerryTypes} that are under cap and able to be restored. * Stored inside the class for a minor performance boost */ - private berriesUnderCap: BerryType[] + private berriesUnderCap: BerryType[]; /** * @param procChance - function providing chance to restore an item * @see {@linkcode createEatenBerry()} */ - constructor( - private procChance: (pokemon: Pokemon) => number - ) { + constructor(private procChance: (pokemon: Pokemon) => number) { super(); } override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { // Ensure we have at least 1 recoverable berry (at least 1 berry in berriesEaten is not capped) const cappedBerries = new Set( - globalScene.getModifiers(BerryModifier, pokemon.isPlayer()).filter( - bm => bm.pokemonId === pokemon.id && bm.getCountUnderMax() < 1 - ).map(bm => bm.berryType) + globalScene + .getModifiers(BerryModifier, pokemon.isPlayer()) + .filter(bm => bm.pokemonId === pokemon.id && bm.getCountUnderMax() < 1) + .map(bm => bm.berryType), ); - this.berriesUnderCap = pokemon.battleData.berriesEaten.filter( - bt => !cappedBerries.has(bt) - ); + this.berriesUnderCap = pokemon.battleData.berriesEaten.filter(bt => !cappedBerries.has(bt)); if (!this.berriesUnderCap.length) { return false; @@ -4096,7 +5452,7 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr { return this.procChance(pokemon) >= pass; } - override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostTurn(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { this.createEatenBerry(pokemon); } @@ -4116,12 +5472,12 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr { // Add the randomly chosen berry or update the existing one const berryModifier = globalScene.findModifier( - (m) => m instanceof BerryModifier && m.berryType === chosenBerryType && m.pokemonId == pokemon.id, - pokemon.isPlayer() + m => m instanceof BerryModifier && m.berryType === chosenBerryType && m.pokemonId === pokemon.id, + pokemon.isPlayer(), ) as BerryModifier | undefined; if (berryModifier) { - berryModifier.stackCount++ + berryModifier.stackCount++; } else { const newBerry = new BerryModifier(chosenBerry, pokemon.id, chosenBerryType, 1); if (pokemon.isPlayer()) { @@ -4132,15 +5488,20 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr { } globalScene.updateModifiers(pokemon.isPlayer()); - globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:postTurnLootCreateEatenBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: chosenBerry.name })); + globalScene.phaseManager.queueMessage( + i18next.t("abilityTriggers:postTurnLootCreateEatenBerry", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + berryName: chosenBerry.name, + }), + ); return true; } } /** - * Attribute to track and re-trigger last turn's berries at the end of the `BerryPhase`. - * Used by {@linkcode AbilityId.CUD_CHEW}. -*/ + * Attribute to track and re-trigger last turn's berries at the end of the `BerryPhase`. + * Used by {@linkcode AbilityId.CUD_CHEW}. + */ export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr { /** * @returns `true` if the pokemon ate anything last turn @@ -4161,7 +5522,13 @@ export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr { * @param _cancelled - N/A * @param _args - N/A */ - override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder | null, _args: any[]): void { + override apply( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder | null, + _args: any[], + ): void { globalScene.phaseManager.unshiftPhase( new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM), ); @@ -4181,7 +5548,7 @@ export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr { /** * @returns always `true` as we always want to move berries into summon data */ - override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApplyPostTurn(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { this.showAbility = false; // don't show popup for turn end berry moving (should ideally be hidden) return true; } @@ -4210,15 +5577,15 @@ export class MoodyAbAttr extends PostTurnAbAttr { /** * Randomly increases one stat stage by 2 and decreases a different stat stage by 1 * @param {Pokemon} pokemon Pokemon that has this ability - * @param passive N/A + * @param _passive N/A * @param simulated true if applying in a simulated call. - * @param args N/A + * @param _args N/A * * Any stat stages at +6 or -6 are excluded from being increased or decreased, respectively * If the pokemon already has all stat stages raised to 6, it will only decrease one stat stage by 1 * If the pokemon already has all stat stages lowered to -6, it will only increase one stat stage by 2 */ - override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostTurn(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { const canRaise = EFFECTIVE_STATS.filter(s => pokemon.getStatStage(s) < 6); let canLower = EFFECTIVE_STATS.filter(s => pokemon.getStatStage(s) > -6); @@ -4226,41 +5593,53 @@ export class MoodyAbAttr extends PostTurnAbAttr { if (canRaise.length > 0) { const raisedStat = canRaise[pokemon.randBattleSeedInt(canRaise.length)]; canLower = canRaise.filter(s => s !== raisedStat); - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ raisedStat ], 2)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase(pokemon.getBattlerIndex(), true, [raisedStat], 2), + ); } if (canLower.length > 0) { const loweredStat = canLower[pokemon.randBattleSeedInt(canLower.length)]; - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ loweredStat ], -1)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase(pokemon.getBattlerIndex(), true, [loweredStat], -1), + ); } } } } export class SpeedBoostAbAttr extends PostTurnAbAttr { - constructor() { super(true); } - override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): boolean { return simulated || (!pokemon.turnData.switchedInThisTurn && !pokemon.turnData.failedRunAway); } - override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.SPD ], 1)); + override applyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { + globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.SPD], 1)); } } export class PostTurnHealAbAttr extends PostTurnAbAttr { - override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return !pokemon.isFullHp(); } - override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - globalScene.phaseManager.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / 16), i18next.t("abilityTriggers:postTurnHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); + globalScene.phaseManager.unshiftPhase( + new PokemonHealPhase( + pokemon.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / 16), + i18next.t("abilityTriggers:postTurnHeal", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + abilityName, + }), + true, + ), + ); } } } @@ -4268,79 +5647,91 @@ export class PostTurnHealAbAttr extends PostTurnAbAttr { export class PostTurnFormChangeAbAttr extends PostTurnAbAttr { private formFunc: (p: Pokemon) => number; - constructor(formFunc: ((p: Pokemon) => number)) { + constructor(formFunc: (p: Pokemon) => number) { super(true); this.formFunc = formFunc; } - override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return this.formFunc(pokemon) !== pokemon.formIndex; } - override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostTurn(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); } } } - /** * Attribute used for abilities (Bad Dreams) that damages the opponents for being asleep */ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr { - override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return pokemon.getOpponents().some(opp => (opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(AbilityId.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus); + override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + return pokemon + .getOpponents() + .some( + opp => + (opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(AbilityId.COMATOSE)) && + !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && + !opp.switchOutStatus, + ); } /** * Deals damage to all sleeping opponents equal to 1/8 of their max hp (min 1) * @param pokemon {@linkcode Pokemon} with this ability - * @param passive N/A + * @param _passive N/A * @param simulated `true` if applying in a simulated call. - * @param args N/A + * @param _args N/A */ - override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostTurn(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { for (const opp of pokemon.getOpponents()) { - if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(AbilityId.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus) { + if ( + (opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(AbilityId.COMATOSE)) && + !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && + !opp.switchOutStatus + ) { if (!simulated) { opp.damageAndUpdate(toDmgValue(opp.getMaxHp() / 8), { result: HitResult.INDIRECT }); - globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) })); + globalScene.phaseManager.queueMessage( + i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) }), + ); } } } } } - /** * Grabs the last failed Pokeball used * @extends PostTurnAbAttr * @see {@linkcode applyPostTurn} */ export class FetchBallAbAttr extends PostTurnAbAttr { - constructor() { - super(); - } - - override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): boolean { return !simulated && !isNullOrUndefined(globalScene.currentBattle.lastUsedPokeball) && !!pokemon.isPlayer; } /** * Adds the last used Pokeball back into the player's inventory * @param pokemon {@linkcode Pokemon} with this ability - * @param passive N/A - * @param args N/A + * @param _passive N/A + * @param _args N/A */ - override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { const lastUsed = globalScene.currentBattle.lastUsedPokeball; globalScene.pokeballCounts[lastUsed!]++; globalScene.currentBattle.lastUsedPokeball = null; - globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:fetchBall", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokeballName: getPokeballName(lastUsed!) })); + globalScene.phaseManager.queueMessage( + i18next.t("abilityTriggers:fetchBall", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + pokeballName: getPokeballName(lastUsed!), + }), + ); } } -export class PostBiomeChangeAbAttr extends AbAttr { } +export class PostBiomeChangeAbAttr extends AbAttr {} export class PostBiomeChangeWeatherChangeAbAttr extends PostBiomeChangeAbAttr { private weatherType: WeatherType; @@ -4351,11 +5742,17 @@ export class PostBiomeChangeWeatherChangeAbAttr extends PostBiomeChangeAbAttr { this.weatherType = weatherType; } - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return ((globalScene.arena.weather?.isImmutable() ?? false) && globalScene.arena.canSetWeather(this.weatherType)); + override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + return (globalScene.arena.weather?.isImmutable() ?? false) && globalScene.arena.canSetWeather(this.weatherType); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + _cancelled: BooleanHolder, + _args: any[], + ): void { if (!simulated) { globalScene.arena.trySetWeather(this.weatherType, pokemon); } @@ -4371,11 +5768,17 @@ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr { this.terrainType = terrainType; } - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return globalScene.arena.canSetTerrain(this.terrainType); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + _cancelled: BooleanHolder, + _args: any[], + ): void { if (!simulated) { globalScene.arena.trySetTerrain(this.terrainType, false, pokemon); } @@ -4388,22 +5791,23 @@ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr { */ export class PostMoveUsedAbAttr extends AbAttr { canApplyPostMoveUsed( - pokemon: Pokemon, - move: PokemonMove, - source: Pokemon, - targets: BattlerIndex[], - simulated: boolean, - args: any[]): boolean { + _pokemon: Pokemon, + _move: PokemonMove, + _source: Pokemon, + _targets: BattlerIndex[], + _simulated: boolean, + _args: any[], + ): boolean { return true; } applyPostMoveUsed( - pokemon: Pokemon, - move: PokemonMove, - source: Pokemon, - targets: BattlerIndex[], - simulated: boolean, - args: any[], + _pokemon: Pokemon, + _move: PokemonMove, + _source: Pokemon, + _targets: BattlerIndex[], + _simulated: boolean, + _args: any[], ): void {} } @@ -4412,13 +5816,26 @@ export class PostMoveUsedAbAttr extends AbAttr { * @extends PostMoveUsedAbAttr */ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { - override canApplyPostMoveUsed(dancer: Pokemon, move: PokemonMove, source: Pokemon, targets: BattlerIndex[], simulated: boolean, args: any[]): boolean { + override canApplyPostMoveUsed( + dancer: Pokemon, + _move: PokemonMove, + source: Pokemon, + _targets: BattlerIndex[], + _simulated: boolean, + _args: any[], + ): boolean { // List of tags that prevent the Dancer from replicating the move - const forbiddenTags = [ BattlerTagType.FLYING, BattlerTagType.UNDERWATER, - BattlerTagType.UNDERGROUND, BattlerTagType.HIDDEN ]; + const forbiddenTags = [ + BattlerTagType.FLYING, + BattlerTagType.UNDERWATER, + BattlerTagType.UNDERGROUND, + BattlerTagType.HIDDEN, + ]; // The move to replicate cannot come from the Dancer - return source.getBattlerIndex() !== dancer.getBattlerIndex() - && !dancer.summonData.tags.some(tag => forbiddenTags.includes(tag.tagType)); + return ( + source.getBattlerIndex() !== dancer.getBattlerIndex() && + !dancer.summonData.tags.some(tag => forbiddenTags.includes(tag.tagType)) + ); } /** @@ -4428,7 +5845,7 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { * @param move {@linkcode PokemonMove} Dancing move used by the source * @param source {@linkcode Pokemon} that used the dancing move * @param targets {@linkcode BattlerIndex}Targets of the dancing move - * @param args N/A + * @param _args N/A */ override applyPostMoveUsed( dancer: Pokemon, @@ -4436,7 +5853,8 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { source: Pokemon, targets: BattlerIndex[], simulated: boolean, - args: any[]): void { + _args: any[], + ): void { if (!simulated) { dancer.turnData.extraTurns++; // If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance @@ -4445,7 +5863,7 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { globalScene.phaseManager.unshiftPhase(new MovePhase(dancer, target, move, true, true)); } else if (move.getMove() instanceof SelfStatusMove) { // If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself - globalScene.phaseManager.unshiftPhase(new MovePhase(dancer, [ dancer.getBattlerIndex() ], move, true, true)); + globalScene.phaseManager.unshiftPhase(new MovePhase(dancer, [dancer.getBattlerIndex()], move, true, true)); } } } @@ -4457,11 +5875,11 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { * @param source {@linkcode Pokemon} Source of the dancing move * @param targets {@linkcode BattlerIndex} Targets of the dancing move */ - getTarget(dancer: Pokemon, source: Pokemon, targets: BattlerIndex[]) : BattlerIndex[] { + getTarget(dancer: Pokemon, source: Pokemon, targets: BattlerIndex[]): BattlerIndex[] { if (dancer.isPlayer()) { - return source.isPlayer() ? targets : [ source.getBattlerIndex() ]; + return source.isPlayer() ? targets : [source.getBattlerIndex()]; } - return source.isPlayer() ? [ source.getBattlerIndex() ] : targets; + return source.isPlayer() ? [source.getBattlerIndex()] : targets; } } @@ -4470,11 +5888,11 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { * @extends AbAttr */ export class PostItemLostAbAttr extends AbAttr { - canApplyPostItemLost(pokemon: Pokemon, simulated: boolean, args: any[]): boolean { + canApplyPostItemLost(_pokemon: Pokemon, _simulated: boolean, _args: any[]): boolean { return true; } - applyPostItemLost(pokemon: Pokemon, simulated: boolean, args: any[]): void {} + applyPostItemLost(_pokemon: Pokemon, _simulated: boolean, _args: any[]): void {} } /** @@ -4488,16 +5906,16 @@ export class PostItemLostApplyBattlerTagAbAttr extends PostItemLostAbAttr { this.tagType = tagType; } - override canApplyPostItemLost(pokemon: Pokemon, simulated: boolean, args: any[]): boolean { + override canApplyPostItemLost(pokemon: Pokemon, simulated: boolean, _args: any[]): boolean { return !pokemon.getTag(this.tagType) && !simulated; } /** * Adds the last used Pokeball back into the player's inventory * @param pokemon {@linkcode Pokemon} with this ability - * @param args N/A + * @param _args N/A */ - override applyPostItemLost(pokemon: Pokemon, simulated: boolean, args: any[]): void { + override applyPostItemLost(pokemon: Pokemon, _simulated: boolean, _args: any[]): void { pokemon.addTag(this.tagType); } } @@ -4511,7 +5929,13 @@ export class StatStageChangeMultiplierAbAttr extends AbAttr { this.multiplier = multiplier; } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: any[], + ): void { (args[0] as NumberHolder).value *= this.multiplier; } } @@ -4519,13 +5943,23 @@ export class StatStageChangeMultiplierAbAttr extends AbAttr { export class StatStageChangeCopyAbAttr extends AbAttr { override apply( pokemon: Pokemon, - passive: boolean, + _passive: boolean, simulated: boolean, - cancelled: BooleanHolder, + _cancelled: BooleanHolder, args: any[], ): void { if (!simulated) { - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, (args[0] as BattleStat[]), (args[1] as number), true, false, false)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase( + pokemon.getBattlerIndex(), + true, + args[0] as BattleStat[], + args[1] as number, + true, + false, + false, + ), + ); } } } @@ -4535,7 +5969,13 @@ export class BypassBurnDamageReductionAbAttr extends AbAttr { super(false); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + cancelled: BooleanHolder, + _args: any[], + ): void { cancelled.value = true; } } @@ -4543,7 +5983,7 @@ export class BypassBurnDamageReductionAbAttr extends AbAttr { /** * Causes Pokemon to take reduced damage from the {@linkcode StatusEffect.BURN | Burn} status * @param multiplier Multiplied with the damage taken -*/ + */ export class ReduceBurnDamageAbAttr extends AbAttr { constructor(protected multiplier: number) { super(false); @@ -4551,18 +5991,30 @@ export class ReduceBurnDamageAbAttr extends AbAttr { /** * Applies the damage reduction - * @param pokemon N/A - * @param passive N/A - * @param cancelled N/A + * @param _pokemon N/A + * @param _passive N/A + * @param _cancelled N/A * @param args `[0]` {@linkcode NumberHolder} The damage value being modified */ - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: any[], + ): void { (args[0] as NumberHolder).value = toDmgValue((args[0] as NumberHolder).value * this.multiplier); } } export class DoubleBerryEffectAbAttr extends AbAttr { - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: any[], + ): void { (args[0] as NumberHolder).value *= 2; } } @@ -4600,7 +6052,7 @@ export class HealFromBerryUseAbAttr extends AbAttr { this.healPercent = Math.max(Math.min(healPercent, 1), 0); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, ...args: [BooleanHolder, any[]]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, ..._args: [BooleanHolder, any[]]): void { if (simulated) { return; } @@ -4610,15 +6062,24 @@ export class HealFromBerryUseAbAttr extends AbAttr { new PokemonHealPhase( pokemon.getBattlerIndex(), toDmgValue(pokemon.getMaxHp() * this.healPercent), - i18next.t("abilityTriggers:healFromBerryUse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), - true - ) - ); + i18next.t("abilityTriggers:healFromBerryUse", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + abilityName, + }), + true, + ), + ); } } export class RunSuccessAbAttr extends AbAttr { - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: any[], + ): void { (args[0] as NumberHolder).value = 256; } } @@ -4640,23 +6101,23 @@ export class CheckTrappedAbAttr extends AbAttr { } canApplyCheckTrapped( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - trapped: BooleanHolder, - otherPokemon: Pokemon, - args: any[], + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _trapped: BooleanHolder, + _otherPokemon: Pokemon, + _args: any[], ): boolean { return true; } applyCheckTrapped( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - trapped: BooleanHolder, - otherPokemon: Pokemon, - args: any[], + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _trapped: BooleanHolder, + _otherPokemon: Pokemon, + _args: any[], ): void {} } @@ -4667,10 +6128,23 @@ export class CheckTrappedAbAttr extends AbAttr { * @see {@linkcode applyCheckTrapped} */ export class ArenaTrapAbAttr extends CheckTrappedAbAttr { - override canApplyCheckTrapped(pokemon: Pokemon, passive: boolean, simulated: boolean, trapped: BooleanHolder, otherPokemon: Pokemon, args: any[]): boolean { - return this.arenaTrapCondition(pokemon, otherPokemon) - && !(otherPokemon.getTypes(true).includes(PokemonType.GHOST) || (otherPokemon.getTypes(true).includes(PokemonType.STELLAR) && otherPokemon.getTypes().includes(PokemonType.GHOST))) - && !otherPokemon.hasAbility(AbilityId.RUN_AWAY); + override canApplyCheckTrapped( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _trapped: BooleanHolder, + otherPokemon: Pokemon, + _args: any[], + ): boolean { + return ( + this.arenaTrapCondition(pokemon, otherPokemon) && + !( + otherPokemon.getTypes(true).includes(PokemonType.GHOST) || + (otherPokemon.getTypes(true).includes(PokemonType.STELLAR) && + otherPokemon.getTypes().includes(PokemonType.GHOST)) + ) && + !otherPokemon.hasAbility(AbilityId.RUN_AWAY) + ); } /** @@ -4679,18 +6153,28 @@ export class ArenaTrapAbAttr extends CheckTrappedAbAttr { * If the enemy has the ability Run Away, it is not trapped. * If the user has Magnet Pull and the enemy is not a Steel type, it is not trapped. * If the user has Arena Trap and the enemy is not grounded, it is not trapped. - * @param pokemon The {@link Pokemon} with this {@link AbAttr} - * @param passive N/A + * @param _pokemon The {@link Pokemon} with this {@link AbAttr} + * @param _passive N/A * @param trapped {@link BooleanHolder} indicating whether the other Pokemon is trapped or not - * @param otherPokemon The {@link Pokemon} that is affected by an Arena Trap ability - * @param args N/A + * @param _otherPokemon The {@link Pokemon} that is affected by an Arena Trap ability + * @param _args N/A */ - override applyCheckTrapped(pokemon: Pokemon, passive: boolean, simulated: boolean, trapped: BooleanHolder, otherPokemon: Pokemon, args: any[]): void { + override applyCheckTrapped( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + trapped: BooleanHolder, + _otherPokemon: Pokemon, + _args: any[], + ): void { trapped.value = true; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { - return i18next.t("abilityTriggers:arenaTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }); + getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { + return i18next.t("abilityTriggers:arenaTrap", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + abilityName, + }); } } @@ -4699,27 +6183,33 @@ export class MaxMultiHitAbAttr extends AbAttr { super(false); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: any[], + ): void { (args[0] as NumberHolder).value = 0; } } export class PostBattleAbAttr extends AbAttr { - constructor(showAbility: boolean = true) { + constructor(showAbility = true) { super(showAbility); } - canApplyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + canApplyPostBattle(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return true; } - applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {} + applyPostBattle(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {} } export class PostBattleLootAbAttr extends PostBattleAbAttr { private randItem?: PokemonHeldItemModifier; - override canApplyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostBattle(pokemon: Pokemon, _passive: boolean, simulated: boolean, args: any[]): boolean { const postBattleLoot = globalScene.currentBattle.postBattleLoot; if (!simulated && postBattleLoot.length && args[0]) { this.randItem = randSeedItem(postBattleLoot); @@ -4729,9 +6219,9 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr { } /** - * @param args - `[0]`: boolean for if the battle ended in a victory + * @param _args - `[0]`: boolean for if the battle ended in a victory */ - override applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostBattle(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { const postBattleLoot = globalScene.currentBattle.postBattleLoot; if (!this.randItem) { this.randItem = randSeedItem(postBattleLoot); @@ -4739,18 +6229,39 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr { if (globalScene.tryTransferHeldItemModifier(this.randItem, pokemon, true, 1, true, undefined, false)) { postBattleLoot.splice(postBattleLoot.indexOf(this.randItem), 1); - globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:postBattleLoot", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), itemName: this.randItem.type.name })); + globalScene.phaseManager.queueMessage( + i18next.t("abilityTriggers:postBattleLoot", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + itemName: this.randItem.type.name, + }), + ); } this.randItem = undefined; } } export class PostFaintAbAttr extends AbAttr { - canApplyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): boolean { + canApplyPostFaint( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker?: Pokemon, + _move?: Move, + _hitResult?: HitResult, + ..._args: any[] + ): boolean { return true; } - applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): void {} + applyPostFaint( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker?: Pokemon, + _move?: Move, + _hitResult?: HitResult, + ..._args: any[] + ): void {} } /** @@ -4759,21 +6270,37 @@ export class PostFaintAbAttr extends AbAttr { * @extends PostFaintAbAttr */ export class PostFaintUnsuppressedWeatherFormChangeAbAttr extends PostFaintAbAttr { - override canApplyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): boolean { + override canApplyPostFaint( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker?: Pokemon, + _move?: Move, + _hitResult?: HitResult, + ..._args: any[] + ): boolean { return getPokemonWithWeatherBasedForms().length > 0; } /** * Triggers {@linkcode Arena.triggerWeatherBasedFormChanges | triggerWeatherBasedFormChanges} * when the user of the ability faints - * @param {Pokemon} pokemon the fainted Pokemon - * @param passive n/a - * @param attacker n/a - * @param move n/a - * @param hitResult n/a - * @param args n/a + * @param {Pokemon} _pokemon the fainted Pokemon + * @param _passive n/a + * @param _attacker n/a + * @param _move n/a + * @param _hitResult n/a + * @param _args n/a */ - override applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): void { + override applyPostFaint( + _pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + _attacker: Pokemon, + _move: Move, + _hitResult: HitResult, + _args: any[], + ): void { if (!simulated) { globalScene.arena.triggerWeatherBasedFormChanges(); } @@ -4789,22 +6316,46 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr { this.damageRatio = damageRatio; } - override canApplyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): boolean { - const diedToDirectDamage = move !== undefined && attacker !== undefined && move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}); + override canApplyPostFaint( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + attacker?: Pokemon, + move?: Move, + _hitResult?: HitResult, + ..._args: any[] + ): boolean { + const diedToDirectDamage = + move !== undefined && + attacker !== undefined && + move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }); const cancelled = new BooleanHolder(false); globalScene.getField(true).map(p => applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled, simulated)); return !(!diedToDirectDamage || cancelled.value || attacker!.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)); } - override applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): void { + override applyPostFaint( + _pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + attacker?: Pokemon, + _move?: Move, + _hitResult?: HitResult, + ..._args: any[] + ): void { if (!simulated) { - attacker!.damageAndUpdate(toDmgValue(attacker!.getMaxHp() * (1 / this.damageRatio)), { result: HitResult.INDIRECT }); + attacker!.damageAndUpdate(toDmgValue(attacker!.getMaxHp() * (1 / this.damageRatio)), { + result: HitResult.INDIRECT, + }); attacker!.turnData.damageTaken += toDmgValue(attacker!.getMaxHp() * (1 / this.damageRatio)); } } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { - return i18next.t("abilityTriggers:postFaintContactDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }); + getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { + return i18next.t("abilityTriggers:postFaintContactDamage", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + abilityName, + }); } } @@ -4812,20 +6363,28 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr { * Attribute used for abilities (Innards Out) that damage the opponent based on how much HP the last attack used to knock out the owner of the ability. */ export class PostFaintHPDamageAbAttr extends PostFaintAbAttr { - constructor() { - super (); - } - - override applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): void { - if (move !== undefined && attacker !== undefined && !simulated) { //If the mon didn't die to indirect damage + override applyPostFaint( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + attacker?: Pokemon, + move?: Move, + _hitResult?: HitResult, + ..._args: any[] + ): void { + //If the mon didn't die to indirect damage + if (move !== undefined && attacker !== undefined && !simulated) { const damage = pokemon.turnData.attacksReceived[0].damage; - attacker.damageAndUpdate((damage), { result: HitResult.INDIRECT }); + attacker.damageAndUpdate(damage, { result: HitResult.INDIRECT }); attacker.turnData.damageTaken += damage; } } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { - return i18next.t("abilityTriggers:postFaintHpDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }); + getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { + return i18next.t("abilityTriggers:postFaintHpDamage", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + abilityName, + }); } } @@ -4841,7 +6400,7 @@ export class RedirectMoveAbAttr extends AbAttr { * - `[2]` - The Pokemon that used the move being redirected */ - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { if (!this.canRedirect(args[0] as MoveId, args[2] as Pokemon)) { return false; } @@ -4850,15 +6409,21 @@ export class RedirectMoveAbAttr extends AbAttr { return target.value !== newTarget; } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: any[], + ): void { const target = args[1] as NumberHolder; const newTarget = pokemon.getBattlerIndex(); target.value = newTarget; } - canRedirect(moveId: MoveId, user: Pokemon): boolean { + canRedirect(moveId: MoveId, _user: Pokemon): boolean { const move = allMoves[moveId]; - return !![ MoveTarget.NEAR_OTHER, MoveTarget.OTHER ].find(t => move.moveTarget === t); + return !![MoveTarget.NEAR_OTHER, MoveTarget.OTHER].find(t => move.moveTarget === t); } } @@ -4875,7 +6440,7 @@ export class RedirectTypeMoveAbAttr extends RedirectMoveAbAttr { } } -export class BlockRedirectAbAttr extends AbAttr { } +export class BlockRedirectAbAttr extends AbAttr {} /** * Used by Early Bird, makes the pokemon wake up faster @@ -4891,7 +6456,7 @@ export class ReduceStatusEffectDurationAbAttr extends AbAttr { this.statusEffect = statusEffect; } - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { return args[1] instanceof NumberHolder && args[0] === this.statusEffect; } @@ -4901,7 +6466,13 @@ export class ReduceStatusEffectDurationAbAttr extends AbAttr { * - `[0]` - The {@linkcode StatusEffect} of the Pokemon * - `[1]` - The number of turns remaining until the status is healed */ - override apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: any[], + ): void { args[1].value -= 1; } } @@ -4919,23 +6490,35 @@ export class FlinchStatStageChangeAbAttr extends FlinchEffectAbAttr { constructor(stats: BattleStat[], stages: number) { super(); - this.stats = Array.isArray(stats) - ? stats - : [ stats ]; + this.stats = Array.isArray(stats) ? stats : [stats]; this.stages = stages; } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + _cancelled: BooleanHolder, + _args: any[], + ): void { if (!simulated) { - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages)); + globalScene.phaseManager.unshiftPhase( + new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages), + ); } } } -export class IncreasePpAbAttr extends AbAttr { } +export class IncreasePpAbAttr extends AbAttr {} export class ForceSwitchOutImmunityAbAttr extends AbAttr { - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + cancelled: BooleanHolder, + _args: any[], + ): void { cancelled.value = true; } } @@ -4945,12 +6528,18 @@ export class ReduceBerryUseThresholdAbAttr extends AbAttr { super(false); } - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { const hpRatio = pokemon.getHpRatio(); return args[0].value < hpRatio; } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: any[], + ): void { args[0].value *= 2; } } @@ -4968,7 +6557,13 @@ export class WeightMultiplierAbAttr extends AbAttr { this.multiplier = multiplier; } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: any[], + ): void { (args[0] as NumberHolder).value *= this.multiplier; } } @@ -4978,7 +6573,13 @@ export class SyncEncounterNatureAbAttr extends AbAttr { super(false); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: any[], + ): void { (args[0] as Pokemon).setNature(pokemon.getNature()); } } @@ -4989,22 +6590,28 @@ export class MoveAbilityBypassAbAttr extends AbAttr { constructor(moveIgnoreFunc?: (pokemon: Pokemon, move: Move) => boolean) { super(false); - this.moveIgnoreFunc = moveIgnoreFunc || ((pokemon, move) => true); + this.moveIgnoreFunc = moveIgnoreFunc || ((_pokemon, _move) => true); } - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return this.moveIgnoreFunc(pokemon, (args[0] as Move)); + override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { + return this.moveIgnoreFunc(pokemon, args[0] as Move); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + cancelled: BooleanHolder, + _args: any[], + ): void { cancelled.value = true; } } -export class AlwaysHitAbAttr extends AbAttr { } +export class AlwaysHitAbAttr extends AbAttr {} /** Attribute for abilities that allow moves that make contact to ignore protection (i.e. Unseen Fist) */ -export class IgnoreProtectOnContactAbAttr extends AbAttr { } +export class IgnoreProtectOnContactAbAttr extends AbAttr {} /** * Attribute implementing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Infiltrator_(Ability) | Infiltrator}. @@ -5015,19 +6622,19 @@ export class InfiltratorAbAttr extends AbAttr { super(false); } - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { return args[0] instanceof BooleanHolder; } /** * Sets a flag to bypass screens, Substitute, Safeguard, and Mist - * @param pokemon n/a - * @param passive n/a - * @param simulated n/a - * @param cancelled n/a + * @param _pokemon n/a + * @param _passive n/a + * @param _simulated n/a + * @param _cancelled n/a * @param args `[0]` a {@linkcode BooleanHolder | BooleanHolder} containing the flag */ - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: null, args: any[]): void { + override apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: null, args: any[]): void { const bypassed = args[0]; bypassed.value = true; } @@ -5038,7 +6645,7 @@ export class InfiltratorAbAttr extends AbAttr { * Allows the source to bounce back {@linkcode MoveFlags.REFLECTABLE | Reflectable} * moves as if the user had used {@linkcode MoveId.MAGIC_COAT | Magic Coat}. */ -export class ReflectStatusMoveAbAttr extends AbAttr { } +export class ReflectStatusMoveAbAttr extends AbAttr {} export class NoTransformAbilityAbAttr extends AbAttr { constructor() { @@ -5062,11 +6669,17 @@ export class IgnoreTypeImmunityAbAttr extends AbAttr { this.allowedMoveTypes = allowedMoveTypes; } - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { return this.defenderType === (args[1] as PokemonType) && this.allowedMoveTypes.includes(args[0] as PokemonType); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + cancelled: BooleanHolder, + _args: any[], + ): void { cancelled.value = true; } } @@ -5085,11 +6698,17 @@ export class IgnoreTypeStatusEffectImmunityAbAttr extends AbAttr { this.defenderType = defenderType; } - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { return this.statusEffect.includes(args[0] as StatusEffect) && this.defenderType.includes(args[1] as PokemonType); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + cancelled: BooleanHolder, + _args: any[], + ): void { cancelled.value = true; } } @@ -5101,20 +6720,16 @@ export class IgnoreTypeStatusEffectImmunityAbAttr extends AbAttr { * @see {@linkcode applyPostBattle} */ export class MoneyAbAttr extends PostBattleAbAttr { - constructor() { - super(); - } - - override canApplyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostBattle(_pokemon: Pokemon, _passive: boolean, simulated: boolean, args: any[]): boolean { return !simulated && args[0]; } /** - * @param pokemon {@linkcode Pokemon} that is the user of this ability. - * @param passive N/A - * @param args - `[0]`: boolean for if the battle ended in a victory + * @param _pokemon {@linkcode Pokemon} that is the user of this ability. + * @param _passive N/A + * @param _args - `[0]`: boolean for if the battle ended in a victory */ - override applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostBattle(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { globalScene.currentBattle.moneyScattered += globalScene.getWaveMoneyAmount(0.2); } } @@ -5139,14 +6754,16 @@ export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageC * @param {ArenaTagType} tagType - The type of arena tag to check for. */ constructor(tagType: ArenaTagType) { - super([ Stat.ATK ], 1, true, false); + super([Stat.ATK], 1, true, false); this.tagType = tagType; } override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const side = pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - return (globalScene.arena.getTagOnSide(this.tagType, side) ?? false) - && super.canApplyPostSummon(pokemon, passive, simulated, args); + return ( + (globalScene.arena.getTagOnSide(this.tagType, side) ?? false) && + super.canApplyPostSummon(pokemon, passive, simulated, args) + ); } /** @@ -5172,10 +6789,16 @@ export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageC export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { private multiplier: number; private tagType: BattlerTagType; - private recoilDamageFunc?: ((pokemon: Pokemon) => number); + private recoilDamageFunc?: (pokemon: Pokemon) => number; private triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string; - constructor(condition: PokemonDefendCondition, multiplier: number, tagType: BattlerTagType, triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string, recoilDamageFunc?: (pokemon: Pokemon) => number) { + constructor( + condition: PokemonDefendCondition, + multiplier: number, + tagType: BattlerTagType, + triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string, + recoilDamageFunc?: (pokemon: Pokemon) => number, + ) { super(condition, multiplier); this.multiplier = multiplier; @@ -5184,7 +6807,15 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { this.triggerMessageFunc = triggerMessageFunc; } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + attacker: Pokemon, + move: Move, + _cancelled: BooleanHolder | null, + _args: any[], + ): boolean { return this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon); } @@ -5194,17 +6825,29 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { * * @param pokemon The Pokémon with the ability. * @param _passive n/a - * @param attacker The attacking Pokémon. - * @param move The move being used. + * @param _attacker The attacking Pokémon. + * @param _move The move being used. * @param _cancelled n/a * @param args Additional arguments. */ - override applyPreDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _cancelled: BooleanHolder, args: any[]): void { + override applyPreDefend( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + _attacker: Pokemon, + _move: Move, + _cancelled: BooleanHolder, + args: any[], + ): void { if (!simulated) { (args[0] as NumberHolder).value = this.multiplier; pokemon.removeTag(this.tagType); if (this.recoilDamageFunc) { - pokemon.damageAndUpdate(this.recoilDamageFunc(pokemon), { result: HitResult.INDIRECT, ignoreSegments: true, ignoreFaintPhase: true }); + pokemon.damageAndUpdate(this.recoilDamageFunc(pokemon), { + result: HitResult.INDIRECT, + ignoreSegments: true, + ignoreFaintPhase: true, + }); } } } @@ -5227,9 +6870,9 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { * @see {@linkcode applyPreSummon()} */ export class PreSummonAbAttr extends AbAttr { - applyPreSummon(pokemon: Pokemon, passive: boolean, args: any[]): void {} + applyPreSummon(_pokemon: Pokemon, _passive: boolean, _args: any[]): void {} - canApplyPreSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + canApplyPreSummon(_pokemon: Pokemon, _passive: boolean, _args: any[]): boolean { return true; } } @@ -5239,27 +6882,32 @@ export class IllusionPreSummonAbAttr extends PreSummonAbAttr { * Apply a new illusion when summoning Zoroark if the illusion is available * * @param pokemon - The Pokémon with the Illusion ability. - * @param passive - N/A - * @param args - N/A + * @param _passive - N/A + * @param _args - N/A * @returns Whether the illusion was applied. */ - override applyPreSummon(pokemon: Pokemon, passive: boolean, args: any[]): void { - const party: Pokemon[] = (pokemon.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter(p => p.isAllowedInBattle()); - const lastPokemon: Pokemon = party.filter(p => p !==pokemon).at(-1) || pokemon; + override applyPreSummon(pokemon: Pokemon, _passive: boolean, _args: any[]): void { + const party: Pokemon[] = (pokemon.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter( + p => p.isAllowedInBattle(), + ); + const lastPokemon: Pokemon = party.filter(p => p !== pokemon).at(-1) || pokemon; pokemon.setIllusion(lastPokemon); } - override canApplyPreSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + override canApplyPreSummon(pokemon: Pokemon, _passive: boolean, _args: any[]): boolean { if (pokemon.hasTrainer()) { - const party: Pokemon[] = (pokemon.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter(p => p.isAllowedInBattle()); - const lastPokemon: Pokemon = party.filter(p => p !==pokemon).at(-1) || pokemon; + const party: Pokemon[] = (pokemon.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter( + p => p.isAllowedInBattle(), + ); + const lastPokemon: Pokemon = party.filter(p => p !== pokemon).at(-1) || pokemon; const speciesId = lastPokemon.species.speciesId; // If the last conscious Pokémon in the party is a Terastallized Ogerpon or Terapagos, Illusion will not activate. // Illusion will also not activate if the Pokémon with Illusion is Terastallized and the last Pokémon in the party is Ogerpon or Terapagos. if ( lastPokemon === pokemon || - ((speciesId === SpeciesId.OGERPON || speciesId === SpeciesId.TERAPAGOS) && (lastPokemon.isTerastallized || pokemon.isTerastallized)) + ((speciesId === SpeciesId.OGERPON || speciesId === SpeciesId.TERAPAGOS) && + (lastPokemon.isTerastallized || pokemon.isTerastallized)) ) { return false; } @@ -5269,7 +6917,13 @@ export class IllusionPreSummonAbAttr extends PreSummonAbAttr { } export class IllusionBreakAbAttr extends AbAttr { - override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder | null, _args: any[]): void { + override apply( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder | null, + _args: any[], + ): void { pokemon.breakIllusion(); pokemon.summonData.illusionBroken = true; } @@ -5280,21 +6934,42 @@ export class PostDefendIllusionBreakAbAttr extends PostDefendAbAttr { * Destroy the illusion upon taking damage * * @param pokemon - The Pokémon with the Illusion ability. - * @param passive - unused - * @param attacker - The attacking Pokémon. - * @param move - The move being used. - * @param hitResult - The type of hitResult the pokemon got - * @param args - unused + * @param _passive - unused + * @param _attacker - The attacking Pokémon. + * @param _move - The move being used. + * @param _hitResult - The type of hitResult the pokemon got + * @param _args - unused * @returns - Whether the illusion was destroyed. */ - override applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): void { + override applyPostDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + _move: Move, + _hitResult: HitResult, + _args: any[], + ): void { pokemon.breakIllusion(); pokemon.summonData.illusionBroken = true; } - override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - const breakIllusion: HitResult[] = [ HitResult.EFFECTIVE, HitResult.SUPER_EFFECTIVE, HitResult.NOT_VERY_EFFECTIVE, HitResult.ONE_HIT_KO ]; - return breakIllusion.includes(hitResult) && !!pokemon.summonData.illusion + override canApplyPostDefend( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _attacker: Pokemon, + _move: Move, + hitResult: HitResult, + _args: any[], + ): boolean { + const breakIllusion: HitResult[] = [ + HitResult.EFFECTIVE, + HitResult.SUPER_EFFECTIVE, + HitResult.NOT_VERY_EFFECTIVE, + HitResult.ONE_HIT_KO, + ]; + return breakIllusion.includes(hitResult) && !!pokemon.summonData.illusion; } } @@ -5303,16 +6978,15 @@ export class IllusionPostBattleAbAttr extends PostBattleAbAttr { * Break the illusion once the battle ends * * @param pokemon - The Pokémon with the Illusion ability. - * @param passive - Unused - * @param args - Unused + * @param _passive - Unused + * @param _args - Unused * @returns - Whether the illusion was applied. */ - override applyPostBattle(pokemon: Pokemon, passive: boolean, simulated:boolean, args: any[]): void { - pokemon.breakIllusion() + override applyPostBattle(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { + pokemon.breakIllusion(); } } - /** * If a Pokémon with this Ability selects a damaging move, it has a 30% chance of going first in its priority bracket. If the Ability activates, this is announced at the start of the turn (after move selection). * @@ -5329,28 +7003,36 @@ export class BypassSpeedChanceAbAttr extends AbAttr { this.chance = chance; } - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApply(pokemon: Pokemon, _passive: boolean, simulated: boolean, args: any[]): boolean { const bypassSpeed = args[0] as BooleanHolder; const turnCommand = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; const isCommandFight = turnCommand?.command === Command.FIGHT; const move = turnCommand?.move?.move ? allMoves[turnCommand.move.move] : null; const isDamageMove = move?.category === MoveCategory.PHYSICAL || move?.category === MoveCategory.SPECIAL; - return !simulated && !bypassSpeed.value && pokemon.randBattleSeedInt(100) < this.chance && isCommandFight && isDamageMove; + return ( + !simulated && !bypassSpeed.value && pokemon.randBattleSeedInt(100) < this.chance && isCommandFight && isDamageMove + ); } /** * bypass move order in their priority bracket when pokemon choose damaging move - * @param {Pokemon} pokemon {@linkcode Pokemon} the Pokemon applying this ability - * @param {boolean} passive N/A - * @param {BooleanHolder} cancelled N/A + * @param {Pokemon} _pokemon {@linkcode Pokemon} the Pokemon applying this ability + * @param {boolean} _passive N/A + * @param {BooleanHolder} _cancelled N/A * @param {any[]} args [0] {@linkcode BooleanHolder} set to true when the ability activated */ - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: any[], + ): void { const bypassSpeed = args[0] as BooleanHolder; bypassSpeed.value = true; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:quickDraw", { pokemonName: getPokemonNameWithAffix(pokemon) }); } } @@ -5358,9 +7040,9 @@ export class BypassSpeedChanceAbAttr extends AbAttr { /** * This attribute checks if a Pokemon's move meets a provided condition to determine if the Pokemon can use Quick Claw * It was created because Pokemon with the ability Mycelium Might cannot access Quick Claw's benefits when using status moves. -*/ + */ export class PreventBypassSpeedChanceAbAttr extends AbAttr { - private condition: ((pokemon: Pokemon, move: Move) => boolean); + private condition: (pokemon: Pokemon, move: Move) => boolean; /** * @param {function} condition - checks if a move meets certain conditions @@ -5370,7 +7052,7 @@ export class PreventBypassSpeedChanceAbAttr extends AbAttr { this.condition = condition; } - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { const turnCommand = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; const isCommandFight = turnCommand?.command === Command.FIGHT; const move = turnCommand?.move?.move ? allMoves[turnCommand.move.move] : null; @@ -5381,7 +7063,13 @@ export class PreventBypassSpeedChanceAbAttr extends AbAttr { * @argument {boolean} bypassSpeed - determines if a Pokemon is able to bypass speed at the moment * @argument {boolean} canCheckHeldItems - determines if a Pokemon has access to Quick Claw's effects or not */ - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + override apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + args: any[], + ): void { const bypassSpeed = args[0] as BooleanHolder; const canCheckHeldItems = args[1] as BooleanHolder; bypassSpeed.value = false; @@ -5398,11 +7086,17 @@ export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr { super(true); } - override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { return !pokemon.isTerastallized; } - override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder, _args: any[]): void { + override apply( + pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder, + _args: any[], + ): void { const currentTerrain = globalScene.arena.getTerrainType(); const typeChange: PokemonType[] = this.determineTypeChange(pokemon, currentTerrain); if (typeChange.length !== 0) { @@ -5445,26 +7139,24 @@ export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr { } override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return globalScene.arena.getTerrainType() !== TerrainType.NONE && - this.canApply(pokemon, passive, simulated, args); + return globalScene.arena.getTerrainType() !== TerrainType.NONE && this.canApply(pokemon, passive, simulated, args); } /** * Checks if the Pokemon should change types if summoned into an active terrain */ - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, _args: any[]): void { this.apply(pokemon, passive, simulated, new BooleanHolder(false), []); } - override getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]) { + override getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]) { const currentTerrain = globalScene.arena.getTerrainType(); const pokemonNameWithAffix = getPokemonNameWithAffix(pokemon); if (currentTerrain === TerrainType.NONE) { return i18next.t("abilityTriggers:pokemonTypeChangeRevert", { pokemonNameWithAffix }); - } else { - const moveType = i18next.t(`pokemonInfo:Type.${PokemonType[this.determineTypeChange(pokemon, currentTerrain)[0]]}`); - return i18next.t("abilityTriggers:pokemonTypeChange", { pokemonNameWithAffix, moveType }); } + const moveType = i18next.t(`pokemonInfo:Type.${PokemonType[this.determineTypeChange(pokemon, currentTerrain)[0]]}`); + return i18next.t("abilityTriggers:pokemonTypeChange", { pokemonNameWithAffix, moveType }); } } @@ -5475,23 +7167,26 @@ function applySingleAbAttrs( applyFunc: AbAttrApplyFunc, successFunc: AbAttrSuccessFunc, args: any[], - gainedMidTurn: boolean = false, - simulated: boolean = false, - messages: string[] = [] + gainedMidTurn = false, + simulated = false, + messages: string[] = [], ) { - if (!pokemon?.canApplyAbility(passive) || (passive && (pokemon.getPassiveAbility().id === pokemon.getAbility().id))) { + if (!pokemon?.canApplyAbility(passive) || (passive && pokemon.getPassiveAbility().id === pokemon.getAbility().id)) { return; } const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility(); - if (gainedMidTurn && ability.getAttrs(attrType).some(attr => attr instanceof PostSummonAbAttr && !attr.shouldActivateOnGain())) { + if ( + gainedMidTurn && + ability.getAttrs(attrType).some(attr => attr instanceof PostSummonAbAttr && !attr.shouldActivateOnGain()) + ) { return; } for (const attr of ability.getAttrs(attrType)) { const condition = attr.getCondition(); let abShown = false; - if (condition && !condition(pokemon) || !successFunc(attr, passive)) { + if ((condition && !condition(pokemon)) || !successFunc(attr, passive)) { continue; } @@ -5540,33 +7235,41 @@ class ForceSwitchOutHelper { * - If the Pokémon is still alive (hp > 0), and if so, it leaves the field and a new SwitchPhase is initiated. */ if (switchOutTarget.isPlayer()) { - if (globalScene.getPlayerParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) { + if (globalScene.getPlayerParty().filter(p => p.isAllowedInBattle() && !p.isOnField()).length < 1) { return false; } if (switchOutTarget.hp > 0) { switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); - globalScene.phaseManager.prependToPhase(new SwitchPhase(this.switchType, switchOutTarget.getFieldIndex(), true, true), MoveEndPhase); + globalScene.phaseManager.prependToPhase( + new SwitchPhase(this.switchType, switchOutTarget.getFieldIndex(), true, true), + MoveEndPhase, + ); return true; } - /** - * For non-wild battles, it checks if the opposing party has any available Pokémon to switch in. - * If yes, the Pokémon leaves the field and a new SwitchSummonPhase is initiated. - */ + /** + * For non-wild battles, it checks if the opposing party has any available Pokémon to switch in. + * If yes, the Pokémon leaves the field and a new SwitchSummonPhase is initiated. + */ } else if (globalScene.currentBattle.battleType !== BattleType.WILD) { - if (globalScene.getEnemyParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) { + if (globalScene.getEnemyParty().filter(p => p.isAllowedInBattle() && !p.isOnField()).length < 1) { return false; } if (switchOutTarget.hp > 0) { switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); - const summonIndex = (globalScene.currentBattle.trainer ? globalScene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0); - globalScene.phaseManager.prependToPhase(new SwitchSummonPhase(this.switchType, switchOutTarget.getFieldIndex(), summonIndex, false, false), MoveEndPhase); + const summonIndex = globalScene.currentBattle.trainer + ? globalScene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) + : 0; + globalScene.phaseManager.prependToPhase( + new SwitchSummonPhase(this.switchType, switchOutTarget.getFieldIndex(), summonIndex, false, false), + MoveEndPhase, + ); return true; } - /** - * For wild Pokémon battles, the Pokémon will flee if the conditions are met (waveIndex and double battles). - * It will not flee if it is a Mystery Encounter with fleeing disabled (checked in `getSwitchOutCondition()`) or if it is a wave 10x wild boss - */ + /** + * For wild Pokémon battles, the Pokémon will flee if the conditions are met (waveIndex and double battles). + * It will not flee if it is a Mystery Encounter with fleeing disabled (checked in `getSwitchOutCondition()`) or if it is a wave 10x wild boss + */ } else { const allyPokemon = switchOutTarget.getAlly(); @@ -5576,7 +7279,12 @@ class ForceSwitchOutHelper { if (switchOutTarget.hp > 0) { switchOutTarget.leaveField(false); - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }), null, true, 500); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }), + null, + true, + 500, + ); if (globalScene.currentBattle.double && !isNullOrUndefined(allyPokemon)) { globalScene.redirectPokemonMoves(switchOutTarget, allyPokemon); } @@ -5622,14 +7330,24 @@ class ForceSwitchOutHelper { } } - if (!player && globalScene.currentBattle.isBattleMysteryEncounter() && !globalScene.currentBattle.mysteryEncounter?.fleeAllowed) { + if ( + !player && + globalScene.currentBattle.isBattleMysteryEncounter() && + !globalScene.currentBattle.mysteryEncounter?.fleeAllowed + ) { return false; } const party = player ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); - return (!player && globalScene.currentBattle.battleType === BattleType.WILD) - || party.filter(p => p.isAllowedInBattle() && !p.isOnField() - && (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > 0; + return ( + (!player && globalScene.currentBattle.battleType === BattleType.WILD) || + party.filter( + p => + p.isAllowedInBattle() && + !p.isOnField() && + (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot), + ).length > 0 + ); } /** @@ -5641,7 +7359,9 @@ class ForceSwitchOutHelper { public getFailedText(target: Pokemon): string | null { const blockedByAbility = new BooleanHolder(false); applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, blockedByAbility); - return blockedByAbility.value ? i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) }) : null; + return blockedByAbility.value + ? i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) }) + : null; } } @@ -5669,22 +7389,23 @@ function calculateShellBellRecovery(pokemon: Pokemon): number { */ export class PostDamageAbAttr extends AbAttr { public canApplyPostDamage( - pokemon: Pokemon, - damage: number, - passive: boolean, - simulated: boolean, - args: any[], - source?: Pokemon): boolean { + _pokemon: Pokemon, + _damage: number, + _passive: boolean, + _simulated: boolean, + _args: any[], + _source?: Pokemon, + ): boolean { return true; } public applyPostDamage( - pokemon: Pokemon, - damage: number, - passive: boolean, - simulated: boolean, - args: any[], - source?: Pokemon, + _pokemon: Pokemon, + _damage: number, + _passive: boolean, + _simulated: boolean, + _args: any[], + _source?: Pokemon, ): void {} } @@ -5711,13 +7432,14 @@ export class PostDamageForceSwitchAbAttr extends PostDamageAbAttr { public override canApplyPostDamage( pokemon: Pokemon, damage: number, - passive: boolean, - simulated: boolean, - args: any[], - source?: Pokemon): boolean { + _passive: boolean, + _simulated: boolean, + _args: any[], + source?: Pokemon, + ): boolean { const moveHistory = pokemon.getMoveHistory(); // Will not activate when the Pokémon's HP is lowered by cutting its own HP - const fordbiddenAttackingMoves = [ MoveId.BELLY_DRUM, MoveId.SUBSTITUTE, MoveId.CURSE, MoveId.PAIN_SPLIT ]; + const fordbiddenAttackingMoves = [MoveId.BELLY_DRUM, MoveId.SUBSTITUTE, MoveId.CURSE, MoveId.PAIN_SPLIT]; if (moveHistory.length > 0) { const lastMoveUsed = moveHistory[moveHistory.length - 1]; if (fordbiddenAttackingMoves.includes(lastMoveUsed.move)) { @@ -5726,20 +7448,25 @@ export class PostDamageForceSwitchAbAttr extends PostDamageAbAttr { } // Dragon Tail and Circle Throw switch out Pokémon before the Ability activates. - const fordbiddenDefendingMoves = [ MoveId.DRAGON_TAIL, MoveId.CIRCLE_THROW ]; + const fordbiddenDefendingMoves = [MoveId.DRAGON_TAIL, MoveId.CIRCLE_THROW]; if (source) { const enemyMoveHistory = source.getMoveHistory(); if (enemyMoveHistory.length > 0) { const enemyLastMoveUsed = enemyMoveHistory[enemyMoveHistory.length - 1]; // Will not activate if the Pokémon's HP falls below half while it is in the air during Sky Drop. - if (fordbiddenDefendingMoves.includes(enemyLastMoveUsed.move) || enemyLastMoveUsed.move === MoveId.SKY_DROP && enemyLastMoveUsed.result === MoveResult.OTHER) { + if ( + fordbiddenDefendingMoves.includes(enemyLastMoveUsed.move) || + (enemyLastMoveUsed.move === MoveId.SKY_DROP && enemyLastMoveUsed.result === MoveResult.OTHER) + ) { return false; - // Will not activate if the Pokémon's HP falls below half by a move affected by Sheer Force. - // TODO: Make this use the sheer force disable condition - } else if (allMoves[enemyLastMoveUsed.move].chance >= 0 && source.hasAbility(AbilityId.SHEER_FORCE)) { + // Will not activate if the Pokémon's HP falls below half by a move affected by Sheer Force. + // TODO: Make this use the sheer force disable condition + } + if (allMoves[enemyLastMoveUsed.move].chance >= 0 && source.hasAbility(AbilityId.SHEER_FORCE)) { return false; + } // Activate only after the last hit of multistrike moves - } else if (source.turnData.hitsLeft > 1) { + if (source.turnData.hitsLeft > 1) { return false; } if (source.turnData.hitCount > 1) { @@ -5769,13 +7496,20 @@ export class PostDamageForceSwitchAbAttr extends PostDamageAbAttr { * the Pokémon's health after damage to determine whether the switch-out should occur. * * @param pokemon The Pokémon that took damage. - * @param damage N/A - * @param passive N/A - * @param simulated Whether the ability is being simulated. - * @param args N/A - * @param source N/A + * @param _damage N/A + * @param _passive N/A + * @param _simulated Whether the ability is being simulated. + * @param _args N/A + * @param _source N/A */ - public override applyPostDamage(pokemon: Pokemon, damage: number, passive: boolean, simulated: boolean, args: any[], source?: Pokemon): void { + public override applyPostDamage( + pokemon: Pokemon, + _damage: number, + _passive: boolean, + _simulated: boolean, + _args: any[], + _source?: Pokemon, + ): void { this.helper.switchOutLogic(pokemon); } } @@ -5785,11 +7519,11 @@ function applyAbAttrsInternal( applyFunc: AbAttrApplyFunc, successFunc: AbAttrSuccessFunc, args: any[], - simulated: boolean = false, + simulated = false, messages: string[] = [], - gainedMidTurn = false + gainedMidTurn = false, ) { - for (const passive of [ false, true ]) { + for (const passive of [false, true]) { if (pokemon) { applySingleAbAttrs(pokemon, passive, attrType, applyFunc, successFunc, args, gainedMidTurn, simulated, messages); globalScene.phaseManager.clearPhaseQueueSplice(); @@ -5862,7 +7596,8 @@ export function applyPostDefendAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args), - (attr, passive) => attr.canApplyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args), args, + (attr, passive) => attr.canApplyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args), + args, simulated, ); } @@ -5879,8 +7614,8 @@ export function applyPostMoveUsedAbAttrs( applyAbAttrsInternal( attrType, pokemon, - (attr, passive) => attr.applyPostMoveUsed(pokemon, move, source, targets, simulated, args), - (attr, passive) => attr.canApplyPostMoveUsed(pokemon, move, source, targets, simulated, args), + (attr, _passive) => attr.applyPostMoveUsed(pokemon, move, source, targets, simulated, args), + (attr, _passive) => attr.canApplyPostMoveUsed(pokemon, move, source, targets, simulated, args), args, simulated, ); @@ -5913,14 +7648,23 @@ export function applyStatMultiplierAbAttrs( * @param ignoreAbility - Whether or not the ability should be ignored by the pokemon or its move. * @param args - unused */ -export function applyAllyStatMultiplierAbAttrs(attrType: Constructor, - pokemon: Pokemon, stat: BattleStat, statValue: NumberHolder, simulated: boolean = false, checkedPokemon: Pokemon, ignoreAbility: boolean, ...args: any[] +export function applyAllyStatMultiplierAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + stat: BattleStat, + statValue: NumberHolder, + simulated = false, + checkedPokemon: Pokemon, + ignoreAbility: boolean, + ...args: any[] ): void { - return applyAbAttrsInternal( + applyAbAttrsInternal( attrType, pokemon, - (attr, passive) => attr.applyAllyStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, ignoreAbility, args), - (attr, passive) => attr.canApplyAllyStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, ignoreAbility, args), + (attr, passive) => + attr.applyAllyStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, ignoreAbility, args), + (attr, passive) => + attr.canApplyAllyStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, ignoreAbility, args), args, simulated, ); @@ -5948,7 +7692,7 @@ export function applyPostDamageAbAttrs( attrType: Constructor, pokemon: Pokemon, damage: number, - passive: boolean, + _passive: boolean, simulated = false, args: any[], source?: Pokemon, @@ -5985,8 +7729,11 @@ export function applyFieldStatMultiplierAbAttrs( applyAbAttrsInternal( attrType, pokemon, - (attr, passive) => attr.applyFieldStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, hasApplied, args), - (attr, passive) => attr.canApplyFieldStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, hasApplied, args), args, + (attr, passive) => + attr.applyFieldStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, hasApplied, args), + (attr, passive) => + attr.canApplyFieldStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, hasApplied, args), + args, ); } @@ -6021,7 +7768,8 @@ export function applyPostAttackAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args), - (attr, passive) => attr.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args), args, + (attr, passive) => attr.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args), + args, simulated, ); } @@ -6075,17 +7823,13 @@ export function applyPostSummonAbAttrs( ); } -export function applyPreSummonAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - ...args: any[] -): void { +export function applyPreSummonAbAttrs(attrType: Constructor, pokemon: Pokemon, ...args: any[]): void { applyAbAttrsInternal( attrType, pokemon, (attr, passive) => attr.applyPreSummon(pokemon, passive, args), (attr, passive) => attr.canApplyPreSummon(pokemon, passive, args), - args + args, ); } @@ -6111,18 +7855,17 @@ export function applyPreLeaveFieldAbAttrs( simulated = false, ...args: any[] ): void { - return applyAbAttrsInternal( + applyAbAttrsInternal( attrType, pokemon, - (attr, passive) => - attr.applyPreLeaveField(pokemon, passive, simulated, args), + (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, args), (attr, passive) => attr.canApplyPreLeaveField(pokemon, passive, simulated, args), args, - simulated + simulated, ); } -export function applyPreStatStageChangeAbAttrs ( +export function applyPreStatStageChangeAbAttrs( attrType: Constructor, pokemon: Pokemon | null, stat: BattleStat, @@ -6144,7 +7887,7 @@ export function applyPostStatStageChangeAbAttrs( attrType: Constructor, pokemon: Pokemon, stats: BattleStat[], - stages: integer, + stages: number, selfTarget: boolean, simulated = false, ...args: any[] @@ -6153,7 +7896,8 @@ export function applyPostStatStageChangeAbAttrs( attrType, pokemon, (attr, _passive) => attr.applyPostStatStageChange(pokemon, simulated, stats, stages, selfTarget, args), - (attr, _passive) => attr.canApplyPostStatStageChange(pokemon, simulated, stats, stages, selfTarget, args), args, + (attr, _passive) => attr.canApplyPostStatStageChange(pokemon, simulated, stats, stages, selfTarget, args), + args, simulated, ); } @@ -6292,7 +8036,8 @@ export function applyCheckTrappedAbAttrs( attrType, pokemon, (attr, passive) => attr.applyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args), - (attr, passive) => attr.canApplyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args), args, + (attr, passive) => attr.canApplyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args), + args, simulated, messages, ); @@ -6342,8 +8087,8 @@ export function applyPostItemLostAbAttrs( applyAbAttrsInternal( attrType, pokemon, - (attr, passive) => attr.applyPostItemLost(pokemon, simulated, args), - (attr, passive) => attr.canApplyPostItemLost(pokemon, simulated, args), + (attr, _passive) => attr.applyPostItemLost(pokemon, simulated, args), + (attr, _passive) => attr.canApplyPostItemLost(pokemon, simulated, args), args, ); } @@ -6353,11 +8098,7 @@ export function applyPostItemLostAbAttrs( * * Ignores passives as they don't change and shouldn't be reapplied when main abilities change */ -export function applyOnGainAbAttrs( - pokemon: Pokemon, - passive: boolean = false, - simulated: boolean = false, - ...args: any[]): void { +export function applyOnGainAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void { applySingleAbAttrs( pokemon, passive, @@ -6378,11 +8119,12 @@ export function applyOnLoseAbAttrs(pokemon: Pokemon, passive = false, simulated pokemon, passive, PreLeaveFieldAbAttr, - (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [ ...args, true ]), - (attr, passive) => attr.canApplyPreLeaveField(pokemon, passive, simulated, [ ...args, true ]), + (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [...args, true]), + (attr, passive) => attr.canApplyPreLeaveField(pokemon, passive, simulated, [...args, true]), args, true, - simulated); + simulated, + ); applySingleAbAttrs( pokemon, @@ -6392,8 +8134,8 @@ export function applyOnLoseAbAttrs(pokemon: Pokemon, passive = false, simulated (attr, passive) => attr.canApply(pokemon, passive, simulated, args), args, true, - simulated - ) + simulated, + ); } /** @@ -6408,12 +8150,16 @@ function setAbilityRevealed(pokemon: Pokemon): void { * Returns all Pokemon on field with weather-based forms */ function getPokemonWithWeatherBasedForms() { - return globalScene.getField(true).filter(p => - (p.hasAbility(AbilityId.FORECAST) && p.species.speciesId === SpeciesId.CASTFORM) - || (p.hasAbility(AbilityId.FLOWER_GIFT) && p.species.speciesId === SpeciesId.CHERRIM) - ); + return globalScene + .getField(true) + .filter( + p => + (p.hasAbility(AbilityId.FORECAST) && p.species.speciesId === SpeciesId.CASTFORM) || + (p.hasAbility(AbilityId.FLOWER_GIFT) && p.species.speciesId === SpeciesId.CHERRIM), + ); } +// biome-ignore format: prevent biome from removing the newlines (e.g. prevent `new Ability(...).attr(...)`) export function initAbilities() { allAbilities.push( new Ability(AbilityId.NONE, 3), @@ -6553,7 +8299,7 @@ export function initAbilities() { .attr(PostSummonHealStatusAbAttr, StatusEffect.BURN) .ignorable(), new Ability(AbilityId.MAGNET_PULL, 3) - .attr(ArenaTrapAbAttr, (user, target) => { + .attr(ArenaTrapAbAttr, (_user, target) => { return target.getTypes(true).includes(PokemonType.STEEL) || (target.getTypes(true).includes(PokemonType.STELLAR) && target.getTypes().includes(PokemonType.STEEL)); }), new Ability(AbilityId.SOUNDPROOF, 3) @@ -6609,7 +8355,7 @@ export function initAbilities() { .bypassFaint() .ignorable(), new Ability(AbilityId.SHED_SKIN, 3) - .conditionalAttr(pokemon => !randSeedInt(3), PostTurnResetStatusAbAttr), + .conditionalAttr(_pokemon => !randSeedInt(3), PostTurnResetStatusAbAttr), new Ability(AbilityId.GUTS, 3) .attr(BypassBurnDamageReductionAbAttr) .conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(AbilityId.COMATOSE), StatMultiplierAbAttr, Stat.ATK, 1.5), @@ -6632,7 +8378,7 @@ export function initAbilities() { .attr(PostSummonWeatherChangeAbAttr, WeatherType.SUNNY) .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.SUNNY), new Ability(AbilityId.ARENA_TRAP, 3) - .attr(ArenaTrapAbAttr, (user, target) => target.isGrounded()) + .attr(ArenaTrapAbAttr, (_user, target) => target.isGrounded()) .attr(DoubleBattleChanceAbAttr), new Ability(AbilityId.VITAL_SPIRIT, 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP) @@ -6660,8 +8406,8 @@ export function initAbilities() { .attr(TypeImmunityStatStageChangeAbAttr, PokemonType.ELECTRIC, Stat.SPD, 1) .ignorable(), new Ability(AbilityId.RIVALRY, 4) - .attr(MovePowerBoostAbAttr, (user, target, move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender === target?.gender, 1.25, true) - .attr(MovePowerBoostAbAttr, (user, target, move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender !== target?.gender, 0.75), + .attr(MovePowerBoostAbAttr, (user, target, _move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender === target?.gender, 1.25, true) + .attr(MovePowerBoostAbAttr, (user, target, _move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender !== target?.gender, 0.75), new Ability(AbilityId.STEADFAST, 4) .attr(FlinchStatStageChangeAbAttr, [ Stat.SPD ], 1), new Ability(AbilityId.SNOW_CLOAK, 4) @@ -6693,7 +8439,7 @@ export function initAbilities() { new Ability(AbilityId.DOWNLOAD, 4) .attr(DownloadAbAttr), new Ability(AbilityId.IRON_FIST, 4) - .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.PUNCHING_MOVE), 1.2), + .attr(MovePowerBoostAbAttr, (_user, _target, move) => move.hasFlag(MoveFlags.PUNCHING_MOVE), 1.2), new Ability(AbilityId.POISON_HEAL, 4) .attr(PostTurnStatusHealAbAttr, StatusEffect.TOXIC, StatusEffect.POISON) .attr(BlockStatusDamageAbAttr, StatusEffect.TOXIC, StatusEffect.POISON), @@ -6721,7 +8467,7 @@ export function initAbilities() { .attr(AlwaysHitAbAttr) .attr(DoubleBattleChanceAbAttr), new Ability(AbilityId.STALL, 4) - .attr(ChangeMovePriorityAbAttr, (pokemon, move: Move) => true, -0.2), + .attr(ChangeMovePriorityAbAttr, (_pokemon, _move: Move) => true, -0.2), new Ability(AbilityId.TECHNICIAN, 4) .attr(MovePowerBoostAbAttr, (user, target, move) => { const power = new NumberHolder(move.power); @@ -6778,7 +8524,7 @@ export function initAbilities() { new Ability(AbilityId.FRISK, 4) .attr(FriskAbAttr), new Ability(AbilityId.RECKLESS, 4) - .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.RECKLESS_MOVE), 1.2), + .attr(MovePowerBoostAbAttr, (_user, _target, move) => move.hasFlag(MoveFlags.RECKLESS_MOVE), 1.2), new Ability(AbilityId.MULTITYPE, 4) .attr(NoFusionAbilityAbAttr) .uncopiable() @@ -6801,7 +8547,7 @@ export function initAbilities() { .attr(PostDefendStealHeldItemAbAttr, (target, user, move) => move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user, target})) .condition(getSheerForceHitDisableAbCondition()), new Ability(AbilityId.SHEER_FORCE, 5) - .attr(MovePowerBoostAbAttr, (user, target, move) => move.chance >= 1, 1.3) + .attr(MovePowerBoostAbAttr, (_user, _target, move) => move.chance >= 1, 1.3) .attr(MoveEffectChanceMultiplierAbAttr, 0), // This attribute does not seem to function - Should disable life orb, eject button, red card, kee/maranga berry if they get implemented new Ability(AbilityId.CONTRARY, 5) .attr(StatStageChangeMultiplierAbAttr, -1) @@ -6809,7 +8555,7 @@ export function initAbilities() { new Ability(AbilityId.UNNERVE, 5) .attr(PreventBerryUseAbAttr), new Ability(AbilityId.DEFIANT, 5) - .attr(PostStatStageChangeStatStageChangeAbAttr, (target, statsChanged, stages) => stages < 0, [ Stat.ATK ], 2), + .attr(PostStatStageChangeStatStageChangeAbAttr, (_target, _statsChanged, stages) => stages < 0, [ Stat.ATK ], 2), new Ability(AbilityId.DEFEATIST, 5) .attr(StatMultiplierAbAttr, Stat.ATK, 0.5) .attr(StatMultiplierAbAttr, Stat.SPATK, 0.5) @@ -6823,8 +8569,8 @@ export function initAbilities() { .attr(AlliedFieldDamageReductionAbAttr, 0.75) .ignorable(), new Ability(AbilityId.WEAK_ARMOR, 5) - .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL, Stat.DEF, -1) - .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL, Stat.SPD, 2), + .attr(PostDefendStatStageChangeAbAttr, (_target, _user, move) => move.category === MoveCategory.PHYSICAL, Stat.DEF, -1) + .attr(PostDefendStatStageChangeAbAttr, (_target, _user, move) => move.category === MoveCategory.PHYSICAL, Stat.SPD, 2), new Ability(AbilityId.HEAVY_METAL, 5) .attr(WeightMultiplierAbAttr, 2) .ignorable(), @@ -6832,12 +8578,12 @@ export function initAbilities() { .attr(WeightMultiplierAbAttr, 0.5) .ignorable(), new Ability(AbilityId.MULTISCALE, 5) - .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.isFullHp(), 0.5) + .attr(ReceivedMoveDamageMultiplierAbAttr, (target, _user, _move) => target.isFullHp(), 0.5) .ignorable(), new Ability(AbilityId.TOXIC_BOOST, 5) - .attr(MovePowerBoostAbAttr, (user, target, move) => move.category === MoveCategory.PHYSICAL && (user?.status?.effect === StatusEffect.POISON || user?.status?.effect === StatusEffect.TOXIC), 1.5), + .attr(MovePowerBoostAbAttr, (user, _target, move) => move.category === MoveCategory.PHYSICAL && (user?.status?.effect === StatusEffect.POISON || user?.status?.effect === StatusEffect.TOXIC), 1.5), new Ability(AbilityId.FLARE_BOOST, 5) - .attr(MovePowerBoostAbAttr, (user, target, move) => move.category === MoveCategory.SPECIAL && user?.status?.effect === StatusEffect.BURN, 1.5), + .attr(MovePowerBoostAbAttr, (user, _target, move) => move.category === MoveCategory.SPECIAL && user?.status?.effect === StatusEffect.BURN, 1.5), new Ability(AbilityId.HARVEST, 5) .attr( PostTurnRestoreBerryAbAttr, @@ -6869,7 +8615,7 @@ export function initAbilities() { .attr(WonderSkinAbAttr) .ignorable(), new Ability(AbilityId.ANALYTIC, 5) - .attr(MovePowerBoostAbAttr, (user, target, move) => { + .attr(MovePowerBoostAbAttr, (user, _target, _move) => { const movePhase = globalScene.phaseManager.findPhase((phase) => phase.is("MovePhase") && phase.pokemon.id !== user?.id); return isNullOrUndefined(movePhase); }, 1.3), @@ -6897,9 +8643,9 @@ export function initAbilities() { new Ability(AbilityId.MOXIE, 5) .attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1), new Ability(AbilityId.JUSTIFIED, 5) - .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === PokemonType.DARK && move.category !== MoveCategory.STATUS, Stat.ATK, 1), + .attr(PostDefendStatStageChangeAbAttr, (_target, user, move) => user.getMoveType(move) === PokemonType.DARK && move.category !== MoveCategory.STATUS, Stat.ATK, 1), new Ability(AbilityId.RATTLED, 5) - .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => { + .attr(PostDefendStatStageChangeAbAttr, (_target, user, move) => { const moveType = user.getMoveType(move); return move.category !== MoveCategory.STATUS && (moveType === PokemonType.DARK || moveType === PokemonType.BUG || moveType === PokemonType.GHOST); @@ -6915,7 +8661,7 @@ export function initAbilities() { .attr(TypeImmunityStatStageChangeAbAttr, PokemonType.GRASS, Stat.ATK, 1) .ignorable(), new Ability(AbilityId.PRANKSTER, 5) - .attr(ChangeMovePriorityAbAttr, (pokemon, move: Move) => move.category === MoveCategory.STATUS, 1), + .attr(ChangeMovePriorityAbAttr, (_pokemon, move: Move) => move.category === MoveCategory.STATUS, 1), new Ability(AbilityId.SAND_FORCE, 5) .attr(MoveTypePowerBoostAbAttr, PokemonType.ROCK, 1.3) .attr(MoveTypePowerBoostAbAttr, PokemonType.GROUND, 1.3) @@ -6966,7 +8712,7 @@ export function initAbilities() { .attr(PokemonTypeChangeAbAttr), //.condition((p) => !p.summonData.abilitiesApplied.includes(AbilityId.PROTEAN)), //Gen 9 Implementation new Ability(AbilityId.FUR_COAT, 6) - .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL, 0.5) + .attr(ReceivedMoveDamageMultiplierAbAttr, (_target, _user, move) => move.category === MoveCategory.PHYSICAL, 0.5) .ignorable(), new Ability(AbilityId.MAGICIAN, 6) .attr(PostAttackStealHeldItemAbAttr), @@ -6974,11 +8720,11 @@ export function initAbilities() { .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.BALLBOMB_MOVE)) .ignorable(), new Ability(AbilityId.COMPETITIVE, 6) - .attr(PostStatStageChangeStatStageChangeAbAttr, (target, statsChanged, stages) => stages < 0, [ Stat.SPATK ], 2), + .attr(PostStatStageChangeStatStageChangeAbAttr, (_target, _statsChanged, stages) => stages < 0, [ Stat.SPATK ], 2), new Ability(AbilityId.STRONG_JAW, 6) - .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.BITING_MOVE), 1.5), + .attr(MovePowerBoostAbAttr, (_user, _target, move) => move.hasFlag(MoveFlags.BITING_MOVE), 1.5), new Ability(AbilityId.REFRIGERATE, 6) - .attr(MoveTypeChangeAbAttr, PokemonType.ICE, 1.2, (user, target, move) => move.type === PokemonType.NORMAL), + .attr(MoveTypeChangeAbAttr, PokemonType.ICE, 1.2, (_user, _target, move) => move.type === PokemonType.NORMAL), new Ability(AbilityId.SWEET_VEIL, 6) .attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.SLEEP) .attr(PostSummonUserFieldRemoveStatusEffectAbAttr, StatusEffect.SLEEP) @@ -6993,20 +8739,20 @@ export function initAbilities() { new Ability(AbilityId.GALE_WINGS, 6) .attr(ChangeMovePriorityAbAttr, (pokemon, move) => pokemon.isFullHp() && pokemon.getMoveType(move) === PokemonType.FLYING, 1), new Ability(AbilityId.MEGA_LAUNCHER, 6) - .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.PULSE_MOVE), 1.5), + .attr(MovePowerBoostAbAttr, (_user, _target, move) => move.hasFlag(MoveFlags.PULSE_MOVE), 1.5), new Ability(AbilityId.GRASS_PELT, 6) .conditionalAttr(getTerrainCondition(TerrainType.GRASSY), StatMultiplierAbAttr, Stat.DEF, 1.5) .ignorable(), new Ability(AbilityId.SYMBIOSIS, 6) .unimplemented(), new Ability(AbilityId.TOUGH_CLAWS, 6) - .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 1.3), + .attr(MovePowerBoostAbAttr, (_user, _target, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 1.3), new Ability(AbilityId.PIXILATE, 6) - .attr(MoveTypeChangeAbAttr, PokemonType.FAIRY, 1.2, (user, target, move) => move.type === PokemonType.NORMAL), + .attr(MoveTypeChangeAbAttr, PokemonType.FAIRY, 1.2, (_user, _target, move) => move.type === PokemonType.NORMAL), new Ability(AbilityId.GOOEY, 6) - .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), Stat.SPD, -1, false), + .attr(PostDefendStatStageChangeAbAttr, (_target, _user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), Stat.SPD, -1, false), new Ability(AbilityId.AERILATE, 6) - .attr(MoveTypeChangeAbAttr, PokemonType.FLYING, 1.2, (user, target, move) => move.type === PokemonType.NORMAL), + .attr(MoveTypeChangeAbAttr, PokemonType.FLYING, 1.2, (_user, _target, move) => move.type === PokemonType.NORMAL), new Ability(AbilityId.PARENTAL_BOND, 6) .attr(AddSecondStrikeAbAttr, 0.25), new Ability(AbilityId.DARK_AURA, 6) @@ -7017,9 +8763,9 @@ export function initAbilities() { .attr(FieldMoveTypePowerBoostAbAttr, PokemonType.FAIRY, 4 / 3), new Ability(AbilityId.AURA_BREAK, 6) .ignorable() - .conditionalAttr(pokemon => globalScene.getField(true).some(p => p.hasAbility(AbilityId.DARK_AURA)), FieldMoveTypePowerBoostAbAttr, PokemonType.DARK, 9 / 16) - .conditionalAttr(pokemon => globalScene.getField(true).some(p => p.hasAbility(AbilityId.FAIRY_AURA)), FieldMoveTypePowerBoostAbAttr, PokemonType.FAIRY, 9 / 16) - .conditionalAttr(pokemon => globalScene.getField(true).some(p => p.hasAbility(AbilityId.DARK_AURA) || p.hasAbility(AbilityId.FAIRY_AURA)), + .conditionalAttr(_pokemon => globalScene.getField(true).some(p => p.hasAbility(AbilityId.DARK_AURA)), FieldMoveTypePowerBoostAbAttr, PokemonType.DARK, 9 / 16) + .conditionalAttr(_pokemon => globalScene.getField(true).some(p => p.hasAbility(AbilityId.FAIRY_AURA)), FieldMoveTypePowerBoostAbAttr, PokemonType.FAIRY, 9 / 16) + .conditionalAttr(_pokemon => globalScene.getField(true).some(p => p.hasAbility(AbilityId.DARK_AURA) || p.hasAbility(AbilityId.FAIRY_AURA)), PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonAuraBreak", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })), new Ability(AbilityId.PRIMORDIAL_SEA, 6) .attr(PostSummonWeatherChangeAbAttr, WeatherType.HEAVY_RAIN) @@ -7037,7 +8783,7 @@ export function initAbilities() { .attr(PreLeaveFieldClearWeatherAbAttr) .bypassFaint(), new Ability(AbilityId.STAMINA, 7) - .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, Stat.DEF, 1), + .attr(PostDefendStatStageChangeAbAttr, (_target, _user, move) => move.category !== MoveCategory.STATUS, Stat.DEF, 1), new Ability(AbilityId.WIMP_OUT, 7) .attr(PostDamageForceSwitchAbAttr) .edgeCase(), // Should not trigger when hurting itself in confusion, causes Fake Out to fail turn 1 and succeed turn 2 if pokemon is switched out before battle start via playing in Switch Mode @@ -7045,9 +8791,9 @@ export function initAbilities() { .attr(PostDamageForceSwitchAbAttr) .edgeCase(), // Should not trigger when hurting itself in confusion, causes Fake Out to fail turn 1 and succeed turn 2 if pokemon is switched out before battle start via playing in Switch Mode new Ability(AbilityId.WATER_COMPACTION, 7) - .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === PokemonType.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2), + .attr(PostDefendStatStageChangeAbAttr, (_target, user, move) => user.getMoveType(move) === PokemonType.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2), new Ability(AbilityId.MERCILESS, 7) - .attr(ConditionalCritAbAttr, (user, target, move) => target?.status?.effect === StatusEffect.TOXIC || target?.status?.effect === StatusEffect.POISON), + .attr(ConditionalCritAbAttr, (_user, target, _move) => target?.status?.effect === StatusEffect.TOXIC || target?.status?.effect === StatusEffect.POISON), new Ability(AbilityId.SHIELDS_DOWN, 7) .attr(PostBattleInitFormChangeAbAttr, () => 0) .attr(PostSummonFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0)) @@ -7061,7 +8807,7 @@ export function initAbilities() { .unsuppressable() .bypassFaint(), new Ability(AbilityId.STAKEOUT, 7) - .attr(MovePowerBoostAbAttr, (user, target, move) => !!target?.turnData.switchedInThisTurn, 2), + .attr(MovePowerBoostAbAttr, (_user, target, _move) => !!target?.turnData.switchedInThisTurn, 2), new Ability(AbilityId.WATER_BUBBLE, 7) .attr(ReceivedTypeDamageMultiplierAbAttr, PokemonType.FIRE, 0.5) .attr(MoveTypePowerBoostAbAttr, PokemonType.WATER, 2) @@ -7071,7 +8817,7 @@ export function initAbilities() { new Ability(AbilityId.STEELWORKER, 7) .attr(MoveTypePowerBoostAbAttr, PokemonType.STEEL), new Ability(AbilityId.BERSERK, 7) - .attr(PostDefendHpGatedStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [ Stat.SPATK ], 1) + .attr(PostDefendHpGatedStatStageChangeAbAttr, (_target, _user, move) => move.category !== MoveCategory.STATUS, 0.5, [ Stat.SPATK ], 1) .condition(getSheerForceHitDisableAbCondition()), new Ability(AbilityId.SLUSH_RUSH, 7) .attr(StatMultiplierAbAttr, Stat.SPD, 2) @@ -7079,9 +8825,9 @@ export function initAbilities() { new Ability(AbilityId.LONG_REACH, 7) .attr(IgnoreContactAbAttr), new Ability(AbilityId.LIQUID_VOICE, 7) - .attr(MoveTypeChangeAbAttr, PokemonType.WATER, 1, (user, target, move) => move.hasFlag(MoveFlags.SOUND_BASED)), + .attr(MoveTypeChangeAbAttr, PokemonType.WATER, 1, (_user, _target, move) => move.hasFlag(MoveFlags.SOUND_BASED)), new Ability(AbilityId.TRIAGE, 7) - .attr(ChangeMovePriorityAbAttr, (pokemon, move) => move.hasFlag(MoveFlags.TRIAGE_MOVE), 3), + .attr(ChangeMovePriorityAbAttr, (_pokemon, move) => move.hasFlag(MoveFlags.TRIAGE_MOVE), 3), new Ability(AbilityId.GALVANIZE, 7) .attr(MoveTypeChangeAbAttr, PokemonType.ELECTRIC, 1.2, (_user, _target, move) => move.type === PokemonType.NORMAL), new Ability(AbilityId.SURGE_SURFER, 7) @@ -7151,7 +8897,7 @@ export function initAbilities() { .attr(AllyMoveCategoryPowerBoostAbAttr, [ MoveCategory.SPECIAL ], 1.3), new Ability(AbilityId.FLUFFY, 7) .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user, target}), 0.5) - .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => user.getMoveType(move) === PokemonType.FIRE, 2) + .attr(ReceivedMoveDamageMultiplierAbAttr, (_target, user, move) => user.getMoveType(move) === PokemonType.FIRE, 2) .ignorable(), new Ability(AbilityId.DAZZLING, 7) .attr(FieldPriorityMoveImmunityAbAttr) @@ -7199,7 +8945,7 @@ export function initAbilities() { new Ability(AbilityId.FULL_METAL_BODY, 7) .attr(ProtectStatAbAttr), new Ability(AbilityId.SHADOW_SHIELD, 7) - .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.isFullHp(), 0.5), + .attr(ReceivedMoveDamageMultiplierAbAttr, (target, _user, _move) => target.isFullHp(), 0.5), new Ability(AbilityId.PRISM_ARMOR, 7) .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75), new Ability(AbilityId.NEUROFORCE, 7) @@ -7215,7 +8961,7 @@ export function initAbilities() { .attr(FetchBallAbAttr) .condition(getOncePerBattleCondition(AbilityId.BALL_FETCH)), new Ability(AbilityId.COTTON_DOWN, 8) - .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, Stat.SPD, -1, false, true) + .attr(PostDefendStatStageChangeAbAttr, (_target, _user, move) => move.category !== MoveCategory.STATUS, Stat.SPD, -1, false, true) .bypassFaint(), new Ability(AbilityId.PROPELLER_TAIL, 8) .attr(BlockRedirectAbAttr), @@ -7238,20 +8984,20 @@ export function initAbilities() { new Ability(AbilityId.STALWART, 8) .attr(BlockRedirectAbAttr), new Ability(AbilityId.STEAM_ENGINE, 8) - .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => { + .attr(PostDefendStatStageChangeAbAttr, (_target, user, move) => { const moveType = user.getMoveType(move); return move.category !== MoveCategory.STATUS && (moveType === PokemonType.FIRE || moveType === PokemonType.WATER); }, Stat.SPD, 6), new Ability(AbilityId.PUNK_ROCK, 8) - .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.SOUND_BASED), 1.3) - .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.hasFlag(MoveFlags.SOUND_BASED), 0.5) + .attr(MovePowerBoostAbAttr, (_user, _target, move) => move.hasFlag(MoveFlags.SOUND_BASED), 1.3) + .attr(ReceivedMoveDamageMultiplierAbAttr, (_target, _user, move) => move.hasFlag(MoveFlags.SOUND_BASED), 0.5) .ignorable(), new Ability(AbilityId.SAND_SPIT, 8) - .attr(PostDefendWeatherChangeAbAttr, WeatherType.SANDSTORM, (target, user, move) => move.category !== MoveCategory.STATUS) + .attr(PostDefendWeatherChangeAbAttr, WeatherType.SANDSTORM, (_target, _user, move) => move.category !== MoveCategory.STATUS) .bypassFaint(), new Ability(AbilityId.ICE_SCALES, 8) - .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.category === MoveCategory.SPECIAL, 0.5) + .attr(ReceivedMoveDamageMultiplierAbAttr, (_target, _user, move) => move.category === MoveCategory.SPECIAL, 0.5) .ignorable(), new Ability(AbilityId.RIPEN, 8) .attr(DoubleBerryEffectAbAttr), @@ -7265,7 +9011,7 @@ export function initAbilities() { // When weather changes to HAIL or SNOW while pokemon is fielded, add BattlerTagType.ICE_FACE .attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.ICE_FACE, 0, WeatherType.HAIL, WeatherType.SNOW) .attr(FormBlockDamageAbAttr, - (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE, + (target, _user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE, (pokemon, abilityName) => i18next.t("abilityTriggers:iceFaceAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName })) .attr(PostBattleInitFormChangeAbAttr, () => 0) .uncopiable() @@ -7343,13 +9089,13 @@ export function initAbilities() { .attr(PostDefendTerrainChangeAbAttr, TerrainType.GRASSY) .bypassFaint(), new Ability(AbilityId.THERMAL_EXCHANGE, 9) - .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === PokemonType.FIRE && move.category !== MoveCategory.STATUS, Stat.ATK, 1) + .attr(PostDefendStatStageChangeAbAttr, (_target, user, move) => user.getMoveType(move) === PokemonType.FIRE && move.category !== MoveCategory.STATUS, Stat.ATK, 1) .attr(StatusEffectImmunityAbAttr, StatusEffect.BURN) .attr(PostSummonHealStatusAbAttr, StatusEffect.BURN) .ignorable(), new Ability(AbilityId.ANGER_SHELL, 9) - .attr(PostDefendHpGatedStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [ Stat.ATK, Stat.SPATK, Stat.SPD ], 1) - .attr(PostDefendHpGatedStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [ Stat.DEF, Stat.SPDEF ], -1) + .attr(PostDefendHpGatedStatStageChangeAbAttr, (_target, _user, move) => move.category !== MoveCategory.STATUS, 0.5, [ Stat.ATK, Stat.SPATK, Stat.SPD ], 1) + .attr(PostDefendHpGatedStatStageChangeAbAttr, (_target, _user, move) => move.category !== MoveCategory.STATUS, 0.5, [ Stat.DEF, Stat.SPDEF ], -1) .condition(getSheerForceHitDisableAbCondition()), new Ability(AbilityId.PURIFYING_SALT, 9) .attr(StatusEffectImmunityAbAttr) @@ -7369,7 +9115,7 @@ export function initAbilities() { new Ability(AbilityId.ROCKY_PAYLOAD, 9) .attr(MoveTypePowerBoostAbAttr, PokemonType.ROCK), new Ability(AbilityId.WIND_POWER, 9) - .attr(PostDefendApplyBattlerTagAbAttr, (target, user, move) => move.hasFlag(MoveFlags.WIND_MOVE), BattlerTagType.CHARGED), + .attr(PostDefendApplyBattlerTagAbAttr, (_target, _user, move) => move.hasFlag(MoveFlags.WIND_MOVE), BattlerTagType.CHARGED), new Ability(AbilityId.ZERO_TO_HERO, 9) .uncopiable() .unreplaceable() @@ -7386,7 +9132,7 @@ export function initAbilities() { .unreplaceable() .edgeCase(), // Encore, Frenzy, and other non-`TURN_END` tags don't lapse correctly on the commanding Pokemon. new Ability(AbilityId.ELECTROMORPHOSIS, 9) - .attr(PostDefendApplyBattlerTagAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, BattlerTagType.CHARGED), + .attr(PostDefendApplyBattlerTagAbAttr, (_target, _user, move) => move.category !== MoveCategory.STATUS, BattlerTagType.CHARGED), new Ability(AbilityId.PROTOSYNTHESIS, 9) .conditionalAttr(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN), PostSummonAddBattlerTagAbAttr, BattlerTagType.PROTOSYNTHESIS, 0, true) .attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.PROTOSYNTHESIS, 0, WeatherType.SUNNY, WeatherType.HARSH_SUN) @@ -7432,14 +9178,14 @@ export function initAbilities() { new Ability(AbilityId.CUD_CHEW, 9) .attr(RepeatBerryNextTurnAbAttr), new Ability(AbilityId.SHARPNESS, 9) - .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.SLICING_MOVE), 1.5), + .attr(MovePowerBoostAbAttr, (_user, _target, move) => move.hasFlag(MoveFlags.SLICING_MOVE), 1.5), new Ability(AbilityId.SUPREME_OVERLORD, 9) - .attr(VariableMovePowerBoostAbAttr, (user, target, move) => 1 + 0.1 * Math.min(user.isPlayer() ? globalScene.arena.playerFaints : globalScene.currentBattle.enemyFaints, 5)) + .attr(VariableMovePowerBoostAbAttr, (user, _target, _move) => 1 + 0.1 * Math.min(user.isPlayer() ? globalScene.arena.playerFaints : globalScene.currentBattle.enemyFaints, 5)) .partial(), // Should only boost once, on summon new Ability(AbilityId.COSTAR, 9) .attr(PostSummonCopyAllyStatsAbAttr), new Ability(AbilityId.TOXIC_DEBRIS, 9) - .attr(PostDefendApplyArenaTrapTagAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL, ArenaTagType.TOXIC_SPIKES) + .attr(PostDefendApplyArenaTrapTagAbAttr, (_target, _user, move) => move.category === MoveCategory.PHYSICAL, ArenaTagType.TOXIC_SPIKES) .bypassFaint(), new Ability(AbilityId.ARMOR_TAIL, 9) .attr(FieldPriorityMoveImmunityAbAttr) @@ -7448,9 +9194,9 @@ export function initAbilities() { .attr(TypeImmunityHealAbAttr, PokemonType.GROUND) .ignorable(), new Ability(AbilityId.MYCELIUM_MIGHT, 9) - .attr(ChangeMovePriorityAbAttr, (pokemon, move) => move.category === MoveCategory.STATUS, -0.2) - .attr(PreventBypassSpeedChanceAbAttr, (pokemon, move) => move.category === MoveCategory.STATUS) - .attr(MoveAbilityBypassAbAttr, (pokemon, move: Move) => move.category === MoveCategory.STATUS), + .attr(ChangeMovePriorityAbAttr, (_pokemon, move) => move.category === MoveCategory.STATUS, -0.2) + .attr(PreventBypassSpeedChanceAbAttr, (_pokemon, move) => move.category === MoveCategory.STATUS) + .attr(MoveAbilityBypassAbAttr, (_pokemon, move: Move) => move.category === MoveCategory.STATUS), new Ability(AbilityId.MINDS_EYE, 9) .attr(IgnoreTypeImmunityAbAttr, PokemonType.GHOST, [ PokemonType.NORMAL, PokemonType.FIGHTING ]) .attr(ProtectStatAbAttr, Stat.ACC) From 1c4edabd1dde2b30179aebe08f9a3bdfad199bc3 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Sun, 8 Jun 2025 01:55:30 -0500 Subject: [PATCH 22/51] [Refactor] Ensure that new phases are created through the phase manager https://github.com/pagefaultgames/pokerogue/pull/5955 * Add newPhase method to phase-manager * Update calls to append/prepend phase to use string phase * Replace instantiations of new phase with phase manager --- src/@types/phase-types.ts | 299 +--------------- src/battle-scene.ts | 47 +-- src/data/abilities/ability.ts | 325 ++++++++++-------- src/data/arena-tag.ts | 61 ++-- src/data/battler-tags.ts | 226 ++++++------ src/data/berry.ts | 37 +- src/data/moves/move.ts | 105 +++--- .../encounters/a-trainers-test-encounter.ts | 3 +- .../encounters/absolute-avarice-encounter.ts | 9 +- .../an-offer-you-cant-refuse-encounter.ts | 3 +- .../encounters/berries-abound-encounter.ts | 9 +- .../encounters/bug-type-superfan-encounter.ts | 7 +- .../encounters/dancing-lessons-encounter.ts | 21 +- .../encounters/dark-deal-encounter.ts | 3 +- .../encounters/delibirdy-encounter.ts | 13 +- .../encounters/fiery-fallout-encounter.ts | 17 +- .../encounters/fight-or-flight-encounter.ts | 9 +- .../encounters/fun-and-games-encounter.ts | 6 +- .../encounters/mysterious-chest-encounter.ts | 3 +- .../encounters/safari-zone-encounter.ts | 6 +- .../slumbering-snorlax-encounter.ts | 3 +- .../teleporting-hijinks-encounter.ts | 9 +- .../encounters/the-strong-stuff-encounter.ts | 9 +- .../the-winstrate-challenge-encounter.ts | 9 +- .../encounters/uncommon-breed-encounter.ts | 9 +- .../utils/encounter-phase-utils.ts | 56 ++- .../utils/encounter-pokemon-utils.ts | 3 +- src/field/arena.ts | 26 +- src/field/pokemon.ts | 51 ++- src/modifier/modifier.ts | 126 ++++--- src/phase-manager.ts | 294 +++++++++++++++- src/phases/attempt-capture-phase.ts | 3 +- src/phases/attempt-run-phase.ts | 9 +- src/phases/battle-end-phase.ts | 3 +- src/phases/berry-phase.ts | 8 +- src/phases/check-status-effect-phase.ts | 3 +- src/phases/check-switch-phase.ts | 8 +- src/phases/command-phase.ts | 9 +- src/phases/egg-lapse-phase.ts | 6 +- src/phases/encounter-phase.ts | 71 ++-- src/phases/evolution-phase.ts | 12 +- src/phases/exp-phase.ts | 3 +- src/phases/faint-phase.ts | 20 +- src/phases/form-change-phase.ts | 3 +- src/phases/game-over-phase.ts | 42 +-- src/phases/level-up-phase.ts | 6 +- src/phases/login-phase.ts | 8 +- src/phases/message-phase.ts | 9 +- src/phases/move-charge-phase.ts | 3 +- src/phases/move-effect-phase.ts | 27 +- src/phases/move-phase.ts | 38 +- src/phases/mystery-encounter-phases.ts | 58 ++-- src/phases/post-game-over-phase.ts | 3 +- src/phases/quiet-form-change-phase.ts | 12 +- src/phases/revival-blessing-phase.ts | 24 +- src/phases/select-biome-phase.ts | 6 +- src/phases/select-modifier-phase.ts | 12 +- src/phases/select-starter-phase.ts | 3 +- src/phases/select-target-phase.ts | 3 +- src/phases/show-ability-phase.ts | 5 +- src/phases/show-party-exp-bar-phase.ts | 6 +- src/phases/stat-stage-change-phase.ts | 23 +- src/phases/summon-phase.ts | 9 +- src/phases/switch-phase.ts | 5 +- src/phases/switch-summon-phase.ts | 3 +- src/phases/title-phase.ts | 23 +- src/phases/trainer-victory-phase.ts | 36 +- src/phases/turn-end-phase.ts | 18 +- src/phases/turn-init-phase.ts | 19 +- src/phases/turn-start-phase.ts | 138 ++++---- src/phases/unavailable-phase.ts | 3 +- src/phases/victory-phase.ts | 62 ++-- src/system/game-data.ts | 11 +- src/ui/challenges-select-ui-handler.ts | 6 +- src/ui/starter-select-ui-handler.ts | 9 +- test/testUtils/gameManager.ts | 4 +- 76 files changed, 1302 insertions(+), 1294 deletions(-) diff --git a/src/@types/phase-types.ts b/src/@types/phase-types.ts index 596d9b15723..1d68c7921dd 100644 --- a/src/@types/phase-types.ts +++ b/src/@types/phase-types.ts @@ -1,286 +1,25 @@ -import type { MoveAnim } from "#app/data/battle-anims"; -import type { AddEnemyBuffModifierPhase } from "#app/phases/add-enemy-buff-modifier-phase"; -import type { AttemptCapturePhase } from "#app/phases/attempt-capture-phase"; -import type { AttemptRunPhase } from "#app/phases/attempt-run-phase"; -import type { BattleEndPhase } from "#app/phases/battle-end-phase"; -import type { BerryPhase } from "#app/phases/berry-phase"; -import type { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase"; -import type { CheckSwitchPhase } from "#app/phases/check-switch-phase"; -import type { CommandPhase } from "#app/phases/command-phase"; -import type { CommonAnimPhase } from "#app/phases/common-anim-phase"; -import type { DamageAnimPhase } from "#app/phases/damage-anim-phase"; -import type { EggHatchPhase } from "#app/phases/egg-hatch-phase"; -import type { EggLapsePhase } from "#app/phases/egg-lapse-phase"; -import type { EggSummaryPhase } from "#app/phases/egg-summary-phase"; -import type { EncounterPhase } from "#app/phases/encounter-phase"; -import type { EndCardPhase } from "#app/phases/end-card-phase"; -import type { EndEvolutionPhase } from "#app/phases/end-evolution-phase"; -import type { EnemyCommandPhase } from "#app/phases/enemy-command-phase"; -import type { EvolutionPhase } from "#app/phases/evolution-phase"; -import type { ExpPhase } from "#app/phases/exp-phase"; -import type { FaintPhase } from "#app/phases/faint-phase"; -import type { FormChangePhase } from "#app/phases/form-change-phase"; -import type { GameOverModifierRewardPhase } from "#app/phases/game-over-modifier-reward-phase"; -import type { GameOverPhase } from "#app/phases/game-over-phase"; -import type { HideAbilityPhase } from "#app/phases/hide-ability-phase"; -import type { HidePartyExpBarPhase } from "#app/phases/hide-party-exp-bar-phase"; -import type { LearnMovePhase } from "#app/phases/learn-move-phase"; -import type { LevelCapPhase } from "#app/phases/level-cap-phase"; -import type { LevelUpPhase } from "#app/phases/level-up-phase"; -import type { LoadMoveAnimPhase } from "#app/phases/load-move-anim-phase"; -import type { LoginPhase } from "#app/phases/login-phase"; -import type { MessagePhase } from "#app/phases/message-phase"; -import type { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; -import type { MoneyRewardPhase } from "#app/phases/money-reward-phase"; -import type { MoveAnimPhase } from "#app/phases/move-anim-phase"; -import type { MoveChargePhase } from "#app/phases/move-charge-phase"; -import type { MoveEffectPhase } from "#app/phases/move-effect-phase"; -import type { MoveEndPhase } from "#app/phases/move-end-phase"; -import type { MoveHeaderPhase } from "#app/phases/move-header-phase"; -import type { MovePhase } from "#app/phases/move-phase"; -import type { - MysteryEncounterPhase, - MysteryEncounterOptionSelectedPhase, - MysteryEncounterBattlePhase, - MysteryEncounterRewardsPhase, - PostMysteryEncounterPhase, - MysteryEncounterBattleStartCleanupPhase, -} from "#app/phases/mystery-encounter-phases"; -import type { NewBattlePhase } from "#app/phases/new-battle-phase"; -import type { NewBiomeEncounterPhase } from "#app/phases/new-biome-encounter-phase"; -import type { NextEncounterPhase } from "#app/phases/next-encounter-phase"; -import type { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase"; -import type { PartyExpPhase } from "#app/phases/party-exp-phase"; -import type { PartyHealPhase } from "#app/phases/party-heal-phase"; -import type { PokemonAnimPhase } from "#app/phases/pokemon-anim-phase"; -import type { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; -import type { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase"; -import type { PostGameOverPhase } from "#app/phases/post-game-over-phase"; -import type { PostSummonPhase } from "#app/phases/post-summon-phase"; -import type { PostTurnStatusEffectPhase } from "#app/phases/post-turn-status-effect-phase"; -import type { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; -import type { ReloadSessionPhase } from "#app/phases/reload-session-phase"; -import type { ResetStatusPhase } from "#app/phases/reset-status-phase"; -import type { ReturnPhase } from "#app/phases/return-phase"; -import type { RevivalBlessingPhase } from "#app/phases/revival-blessing-phase"; -import type { RibbonModifierRewardPhase } from "#app/phases/ribbon-modifier-reward-phase"; -import type { ScanIvsPhase } from "#app/phases/scan-ivs-phase"; -import type { SelectBiomePhase } from "#app/phases/select-biome-phase"; -import type { SelectChallengePhase } from "#app/phases/select-challenge-phase"; -import type { SelectGenderPhase } from "#app/phases/select-gender-phase"; -import type { SelectModifierPhase } from "#app/phases/select-modifier-phase"; -import type { SelectStarterPhase } from "#app/phases/select-starter-phase"; -import type { SelectTargetPhase } from "#app/phases/select-target-phase"; -import type { ShinySparklePhase } from "#app/phases/shiny-sparkle-phase"; -import type { ShowAbilityPhase } from "#app/phases/show-ability-phase"; -import type { ShowPartyExpBarPhase } from "#app/phases/show-party-exp-bar-phase"; -import type { ShowTrainerPhase } from "#app/phases/show-trainer-phase"; -import type { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; -import type { SummonMissingPhase } from "#app/phases/summon-missing-phase"; -import type { SummonPhase } from "#app/phases/summon-phase"; -import type { SwitchBiomePhase } from "#app/phases/switch-biome-phase"; -import type { SwitchPhase } from "#app/phases/switch-phase"; -import type { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; -import type { TeraPhase } from "#app/phases/tera-phase"; -import type { TitlePhase } from "#app/phases/title-phase"; -import type { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase"; -import type { TrainerVictoryPhase } from "#app/phases/trainer-victory-phase"; -import type { TurnEndPhase } from "#app/phases/turn-end-phase"; -import type { TurnInitPhase } from "#app/phases/turn-init-phase"; -import type { TurnStartPhase } from "#app/phases/turn-start-phase"; -import type { UnavailablePhase } from "#app/phases/unavailable-phase"; -import type { UnlockPhase } from "#app/phases/unlock-phase"; -import type { VictoryPhase } from "#app/phases/victory-phase"; -import type { WeatherEffectPhase } from "#app/phases/weather-effect-phase"; +import type { PhaseConstructorMap } from "#app/phase-manager"; -export type PhaseClass = - | typeof AddEnemyBuffModifierPhase - | typeof AttemptCapturePhase - | typeof AttemptRunPhase - | typeof BattleEndPhase - | typeof BerryPhase - | typeof CheckStatusEffectPhase - | typeof CheckSwitchPhase - | typeof CommandPhase - | typeof CommonAnimPhase - | typeof DamageAnimPhase - | typeof EggHatchPhase - | typeof EggLapsePhase - | typeof EggSummaryPhase - | typeof EncounterPhase - | typeof EndCardPhase - | typeof EndEvolutionPhase - | typeof EnemyCommandPhase - | typeof EvolutionPhase - | typeof FormChangePhase - | typeof ExpPhase - | typeof FaintPhase - | typeof FormChangePhase - | typeof GameOverPhase - | typeof GameOverModifierRewardPhase - | typeof HideAbilityPhase - | typeof HidePartyExpBarPhase - | typeof LearnMovePhase - | typeof LevelUpPhase - | typeof LevelCapPhase - | typeof LoadMoveAnimPhase - | typeof LoginPhase - | typeof MessagePhase - | typeof ModifierRewardPhase - | typeof MoneyRewardPhase - | typeof MoveAnimPhase - | typeof MoveChargePhase - | typeof MoveEffectPhase - | typeof MoveEndPhase - | typeof MoveHeaderPhase - | typeof MovePhase - | typeof MysteryEncounterPhase - | typeof MysteryEncounterOptionSelectedPhase - | typeof MysteryEncounterBattlePhase - | typeof MysteryEncounterRewardsPhase - | typeof MysteryEncounterBattleStartCleanupPhase - | typeof MysteryEncounterRewardsPhase - | typeof PostMysteryEncounterPhase - | typeof NewBattlePhase - | typeof NewBiomeEncounterPhase - | typeof NextEncounterPhase - | typeof ObtainStatusEffectPhase - | typeof PartyExpPhase - | typeof PartyHealPhase - | typeof PokemonAnimPhase - | typeof PokemonHealPhase - | typeof PokemonTransformPhase - | typeof PostGameOverPhase - | typeof PostSummonPhase - | typeof PostTurnStatusEffectPhase - | typeof QuietFormChangePhase - | typeof ReloadSessionPhase - | typeof ResetStatusPhase - | typeof ReturnPhase - | typeof RevivalBlessingPhase - | typeof RibbonModifierRewardPhase - | typeof ScanIvsPhase - | typeof SelectBiomePhase - | typeof SelectChallengePhase - | typeof SelectGenderPhase - | typeof SelectModifierPhase - | typeof SelectStarterPhase - | typeof SelectTargetPhase - | typeof ShinySparklePhase - | typeof ShowAbilityPhase - | typeof ShowTrainerPhase - | typeof ShowPartyExpBarPhase - | typeof StatStageChangePhase - | typeof SummonMissingPhase - | typeof SummonPhase - | typeof SwitchBiomePhase - | typeof SwitchPhase - | typeof SwitchSummonPhase - | typeof TeraPhase - | typeof TitlePhase - | typeof ToggleDoublePositionPhase - | typeof TrainerVictoryPhase - | typeof TurnEndPhase - | typeof TurnInitPhase - | typeof TurnStartPhase - | typeof UnavailablePhase - | typeof UnlockPhase - | typeof VictoryPhase - | typeof WeatherEffectPhase; +// 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"; -/** Typescript map used to map a string phase to the actual phase type */ +// 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 = { - AddEnemyBuffModifierPhase: AddEnemyBuffModifierPhase; - AttemptCapturePhase: AttemptCapturePhase; - AttemptRunPhase: AttemptRunPhase; - BattleEndPhase: BattleEndPhase; - BerryPhase: BerryPhase; - CheckStatusEffectPhase: CheckStatusEffectPhase; - CheckSwitchPhase: CheckSwitchPhase; - CommandPhase: CommandPhase; - CommonAnimPhase: CommonAnimPhase; - DamageAnimPhase: DamageAnimPhase; - EggHatchPhase: EggHatchPhase; - EggLapsePhase: EggLapsePhase; - EggSummaryPhase: EggSummaryPhase; - EncounterPhase: EncounterPhase; - EndCardPhase: EndCardPhase; - EndEvolutionPhase: EndEvolutionPhase; - EnemyCommandPhase: EnemyCommandPhase; - EvolutionPhase: EvolutionPhase; - ExpPhase: ExpPhase; - FaintPhase: FaintPhase; - FormChangePhase: FormChangePhase; - GameOverPhase: GameOverPhase; - GameOverModifierRewardPhase: GameOverModifierRewardPhase; - HideAbilityPhase: HideAbilityPhase; - HidePartyExpBarPhase: HidePartyExpBarPhase; - LearnMovePhase: LearnMovePhase; - LevelCapPhase: LevelCapPhase; - LevelUpPhase: LevelUpPhase; - LoadMoveAnimPhase: LoadMoveAnimPhase; - LoginPhase: LoginPhase; - MessagePhase: MessagePhase; - ModifierRewardPhase: ModifierRewardPhase; - MoneyRewardPhase: MoneyRewardPhase; - MoveAnimPhase: MoveAnimPhase; - MoveChargePhase: MoveChargePhase; - MoveEffectPhase: MoveEffectPhase; - MoveEndPhase: MoveEndPhase; - MoveHeaderPhase: MoveHeaderPhase; - MovePhase: MovePhase; - MysteryEncounterPhase: MysteryEncounterPhase; - MysteryEncounterOptionSelectedPhase: MysteryEncounterOptionSelectedPhase; - MysteryEncounterBattlePhase: MysteryEncounterBattlePhase; - MysteryEncounterBattleStartCleanupPhase: MysteryEncounterBattleStartCleanupPhase; - MysteryEncounterRewardsPhase: MysteryEncounterRewardsPhase; - PostMysteryEncounterPhase: PostMysteryEncounterPhase; - NewBattlePhase: NewBattlePhase; - NewBiomeEncounterPhase: NewBiomeEncounterPhase; - NextEncounterPhase: NextEncounterPhase; - ObtainStatusEffectPhase: ObtainStatusEffectPhase; - PartyExpPhase: PartyExpPhase; - PartyHealPhase: PartyHealPhase; - PokemonAnimPhase: PokemonAnimPhase; - PokemonHealPhase: PokemonHealPhase; - PokemonTransformPhase: PokemonTransformPhase; - PostGameOverPhase: PostGameOverPhase; - PostSummonPhase: PostSummonPhase; - PostTurnStatusEffectPhase: PostTurnStatusEffectPhase; - QuietFormChangePhase: QuietFormChangePhase; - ReloadSessionPhase: ReloadSessionPhase; - ResetStatusPhase: ResetStatusPhase; - ReturnPhase: ReturnPhase; - RevivalBlessingPhase: RevivalBlessingPhase; - RibbonModifierRewardPhase: RibbonModifierRewardPhase; - ScanIvsPhase: ScanIvsPhase; - SelectBiomePhase: SelectBiomePhase; - SelectChallengePhase: SelectChallengePhase; - SelectGenderPhase: SelectGenderPhase; - SelectModifierPhase: SelectModifierPhase; - SelectStarterPhase: SelectStarterPhase; - SelectTargetPhase: SelectTargetPhase; - ShinySparklePhase: ShinySparklePhase; - ShowAbilityPhase: ShowAbilityPhase; - ShowPartyExpBarPhase: ShowPartyExpBarPhase; - ShowTrainerPhase: ShowTrainerPhase; - StatStageChangePhase: StatStageChangePhase; - SummonMissingPhase: SummonMissingPhase; - SummonPhase: SummonPhase; - SwitchBiomePhase: SwitchBiomePhase; - SwitchPhase: SwitchPhase; - SwitchSummonPhase: SwitchSummonPhase; - TeraPhase: TeraPhase; - TitlePhase: TitlePhase; - ToggleDoublePositionPhase: ToggleDoublePositionPhase; - TrainerVictoryPhase: TrainerVictoryPhase; - TurnEndPhase: TurnEndPhase; - TurnInitPhase: TurnInitPhase; - TurnStartPhase: TurnStartPhase; - UnavailablePhase: UnavailablePhase; - UnlockPhase: UnlockPhase; - VictoryPhase: VictoryPhase; - WeatherEffectPhase: WeatherEffectPhase; + [K in keyof PhaseConstructorMap]: InstanceType; }; +/** + * 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; diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 23601910e4d..7743302cf94 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -108,7 +108,6 @@ import { SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger, } from "#app/data/pokemon-forms"; -import { FormChangePhase } from "#app/phases/form-change-phase"; import { getTypeRgb } from "#app/data/type"; import { PokemonType } from "#enums/pokemon-type"; import PokemonSpriteSparkleHandler from "#app/field/pokemon-sprite-sparkle-handler"; @@ -142,18 +141,7 @@ import i18next from "i18next"; import { TrainerType } from "#enums/trainer-type"; import { battleSpecDialogue } from "#app/data/dialogue"; import { LoadingScene } from "#app/loading-scene"; -import { LevelCapPhase } from "#app/phases/level-cap-phase"; -import { LoginPhase } from "#app/phases/login-phase"; import type { 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 { TitlePhase } from "#app/phases/title-phase"; -import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { @@ -168,8 +156,6 @@ import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-e import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; 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 { ExpGainsSpeed } from "#enums/exp-gains-speed"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -699,8 +685,8 @@ export default class BattleScene extends SceneBase { ).then(() => loadMoveAnimAssets(defaultMoves, true)), this.initStarterColors(), ]).then(() => { - this.phaseManager.pushPhase(new LoginPhase()); - this.phaseManager.pushPhase(new TitlePhase()); + this.phaseManager.pushNew("LoginPhase"); + this.phaseManager.pushNew("TitlePhase"); this.phaseManager.shiftPhase(); }); @@ -1475,7 +1461,7 @@ export default class BattleScene extends SceneBase { playerField.forEach((pokemon, p) => { if (pokemon.isOnField()) { - this.phaseManager.pushPhase(new ReturnPhase(p)); + this.phaseManager.pushNew("ReturnPhase", p); } }); @@ -1492,7 +1478,7 @@ export default class BattleScene extends SceneBase { } if (!this.trainer.visible) { - this.phaseManager.pushPhase(new ShowTrainerPhase()); + this.phaseManager.pushNew("ShowTrainerPhase"); } } @@ -1501,13 +1487,13 @@ export default class BattleScene extends SceneBase { } if (!this.gameMode.hasRandomBiomes && !isNewBiome) { - this.phaseManager.pushPhase(new NextEncounterPhase()); + this.phaseManager.pushNew("NextEncounterPhase"); } else { - this.phaseManager.pushPhase(new NewBiomeEncounterPhase()); + this.phaseManager.pushNew("NewBiomeEncounterPhase"); const newMaxExpLevel = this.getMaxExpLevel(); if (newMaxExpLevel > maxExpLevel) { - this.phaseManager.pushPhase(new LevelCapPhase()); + this.phaseManager.pushNew("LevelCapPhase"); } } } @@ -3199,9 +3185,9 @@ export default class BattleScene extends SceneBase { if (matchingFormChange) { let phase: Phase; if (pokemon.isPlayer() && !matchingFormChange.quiet) { - phase = new FormChangePhase(pokemon, matchingFormChange, modal); + phase = this.phaseManager.create("FormChangePhase", pokemon, matchingFormChange, modal); } else { - phase = new QuietFormChangePhase(pokemon, matchingFormChange); + phase = this.phaseManager.create("QuietFormChangePhase", pokemon, matchingFormChange); } if (pokemon.isPlayer() && !matchingFormChange.quiet && modal) { this.phaseManager.overridePhase(phase); @@ -3223,11 +3209,12 @@ export default class BattleScene extends SceneBase { fieldAssets?: Phaser.GameObjects.Sprite[], delayed = false, ): boolean { - const phase: Phase = new PokemonAnimPhase(battleAnimType, pokemon, fieldAssets); + const phaseManager = this.phaseManager; + const phase: Phase = phaseManager.create("PokemonAnimPhase", battleAnimType, pokemon, fieldAssets); if (delayed) { - this.phaseManager.pushPhase(phase); + phaseManager.pushPhase(phase); } else { - this.phaseManager.unshiftPhase(phase); + phaseManager.unshiftPhase(phase); } return true; } @@ -3335,9 +3322,9 @@ export default class BattleScene extends SceneBase { this.currentBattle.double = true; const availablePartyMembers = this.getPlayerParty().filter(p => p.isAllowedInBattle()); if (availablePartyMembers.length > 1) { - this.phaseManager.pushPhase(new ToggleDoublePositionPhase(true)); + this.phaseManager.pushNew("ToggleDoublePositionPhase", true); if (!availablePartyMembers[1].isOnField()) { - this.phaseManager.pushPhase(new SummonPhase(1)); + this.phaseManager.pushNew("SummonPhase", 1); } } @@ -3461,8 +3448,8 @@ export default class BattleScene extends SceneBase { const partyMemberIndex = party.indexOf(expPartyMembers[pm]); this.phaseManager.unshiftPhase( expPartyMembers[pm].isOnField() - ? new ExpPhase(partyMemberIndex, exp) - : new ShowPartyExpBarPhase(partyMemberIndex, exp), + ? this.phaseManager.create("ExpPhase", partyMemberIndex, exp) + : this.phaseManager.create("ShowPartyExpBarPhase", partyMemberIndex, exp), ); } } diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 9269f84d269..a79e2206348 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -47,16 +47,8 @@ import { Command } from "#app/ui/command-ui-handler"; import { BerryModifierType } from "#app/modifier/modifier-type"; import { getPokeballName } from "#app/data/pokeball"; import { BattleType } from "#enums/battle-type"; -import { MovePhase } from "#app/phases/move-phase"; -import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; -import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; +import type { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { globalScene } from "#app/global-scene"; -import { SwitchPhase } from "#app/phases/switch-phase"; -import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; -import { BattleEndPhase } from "#app/phases/battle-end-phase"; -import { NewBattlePhase } from "#app/phases/new-battle-phase"; -import { MoveEndPhase } from "#app/phases/move-end-phase"; -import { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase"; import { allAbilities } from "#app/data/data-lists"; import { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr"; import { Ability } from "#app/data/abilities/ability-class"; @@ -77,7 +69,6 @@ import { MoveFlags } from "#enums/MoveFlags"; import { MoveTarget } from "#enums/MoveTarget"; import { MoveCategory } from "#enums/MoveCategory"; import type { BerryType } from "#enums/berry-type"; -import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { CommonAnim } from "../battle-anims"; import { getBerryEffectFunc } from "../berry"; import { BerryUsedEvent } from "#app/events/battle-scene"; @@ -98,7 +89,6 @@ import type { import type { BattlerIndex } from "#app/battle"; import type Move from "#app/data/moves/move"; import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag"; -import { SelectBiomePhase } from "#app/phases/select-biome-phase"; import { noAbilityTypeOverrideMoves } from "../moves/invalid-moves"; export class BlockRecoilDamageAttr extends AbAttr { @@ -200,10 +190,13 @@ export class PostTeraFormChangeStatChangeAbAttr extends AbAttr { const statStageChangePhases: StatStageChangePhase[] = []; if (!simulated) { - statStageChangePhases.push(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages)); + const phaseManager = globalScene.phaseManager; + statStageChangePhases.push( + phaseManager.create("StatStageChangePhase", pokemon.getBattlerIndex(), true, this.stats, this.stages), + ); for (const statStageChangePhase of statStageChangePhases) { - globalScene.phaseManager.unshiftPhase(statStageChangePhase); + phaseManager.unshiftPhase(statStageChangePhase); } } } @@ -581,16 +574,15 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr { super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); if (!pokemon.isFullHp() && !simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - globalScene.phaseManager.unshiftPhase( - new PokemonHealPhase( - pokemon.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / 4), - i18next.t("abilityTriggers:typeImmunityHeal", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - abilityName, - }), - true, - ), + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + pokemon.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / 4), + i18next.t("abilityTriggers:typeImmunityHeal", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + abilityName, + }), + true, ); cancelled.value = true; // Suppresses "No Effect" message } @@ -632,8 +624,12 @@ class TypeImmunityStatStageChangeAbAttr extends TypeImmunityAbAttr { super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); cancelled.value = true; // Suppresses "No Effect" message if (!simulated) { - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, [this.stat], this.stages), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + [this.stat], + this.stages, ); } } @@ -960,8 +956,12 @@ export class MoveImmunityStatStageChangeAbAttr extends MoveImmunityAbAttr { args: any[], ): void { super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, [this.stat], this.stages), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + [this.stat], + this.stages, ); } } @@ -1063,18 +1063,21 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr { const ally = pokemon.getAlly(); const otherPokemon = !isNullOrUndefined(ally) ? pokemon.getOpponents().concat([ally]) : pokemon.getOpponents(); for (const other of otherPokemon) { - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(other.getBattlerIndex(), false, [this.stat], this.stages), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + other.getBattlerIndex(), + false, + [this.stat], + this.stages, ); } } else { - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase( - (this.selfTarget ? pokemon : attacker).getBattlerIndex(), - this.selfTarget, - [this.stat], - this.stages, - ), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + (this.selfTarget ? pokemon : attacker).getBattlerIndex(), + this.selfTarget, + [this.stat], + this.stages, ); } } @@ -1130,13 +1133,12 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr { _args: any[], ): void { if (!simulated) { - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase( - (this.selfTarget ? pokemon : attacker).getBattlerIndex(), - true, - this.stats, - this.stages, - ), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + (this.selfTarget ? pokemon : attacker).getBattlerIndex(), + true, + this.stats, + this.stages, ); } } @@ -1450,8 +1452,12 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr { _args: any[], ): void { if (!simulated) { - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, [this.stat], this.stages), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + [this.stat], + this.stages, ); } } @@ -1766,8 +1772,12 @@ export class PostStatStageChangeStatStageChangeAbAttr extends PostStatStageChang _args: any[], ): void { if (!simulated) { - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.statsToChange, this.stages), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + this.statsToChange, + this.stages, ); } } @@ -2928,9 +2938,7 @@ class PostVictoryStatStageChangeAbAttr extends PostVictoryAbAttr { override applyPostVictory(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { const stat = typeof this.stat === "function" ? this.stat(pokemon) : this.stat; if (!simulated) { - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, [stat], this.stages), - ); + globalScene.phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), true, [stat], this.stages); } } } @@ -2996,9 +3004,7 @@ export class PostKnockOutStatStageChangeAbAttr extends PostKnockOutAbAttr { ): void { const stat = typeof this.stat === "function" ? this.stat(pokemon) : this.stat; if (!simulated) { - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, [stat], this.stages), - ); + globalScene.phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), true, [stat], this.stages); } } } @@ -3112,8 +3118,12 @@ export class PostIntimidateStatStageChangeAbAttr extends AbAttr { _args: any[], ): void { if (!simulated) { - globalScene.phaseManager.pushPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages), + globalScene.phaseManager.pushNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + false, + this.stats, + this.stages, ); } cancelled.value = this.overwrites; @@ -3315,8 +3325,12 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { if (this.selfTarget) { // we unshift the StatStageChangePhase to put it right after the showAbility and not at the end of the // phase list (which could be after CommandPhase for example) - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + this.stats, + this.stages, ); } else { for (const opponent of pokemon.getOpponents()) { @@ -3330,8 +3344,12 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { } } if (!cancelled.value) { - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(opponent.getBattlerIndex(), false, this.stats, this.stages), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + opponent.getBattlerIndex(), + false, + this.stats, + this.stages, ); } } @@ -3357,17 +3375,16 @@ export class PostSummonAllyHealAbAttr extends PostSummonAbAttr { override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { const target = pokemon.getAlly(); if (!simulated && !isNullOrUndefined(target)) { - globalScene.phaseManager.unshiftPhase( - new PokemonHealPhase( - target.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / this.healRatio), - i18next.t("abilityTriggers:postSummonAllyHeal", { - pokemonNameWithAffix: getPokemonNameWithAffix(target), - pokemonName: pokemon.name, - }), - true, - !this.showAnim, - ), + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + target.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / this.healRatio), + i18next.t("abilityTriggers:postSummonAllyHeal", { + pokemonNameWithAffix: getPokemonNameWithAffix(target), + pokemonName: pokemon.name, + }), + true, + !this.showAnim, ); } } @@ -3445,7 +3462,7 @@ export class DownloadAbAttr extends PostSummonAbAttr { } if (!simulated) { - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, 1)); + globalScene.phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), false, this.stats, 1); } } } @@ -3726,8 +3743,11 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { override applyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { const target = this.getTarget(pokemon.getOpponents()); - globalScene.phaseManager.unshiftPhase( - new PokemonTransformPhase(pokemon.getBattlerIndex(), target.getBattlerIndex(), true), + globalScene.phaseManager.unshiftNew( + "PokemonTransformPhase", + pokemon.getBattlerIndex(), + target.getBattlerIndex(), + true, ); } } @@ -4110,8 +4130,17 @@ export class ReflectStatStageChangeAbAttr extends PreStatStageChangeAbAttr { const stages = args[1]; this.reflectedStat = stat; if (!simulated) { - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(attacker.getBattlerIndex(), false, [stat], stages, true, false, true, null, true), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + attacker.getBattlerIndex(), + false, + [stat], + stages, + true, + false, + true, + null, + true, ); } cancelled.value = true; @@ -5212,16 +5241,15 @@ export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr { ): void { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; if (!simulated) { - globalScene.phaseManager.unshiftPhase( - new PokemonHealPhase( - pokemon.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / (16 / this.healFactor)), - i18next.t("abilityTriggers:postWeatherLapseHeal", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - abilityName, - }), - true, - ), + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + pokemon.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / (16 / this.healFactor)), + i18next.t("abilityTriggers:postWeatherLapseHeal", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + abilityName, + }), + true, ); } } @@ -5366,13 +5394,12 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr { override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - globalScene.phaseManager.unshiftPhase( - new PokemonHealPhase( - pokemon.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / 8), - i18next.t("abilityTriggers:poisonHeal", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName }), - true, - ), + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + pokemon.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / 8), + i18next.t("abilityTriggers:poisonHeal", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName }), + true, ); } } @@ -5529,8 +5556,11 @@ export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr { _cancelled: BooleanHolder | null, _args: any[], ): void { - globalScene.phaseManager.unshiftPhase( - new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM), + globalScene.phaseManager.unshiftNew( + "CommonAnimPhase", + pokemon.getBattlerIndex(), + pokemon.getBattlerIndex(), + CommonAnim.USE_ITEM, ); // Re-apply effects of all berries previously scarfed. @@ -5593,15 +5623,11 @@ export class MoodyAbAttr extends PostTurnAbAttr { if (canRaise.length > 0) { const raisedStat = canRaise[pokemon.randBattleSeedInt(canRaise.length)]; canLower = canRaise.filter(s => s !== raisedStat); - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, [raisedStat], 2), - ); + globalScene.phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), true, [raisedStat], 2); } if (canLower.length > 0) { const loweredStat = canLower[pokemon.randBattleSeedInt(canLower.length)]; - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, [loweredStat], -1), - ); + globalScene.phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), true, [loweredStat], -1); } } } @@ -5617,7 +5643,7 @@ export class SpeedBoostAbAttr extends PostTurnAbAttr { } override applyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.SPD], 1)); + globalScene.phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), true, [Stat.SPD], 1); } } @@ -5629,16 +5655,15 @@ export class PostTurnHealAbAttr extends PostTurnAbAttr { override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - globalScene.phaseManager.unshiftPhase( - new PokemonHealPhase( - pokemon.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / 16), - i18next.t("abilityTriggers:postTurnHeal", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - abilityName, - }), - true, - ), + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + pokemon.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / 16), + i18next.t("abilityTriggers:postTurnHeal", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + abilityName, + }), + true, ); } } @@ -5857,13 +5882,14 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { ): void { if (!simulated) { dancer.turnData.extraTurns++; + const phaseManager = globalScene.phaseManager; // If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance if (move.getMove() instanceof AttackMove || move.getMove() instanceof StatusMove) { const target = this.getTarget(dancer, source, targets); - globalScene.phaseManager.unshiftPhase(new MovePhase(dancer, target, move, true, true)); + phaseManager.unshiftNew("MovePhase", dancer, target, move, true, true); } else if (move.getMove() instanceof SelfStatusMove) { // If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself - globalScene.phaseManager.unshiftPhase(new MovePhase(dancer, [dancer.getBattlerIndex()], move, true, true)); + phaseManager.unshiftNew("MovePhase", dancer, [dancer.getBattlerIndex()], move, true, true); } } } @@ -5949,16 +5975,15 @@ export class StatStageChangeCopyAbAttr extends AbAttr { args: any[], ): void { if (!simulated) { - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase( - pokemon.getBattlerIndex(), - true, - args[0] as BattleStat[], - args[1] as number, - true, - false, - false, - ), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + args[0] as BattleStat[], + args[1] as number, + true, + false, + false, ); } } @@ -6058,16 +6083,15 @@ export class HealFromBerryUseAbAttr extends AbAttr { } const { name: abilityName } = passive ? pokemon.getPassiveAbility() : pokemon.getAbility(); - globalScene.phaseManager.unshiftPhase( - new PokemonHealPhase( - pokemon.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() * this.healPercent), - i18next.t("abilityTriggers:healFromBerryUse", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - abilityName, - }), - true, - ), + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + pokemon.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() * this.healPercent), + i18next.t("abilityTriggers:healFromBerryUse", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + abilityName, + }), + true, ); } } @@ -6502,8 +6526,12 @@ export class FlinchStatStageChangeAbAttr extends FlinchEffectAbAttr { _args: any[], ): void { if (!simulated) { - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + this.stats, + this.stages, ); } } @@ -7241,9 +7269,13 @@ class ForceSwitchOutHelper { if (switchOutTarget.hp > 0) { switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); - globalScene.phaseManager.prependToPhase( - new SwitchPhase(this.switchType, switchOutTarget.getFieldIndex(), true, true), - MoveEndPhase, + globalScene.phaseManager.prependNewToPhase( + "MoveEndPhase", + "SwitchPhase", + this.switchType, + switchOutTarget.getFieldIndex(), + true, + true, ); return true; } @@ -7260,9 +7292,14 @@ class ForceSwitchOutHelper { const summonIndex = globalScene.currentBattle.trainer ? globalScene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0; - globalScene.phaseManager.prependToPhase( - new SwitchSummonPhase(this.switchType, switchOutTarget.getFieldIndex(), summonIndex, false, false), - MoveEndPhase, + globalScene.phaseManager.prependNewToPhase( + "MoveEndPhase", + "SwitchSummonPhase", + this.switchType, + switchOutTarget.getFieldIndex(), + summonIndex, + false, + false, ); return true; } @@ -7294,13 +7331,13 @@ class ForceSwitchOutHelper { globalScene.clearEnemyHeldItemModifiers(); if (switchOutTarget.hp) { - globalScene.phaseManager.pushPhase(new BattleEndPhase(false)); + globalScene.phaseManager.pushNew("BattleEndPhase", false); if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) { - globalScene.phaseManager.pushPhase(new SelectBiomePhase()); + globalScene.phaseManager.pushNew("SelectBiomePhase"); } - globalScene.phaseManager.pushPhase(new NewBattlePhase()); + globalScene.phaseManager.pushNew("NewBattlePhase"); } } } diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 70fceb17c49..28b8c6acd41 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -26,10 +26,6 @@ import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; 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 { BOTH, @@ -568,9 +564,7 @@ class WishTag extends ArenaTag { const target = globalScene.getField()[this.battlerIndex]; if (target?.isActive(true)) { globalScene.phaseManager.queueMessage(this.triggerMessage); - globalScene.phaseManager.unshiftPhase( - new PokemonHealPhase(target.getBattlerIndex(), this.healHp, null, true, false), - ); + globalScene.phaseManager.unshiftNew("PokemonHealPhase", target.getBattlerIndex(), this.healHp, null, true, false); } } } @@ -893,8 +887,13 @@ export class DelayedAttackTag extends ArenaTag { const ret = super.lapse(arena); if (!ret) { - globalScene.phaseManager.unshiftPhase( - new MoveEffectPhase(this.sourceId!, [this.targetIndex], allMoves[this.sourceMove!], false, true), + globalScene.phaseManager.unshiftNew( + "MoveEffectPhase", + this.sourceId!, + [this.targetIndex], + allMoves[this.sourceMove!], + false, + true, ); // TODO: are those bangs correct? } @@ -1028,19 +1027,18 @@ class StickyWebTag extends ArenaTrapTag { }), ); const stages = new NumberHolder(-1); - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase( - pokemon.getBattlerIndex(), - false, - [Stat.SPD], - stages.value, - true, - false, - true, - null, - false, - true, - ), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + false, + [Stat.SPD], + stages.value, + true, + false, + true, + null, + false, + true, ); return true; } @@ -1138,26 +1136,26 @@ class TailwindTag extends ArenaTag { const source = globalScene.getPokemonById(this.sourceId!); //TODO: this bang is questionable! const party = (source?.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField()) ?? []; + const phaseManager = globalScene.phaseManager; for (const pokemon of party) { // Apply the CHARGED tag to party members with the WIND_POWER ability if (pokemon.hasAbility(AbilityId.WIND_POWER) && !pokemon.getTag(BattlerTagType.CHARGED)) { pokemon.addTag(BattlerTagType.CHARGED); - globalScene.phaseManager.queueMessage( + phaseManager.queueMessage( i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: this.getMoveName(), }), ); } + // Raise attack by one stage if party member has WIND_RIDER ability // TODO: Ability displays should be handled by the ability if (pokemon.hasAbility(AbilityId.WIND_RIDER)) { - globalScene.phaseManager.queueAbilityDisplay(pokemon, false, true); - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.ATK], 1, true), - ); - globalScene.phaseManager.queueAbilityDisplay(pokemon, false, false); + phaseManager.queueAbilityDisplay(pokemon, false, true); + phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), true, [Stat.ATK], 1, true); + phaseManager.queueAbilityDisplay(pokemon, false, false); } } } @@ -1319,8 +1317,11 @@ class FireGrassPledgeTag extends ArenaTag { }), ); // TODO: Replace this with a proper animation - globalScene.phaseManager.unshiftPhase( - new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.MAGMA_STORM), + globalScene.phaseManager.unshiftNew( + "CommonAnimPhase", + pokemon.getBattlerIndex(), + pokemon.getBattlerIndex(), + CommonAnim.MAGMA_STORM, ); pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT }); }); diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index d0b4620d8eb..d4f62237446 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -27,12 +27,9 @@ import { PokemonType } from "#enums/pokemon-type"; import type Pokemon from "#app/field/pokemon"; import { HitResult, MoveResult } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; -import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import type { MoveEffectPhase } from "#app/phases/move-effect-phase"; -import { MovePhase } from "#app/phases/move-phase"; -import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; +import type { MovePhase } from "#app/phases/move-phase"; import type { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase"; -import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import i18next from "#app/plugins/i18n"; import { BooleanHolder, getFrameMs, NumberHolder, toDmgValue } from "#app/utils/common"; import { AbilityId } from "#enums/ability-id"; @@ -560,7 +557,7 @@ export class ShellTrapTag extends BattlerTag { // Only shift MovePhase timing if it's not already next up if (shellTrapPhaseIndex !== -1 && shellTrapPhaseIndex !== firstMovePhaseIndex) { const shellTrapMovePhase = globalScene.phaseManager.phaseQueue.splice(shellTrapPhaseIndex, 1)[0]; - globalScene.phaseManager.prependToPhase(shellTrapMovePhase, MovePhase); + globalScene.phaseManager.prependToPhase(shellTrapMovePhase, "MovePhase"); } this.activated = true; @@ -719,9 +716,7 @@ export class ConfusedTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - globalScene.phaseManager.unshiftPhase( - new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION), - ); + globalScene.phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION); globalScene.phaseManager.queueMessage( i18next.t("battlerTags:confusedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), @@ -756,14 +751,14 @@ export class ConfusedTag extends BattlerTag { return false; } - globalScene.phaseManager.queueMessage( + const phaseManager = globalScene.phaseManager; + + phaseManager.queueMessage( i18next.t("battlerTags:confusedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), ); - globalScene.phaseManager.unshiftPhase( - new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION), - ); + phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION); // 1/3 chance of hitting self with a 40 base power move if (pokemon.randBattleSeedInt(3) === 0 || Overrides.CONFUSION_ACTIVATION_OVERRIDE === true) { @@ -773,9 +768,9 @@ export class ConfusedTag extends BattlerTag { ((((2 * pokemon.level) / 5 + 2) * 40 * atk) / def / 50 + 2) * (pokemon.randBattleSeedIntRange(85, 100) / 100), ); // Intentionally don't increment rage fist's hitCount - globalScene.phaseManager.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself")); + phaseManager.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself")); pokemon.damageAndUpdate(damage, { result: HitResult.CONFUSION }); - (globalScene.phaseManager.getCurrentPhase() as MovePhase).cancel(); + (phaseManager.getCurrentPhase() as MovePhase).cancel(); } return true; @@ -881,24 +876,24 @@ export class InfatuatedTag extends BattlerTag { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); + const phaseManager = globalScene.phaseManager; + if (ret) { - globalScene.phaseManager.queueMessage( + phaseManager.queueMessage( i18next.t("battlerTags:infatuatedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), sourcePokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct? }), ); - globalScene.phaseManager.unshiftPhase( - new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT), - ); + phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT); if (pokemon.randBattleSeedInt(2)) { - globalScene.phaseManager.queueMessage( + phaseManager.queueMessage( i18next.t("battlerTags:infatuatedLapseImmobilize", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), ); - (globalScene.phaseManager.getCurrentPhase() as MovePhase).cancel(); + (phaseManager.getCurrentPhase() as MovePhase).cancel(); } } @@ -965,26 +960,28 @@ export class SeedTag extends BattlerTag { applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { - globalScene.phaseManager.unshiftPhase( - new CommonAnimPhase(source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED), + globalScene.phaseManager.unshiftNew( + "CommonAnimPhase", + source.getBattlerIndex(), + pokemon.getBattlerIndex(), + CommonAnim.LEECH_SEED, ); const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT }); const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false); - globalScene.phaseManager.unshiftPhase( - new PokemonHealPhase( - source.getBattlerIndex(), - !reverseDrain ? damage : damage * -1, - !reverseDrain - ? i18next.t("battlerTags:seededLapse", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - }) - : i18next.t("battlerTags:seededLapseShed", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - }), - false, - true, - ), + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + source.getBattlerIndex(), + !reverseDrain ? damage : damage * -1, + !reverseDrain + ? i18next.t("battlerTags:seededLapse", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + }) + : i18next.t("battlerTags:seededLapseShed", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + }), + false, + true, ); } } @@ -1039,9 +1036,9 @@ export class PowderTag extends BattlerTag { movePhase.fail(); movePhase.showMoveText(); - globalScene.phaseManager.unshiftPhase( - new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.POWDER), - ); + const idx = pokemon.getBattlerIndex(); + + globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER); const cancelDamage = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelDamage); @@ -1088,14 +1085,13 @@ export class NightmareTag extends BattlerTag { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (ret) { - globalScene.phaseManager.queueMessage( + const phaseManager = globalScene.phaseManager; + phaseManager.queueMessage( i18next.t("battlerTags:nightmareLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), ); - globalScene.phaseManager.unshiftPhase( - new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE), - ); // TODO: Update animation type + phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE); // TODO: Update animation type const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); @@ -1194,7 +1190,7 @@ export class EncoreTag extends MoveRestrictionBattlerTag { const lastMove = pokemon.getLastXMoves(1)[0]; globalScene.phaseManager.tryReplacePhase( m => m.is("MovePhase") && m.pokemon === pokemon, - new MovePhase(pokemon, lastMove.targets ?? [], movesetMove), + globalScene.phaseManager.create("MovePhase", pokemon, lastMove.targets ?? [], movesetMove), ); } } @@ -1276,15 +1272,14 @@ export class IngrainTag extends TrappedTag { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (ret) { - globalScene.phaseManager.unshiftPhase( - new PokemonHealPhase( - pokemon.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / 16), - i18next.t("battlerTags:ingrainLapse", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - }), - true, - ), + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + pokemon.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / 16), + i18next.t("battlerTags:ingrainLapse", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + }), + true, ); } @@ -1315,8 +1310,12 @@ export class OctolockTag extends TrappedTag { const shouldLapse = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (shouldLapse) { - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), false, [Stat.DEF, Stat.SPDEF], -1), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + false, + [Stat.DEF, Stat.SPDEF], + -1, ); return true; } @@ -1344,16 +1343,15 @@ export class AquaRingTag extends BattlerTag { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (ret) { - globalScene.phaseManager.unshiftPhase( - new PokemonHealPhase( - pokemon.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / 16), - i18next.t("battlerTags:aquaRingLapse", { - moveName: this.getMoveName(), - pokemonName: getPokemonNameWithAffix(pokemon), - }), - true, - ), + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + pokemon.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / 16), + i18next.t("battlerTags:aquaRingLapse", { + moveName: this.getMoveName(), + pokemonName: getPokemonNameWithAffix(pokemon), + }), + true, ); } @@ -1445,13 +1443,14 @@ export abstract class DamagingTrapTag extends TrappedTag { const ret = super.lapse(pokemon, lapseType); if (ret) { - globalScene.phaseManager.queueMessage( + const phaseManager = globalScene.phaseManager; + phaseManager.queueMessage( i18next.t("battlerTags:damagingTrapLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: this.getMoveName(), }), ); - globalScene.phaseManager.unshiftPhase(new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, this.commonAnim)); + phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, this.commonAnim); const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); @@ -1764,8 +1763,12 @@ export class ContactStatStageChangeProtectedTag extends DamageProtectedTag { * @param user - The pokemon that is being attacked and has the tag */ override onContact(attacker: Pokemon, _user: Pokemon): void { - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(attacker.getBattlerIndex(), false, [this.stat], this.levels), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + attacker.getBattlerIndex(), + false, + [this.stat], + this.levels, ); } } @@ -2281,8 +2284,11 @@ export class SaltCuredTag extends BattlerTag { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (ret) { - globalScene.phaseManager.unshiftPhase( - new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE), + globalScene.phaseManager.unshiftNew( + "CommonAnimPhase", + pokemon.getBattlerIndex(), + pokemon.getBattlerIndex(), + CommonAnim.SALT_CURE, ); const cancelled = new BooleanHolder(false); @@ -2332,8 +2338,11 @@ export class CursedTag extends BattlerTag { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (ret) { - globalScene.phaseManager.unshiftPhase( - new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE), + globalScene.phaseManager.unshiftNew( + "CommonAnimPhase", + pokemon.getBattlerIndex(), + pokemon.getBattlerIndex(), + CommonAnim.SALT_CURE, ); const cancelled = new BooleanHolder(false); @@ -2509,13 +2518,12 @@ export class CommandedTag extends BattlerTag { /** Caches the Tatsugiri's form key and sharply boosts the tagged Pokemon's stats */ override onAdd(pokemon: Pokemon): void { this._tatsugiriFormKey = this.getSourcePokemon()?.getFormKey() ?? "curly"; - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase( - pokemon.getBattlerIndex(), - true, - [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], - 2, - ), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], + 2, ); } @@ -2592,17 +2600,16 @@ export class StockpilingTag extends BattlerTag { ); // Attempt to increase DEF and SPDEF by one stage, keeping track of successful changes. - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase( - pokemon.getBattlerIndex(), - true, - [Stat.SPDEF, Stat.DEF], - 1, - true, - false, - true, - this.onStatStagesChanged, - ), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + [Stat.SPDEF, Stat.DEF], + 1, + true, + false, + true, + this.onStatStagesChanged, ); } } @@ -2620,14 +2627,28 @@ export class StockpilingTag extends BattlerTag { const spDefChange = this.statChangeCounts[Stat.SPDEF]; if (defChange) { - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.DEF], -defChange, true, false, true), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + [Stat.DEF], + -defChange, + true, + false, + true, ); } if (spDefChange) { - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.SPDEF], -spDefChange, true, false, true), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + [Stat.SPDEF], + -spDefChange, + true, + false, + true, ); } } @@ -2667,9 +2688,7 @@ export class GulpMissileTag extends BattlerTag { } if (this.tagType === BattlerTagType.GULP_MISSILE_ARROKUDA) { - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(attacker.getBattlerIndex(), false, [Stat.DEF], -1), - ); + globalScene.phaseManager.unshiftNew("StatStageChangePhase", attacker.getBattlerIndex(), false, [Stat.DEF], -1); } else { attacker.trySetStatus(StatusEffect.PARALYSIS, true, pokemon); } @@ -3290,8 +3309,15 @@ export class SyrupBombTag extends BattlerTag { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), ); - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.SPD], -1, true, false, true), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + [Stat.SPD], + -1, + true, + false, + true, ); return --this.turnCount > 0; } diff --git a/src/data/berry.ts b/src/data/berry.ts index defc9b85541..52ea1c44391 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -8,8 +8,6 @@ import i18next from "i18next"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; 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"; export function getBerryName(berryType: BerryType): string { @@ -75,16 +73,15 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { { const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4)); applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, hpHealed); - globalScene.phaseManager.unshiftPhase( - new PokemonHealPhase( - consumer.getBattlerIndex(), - hpHealed.value, - i18next.t("battle:hpHealBerry", { - pokemonNameWithAffix: getPokemonNameWithAffix(consumer), - berryName: getBerryName(berryType), - }), - true, - ), + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + consumer.getBattlerIndex(), + hpHealed.value, + i18next.t("battle:hpHealBerry", { + pokemonNameWithAffix: getPokemonNameWithAffix(consumer), + berryName: getBerryName(berryType), + }), + true, ); } break; @@ -109,8 +106,12 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { const stat: BattleStat = berryType - BerryType.ENIGMA; const statStages = new NumberHolder(1); applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, statStages); - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(consumer.getBattlerIndex(), true, [stat], statStages.value), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + consumer.getBattlerIndex(), + true, + [stat], + statStages.value, ); } break; @@ -126,8 +127,12 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { const randStat = randSeedInt(Stat.SPD, Stat.ATK); const stages = new NumberHolder(2); applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, stages); - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(consumer.getBattlerIndex(), true, [randStat], stages.value), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + consumer.getBattlerIndex(), + true, + [randStat], + stages.value, ); } break; diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index d44ecbed4cf..976c0cd7d97 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -1890,8 +1890,8 @@ export class HealAttr extends MoveEffectAttr { * This heals the target and shows the appropriate message. */ addHealPhase(target: Pokemon, healRatio: number) { - globalScene.phaseManager.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), - toDmgValue(target.getMaxHp() * healRatio), i18next.t("moveTriggers:healHp", { pokemonName: getPokemonNameWithAffix(target) }), true, !this.showAnim)); + globalScene.phaseManager.unshiftNew("PokemonHealPhase", target.getBattlerIndex(), + toDmgValue(target.getMaxHp() * healRatio), i18next.t("moveTriggers:healHp", { pokemonName: getPokemonNameWithAffix(target) }), true, !this.showAnim); } getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { @@ -2021,8 +2021,10 @@ export class SacrificialFullRestoreAttr extends SacrificialAttr { 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); - globalScene.phaseManager.pushPhase( - new PokemonHealPhase( + const pm = globalScene.phaseManager; + + pm.pushPhase( + pm.create("PokemonHealPhase", user.getBattlerIndex(), maxPartyMemberHp, i18next.t(this.moveMessage, { pokemonName: getPokemonNameWithAffix(user) }), @@ -2233,7 +2235,7 @@ export class HitHealAttr extends MoveEffectAttr { message = ""; } } - globalScene.phaseManager.unshiftPhase(new PokemonHealPhase(user.getBattlerIndex(), healAmount, message, false, true)); + globalScene.phaseManager.unshiftNew("PokemonHealPhase", user.getBattlerIndex(), healAmount, message, false, true); return true; } @@ -3067,7 +3069,7 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr { if (!virtual) { overridden.value = true; - globalScene.phaseManager.unshiftPhase(new MoveAnimPhase(new MoveChargeAnim(this.chargeAnim, move.id, user))); + globalScene.phaseManager.unshiftNew("MoveAnimPhase", new MoveChargeAnim(this.chargeAnim, move.id, 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 }); const side = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; @@ -3125,7 +3127,7 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr { const allyMovePhaseIndex = globalScene.phaseManager.phaseQueue.indexOf(allyMovePhase); const firstMovePhaseIndex = globalScene.phaseManager.phaseQueue.findIndex((phase) => phase.is("MovePhase")); if (allyMovePhaseIndex !== firstMovePhaseIndex) { - globalScene.phaseManager.prependToPhase(globalScene.phaseManager.phaseQueue.splice(allyMovePhaseIndex, 1)[0], MovePhase); + globalScene.phaseManager.prependToPhase(globalScene.phaseManager.phaseQueue.splice(allyMovePhaseIndex, 1)[0], "MovePhase"); } overridden.value = true; @@ -3207,7 +3209,7 @@ export class StatStageChangeAttr extends MoveEffectAttr { const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); if (moveChance < 0 || moveChance === 100 || user.randBattleSeedInt(100) < moveChance) { const stages = this.getLevels(user); - globalScene.phaseManager.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; } @@ -3432,7 +3434,7 @@ export class AcupressureStatStageChangeAttr extends MoveEffectAttr { const randStats = BATTLE_STATS.filter((s) => target.getStatStage(s) < 6); if (randStats.length > 0) { const boostStat = [ randStats[user.randBattleSeedInt(randStats.length)] ]; - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(target.getBattlerIndex(), this.selfTarget, boostStat, 2)); + globalScene.phaseManager.unshiftNew("StatStageChangePhase", target.getBattlerIndex(), this.selfTarget, boostStat, 2); return true; } return false; @@ -3510,7 +3512,7 @@ export class OrderUpStatBoostAttr extends MoveEffectAttr { break; } - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(user.getBattlerIndex(), this.selfTarget, [ increasedStat ], 1)); + globalScene.phaseManager.unshiftNew("StatStageChangePhase", user.getBattlerIndex(), this.selfTarget, [ increasedStat ], 1); return true; } } @@ -4227,8 +4229,8 @@ export class PresentPowerAttr extends VariablePowerAttr { // If this move is multi-hit, disable all other hits user.turnData.hitCount = 1; user.turnData.hitsLeft = 1; - globalScene.phaseManager.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), - toDmgValue(target.getMaxHp() / 4), i18next.t("moveTriggers:regainedHealth", { pokemonName: getPokemonNameWithAffix(target) }), true)); + globalScene.phaseManager.unshiftNew("PokemonHealPhase", target.getBattlerIndex(), + toDmgValue(target.getMaxHp() / 4), i18next.t("moveTriggers:regainedHealth", { pokemonName: getPokemonNameWithAffix(target) }), true); } return true; @@ -4488,7 +4490,7 @@ export class CueNextRoundAttr extends MoveEffectAttr { const nextRoundIndex = globalScene.phaseManager.phaseQueue.indexOf(nextRoundPhase); const nextMoveIndex = globalScene.phaseManager.phaseQueue.findIndex(phase => phase.is("MovePhase")); if (nextRoundIndex !== nextMoveIndex) { - globalScene.phaseManager.prependToPhase(globalScene.phaseManager.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) @@ -4546,7 +4548,7 @@ export class SpectralThiefAttr extends StatChangeBeforeDmgCalcAttr { */ const availableToSteal = Math.min(statStageValueTarget, 6 - statStageValueUser); - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(user.getBattlerIndex(), this.selfTarget, [ s ], availableToSteal)); + globalScene.phaseManager.unshiftNew("StatStageChangePhase", user.getBattlerIndex(), this.selfTarget, [ s ], availableToSteal); target.setStatStage(s, statStageValueTarget - availableToSteal); } } @@ -5680,8 +5682,8 @@ export class CurseAttr extends MoveEffectAttr { target.addTag(BattlerTagType.CURSED, 0, move.id, user.id); return true; } else { - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(user.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF ], 1)); - globalScene.phaseManager.unshiftPhase(new StatStageChangePhase(user.getBattlerIndex(), true, [ Stat.SPD ], -1)); + globalScene.phaseManager.unshiftNew("StatStageChangePhase", user.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF ], 1); + globalScene.phaseManager.unshiftNew("StatStageChangePhase", user.getBattlerIndex(), true, [ Stat.SPD ], -1); return true; } } @@ -6154,7 +6156,7 @@ export class RevivalBlessingAttr extends MoveEffectAttr { override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { // If user is player, checks if the user has fainted pokemon if (user.isPlayer()) { - globalScene.phaseManager.unshiftPhase(new RevivalBlessingPhase(user)); + globalScene.phaseManager.unshiftNew("RevivalBlessingPhase", user); return true; } 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 @@ -6177,7 +6179,7 @@ export class RevivalBlessingAttr extends MoveEffectAttr { if (user.fieldPosition === FieldPosition.CENTER) { user.setFieldPosition(FieldPosition.LEFT); } - globalScene.phaseManager.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, false)); + globalScene.phaseManager.unshiftNew("SwitchSummonPhase", SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, false); } } return true; @@ -6255,26 +6257,23 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { if (this.switchType === SwitchType.FORCE_SWITCH) { switchOutTarget.leaveField(true); const slotIndex = eligibleNewIndices[user.randBattleSeedInt(eligibleNewIndices.length)]; - globalScene.phaseManager.prependToPhase( - new SwitchSummonPhase( - this.switchType, - switchOutTarget.getFieldIndex(), - slotIndex, - false, - true - ), - MoveEndPhase + globalScene.phaseManager.prependNewToPhase( + "MoveEndPhase", + "SwitchSummonPhase", + this.switchType, + switchOutTarget.getFieldIndex(), + slotIndex, + false, + true ); } else { switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); - globalScene.phaseManager.prependToPhase( - new SwitchPhase( + globalScene.phaseManager.prependNewToPhase("MoveEndPhase", + "SwitchPhase", this.switchType, switchOutTarget.getFieldIndex(), true, true - ), - MoveEndPhase ); return true; } @@ -6298,27 +6297,23 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { if (this.switchType === SwitchType.FORCE_SWITCH) { switchOutTarget.leaveField(true); const slotIndex = eligibleNewIndices[user.randBattleSeedInt(eligibleNewIndices.length)]; - globalScene.phaseManager.prependToPhase( - new SwitchSummonPhase( + globalScene.phaseManager.prependNewToPhase("MoveEndPhase", + "SwitchSummonPhase", this.switchType, switchOutTarget.getFieldIndex(), slotIndex, false, false - ), - MoveEndPhase ); } else { switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); - globalScene.phaseManager.prependToPhase( - new SwitchSummonPhase( - this.switchType, - switchOutTarget.getFieldIndex(), - (globalScene.currentBattle.trainer ? globalScene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0), - false, - false - ), - MoveEndPhase + globalScene.phaseManager.prependNewToPhase("MoveEndPhase", + "SwitchSummonPhase", + this.switchType, + switchOutTarget.getFieldIndex(), + (globalScene.currentBattle.trainer ? globalScene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0), + false, + false ); } } @@ -6351,13 +6346,13 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { globalScene.clearEnemyHeldItemModifiers(switchOutTarget); if (!allyPokemon?.isActive(true) && switchOutTarget.hp) { - globalScene.phaseManager.pushPhase(new BattleEndPhase(false)); + globalScene.phaseManager.pushNew("BattleEndPhase", false); if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) { - globalScene.phaseManager.pushPhase(new SelectBiomePhase()); + globalScene.phaseManager.pushNew("SelectBiomePhase"); } - globalScene.phaseManager.pushPhase(new NewBattlePhase()); + globalScene.phaseManager.pushNew("NewBattlePhase"); } } @@ -6733,8 +6728,8 @@ class CallMoveAttr extends OverrideMoveEffectAttr { ? moveTargets.targets : [ 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 }); - globalScene.phaseManager.unshiftPhase(new LoadMoveAnimPhase(move.id)); - globalScene.phaseManager.unshiftPhase(new MovePhase(user, targets, new PokemonMove(move.id, 0, 0, true), true, true)); + globalScene.phaseManager.unshiftNew("LoadMoveAnimPhase", move.id); + globalScene.phaseManager.unshiftNew("MovePhase", user, targets, new PokemonMove(move.id, 0, 0, true), true, true); return true; } } @@ -6962,8 +6957,8 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr { } user.getMoveQueue().push({ move: moveId, targets: [ target.getBattlerIndex() ], ignorePP: true }); - globalScene.phaseManager.unshiftPhase(new LoadMoveAnimPhase(moveId)); - globalScene.phaseManager.unshiftPhase(new MovePhase(user, [ target.getBattlerIndex() ], new PokemonMove(moveId, 0, 0, true), true)); + globalScene.phaseManager.unshiftNew("LoadMoveAnimPhase", moveId); + globalScene.phaseManager.unshiftNew("MovePhase", user, [ target.getBattlerIndex() ], new PokemonMove(moveId, 0, 0, true), true); return true; } } @@ -7050,7 +7045,7 @@ export class RepeatMoveAttr extends MoveEffectAttr { })); target.getMoveQueue().unshift({ move: lastMove.move, targets: moveTargets, ignorePP: false }); target.turnData.extraTurns++; - globalScene.phaseManager.appendToPhase(new MovePhase(target, moveTargets, movesetMove), MoveEndPhase); + globalScene.phaseManager.appendNewToPhase("MoveEndPhase", "MovePhase", target, moveTargets, movesetMove); return true; } @@ -7550,7 +7545,7 @@ export class TransformAttr extends MoveEffectAttr { return false; } - globalScene.phaseManager.unshiftPhase(new PokemonTransformPhase(user.getBattlerIndex(), target.getBattlerIndex())); + globalScene.phaseManager.unshiftNew("PokemonTransformPhase", user.getBattlerIndex(), target.getBattlerIndex()); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:transformedIntoTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })); @@ -7852,7 +7847,7 @@ export class AfterYouAttr extends MoveEffectAttr { //Will find next acting phase of the targeted pokémon, delete it and queue it next on successful delete. const nextAttackPhase = globalScene.phaseManager.findPhase((phase) => phase.pokemon === target); if (nextAttackPhase && globalScene.phaseManager.tryRemovePhase((phase: MovePhase) => phase.pokemon === target)) { - globalScene.phaseManager.prependToPhase(new MovePhase(target, [ ...nextAttackPhase.targets ], nextAttackPhase.move), MovePhase); + globalScene.phaseManager.prependNewToPhase("MovePhase", "MovePhase", target, [ ...nextAttackPhase.targets ], nextAttackPhase.move); } return true; @@ -7889,7 +7884,7 @@ export class ForceLastAttr extends MoveEffectAttr { globalScene.phaseManager.phaseQueue.splice( globalScene.phaseManager.phaseQueue.indexOf(prependPhase), 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) ); } } diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index 5f69090064f..11081892205 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -19,7 +19,6 @@ import i18next from "i18next"; import type { IEggOptions } from "#app/data/egg"; import { EggSourceType } from "#enums/egg-source-types"; import { EggTier } from "#enums/egg-type"; -import { PartyHealPhase } from "#app/phases/party-heal-phase"; import { ModifierTier } from "#app/modifier/modifier-tier"; import { modifierTypes } from "#app/modifier/modifier-type"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; @@ -182,7 +181,7 @@ export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder. async () => { const encounter = globalScene.currentBattle.mysteryEncounter!; // Full heal party - globalScene.phaseManager.unshiftPhase(new PartyHealPhase(true)); + globalScene.phaseManager.unshiftNew("PartyHealPhase", true); const eggOptions: IEggOptions = { pulled: false, diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index e23b5024599..82fbc0efd69 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -35,7 +35,6 @@ import { TrainerSlot } from "#enums/trainer-slot"; import { PokeballType } from "#enums/pokeball"; import type HeldModifierConfig from "#app/@types/held-modifier-config"; import type { BerryType } from "#enums/berry-type"; -import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { Stat } from "#enums/stat"; import i18next from "i18next"; @@ -237,8 +236,12 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { queueEncounterMessage(`${namespace}:option.1.boss_enraged`); - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + statChangesForBattle, + 1, ); }, }, diff --git a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts index 87cd8f207c4..cdb98c56ed1 100644 --- a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts +++ b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts @@ -22,7 +22,6 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; 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 i18next from "i18next"; @@ -137,7 +136,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterB }) .withOptionPhase(async () => { // Give the player a Shiny Charm - globalScene.phaseManager.unshiftPhase(new ModifierRewardPhase(modifierTypes.SHINY_CHARM)); + globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.SHINY_CHARM); leaveEncounterWithoutBattle(true); }) .build(), diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index 3de16c7086e..65ae3ea6c4f 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -35,7 +35,6 @@ import { BerryModifier } from "#app/modifier/modifier"; import i18next from "#app/plugins/i18n"; import { BerryType } from "#enums/berry-type"; 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"; /** 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].mysteryEncounterBattleEffects = (pokemon: Pokemon) => { queueEncounterMessage(`${namespace}:option.2.boss_enraged`); - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + statChangesForBattle, + 1, ); }; setEncounterRewards( diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index cd8289163eb..938a136bced 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -26,7 +26,6 @@ import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon"; 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 type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; @@ -766,8 +765,10 @@ function doBugTypeMoveTutor(): Promise { // Option select complete, handle if they are learning a move if (result && result.selectedOptionIndex < moveOptions.length) { - globalScene.phaseManager.unshiftPhase( - new LearnMovePhase(result.selectedPokemonIndex, moveOptions[result.selectedOptionIndex].moveId), + globalScene.phaseManager.unshiftNew( + "LearnMovePhase", + result.selectedPokemonIndex, + moveOptions[result.selectedOptionIndex].moveId, ); } diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index 66cd1ae0509..80465e1d20c 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -26,8 +26,6 @@ import type Pokemon from "#app/field/pokemon"; import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; 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 type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -176,13 +174,12 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { queueEncounterMessage(`${namespace}:option.1.boss_enraged`); - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase( - pokemon.getBattlerIndex(), - true, - [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF], - 1, - ), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF], + 1, ); }, }, @@ -245,8 +242,10 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder const onPokemonSelected = (pokemon: PlayerPokemon) => { encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender()); - globalScene.phaseManager.unshiftPhase( - new LearnMovePhase(globalScene.getPlayerParty().indexOf(pokemon), MoveId.REVELATION_DANCE), + globalScene.phaseManager.unshiftNew( + "LearnMovePhase", + globalScene.getPlayerParty().indexOf(pokemon), + MoveId.REVELATION_DANCE, ); // Play animation again to "learn" the dance diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 1385e9c5a75..6474df3570e 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -16,7 +16,6 @@ import { } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { PokemonFormChangeItemModifier } from "#app/modifier/modifier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; @@ -165,7 +164,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE .withOptionPhase(async () => { // Give the player 5 Rogue Balls const encounter = globalScene.currentBattle.mysteryEncounter!; - globalScene.phaseManager.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 // If this is a mono-type challenge, always ensure the required type is filtered for diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index c321b5f9674..40893d93930 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -29,7 +29,6 @@ import { } from "#app/modifier/modifier"; import type { PokemonHeldItemModifierType } 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 type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { randSeedItem } from "#app/utils/common"; @@ -65,10 +64,10 @@ const doEventReward = () => { return !(existingCharm && existingCharm.getStackCount() >= existingCharm.getMaxStackCount()); }); if (candidates.length > 0) { - globalScene.phaseManager.unshiftPhase(new ModifierRewardPhase(modifierTypes[randSeedItem(candidates)])); + globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes[randSeedItem(candidates)]); } else { // At max stacks, give a Voucher instead - globalScene.phaseManager.unshiftPhase(new ModifierRewardPhase(modifierTypes.VOUCHER)); + globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.VOUCHER); } } }; @@ -181,7 +180,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with ); doEventReward(); } else { - globalScene.phaseManager.unshiftPhase(new ModifierRewardPhase(modifierTypes.AMULET_COIN)); + globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.AMULET_COIN); doEventReward(); } @@ -266,7 +265,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with ); doEventReward(); } else { - globalScene.phaseManager.unshiftPhase(new ModifierRewardPhase(modifierTypes.CANDY_JAR)); + globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.CANDY_JAR); doEventReward(); } } else { @@ -288,7 +287,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with ); doEventReward(); } else { - globalScene.phaseManager.unshiftPhase(new ModifierRewardPhase(modifierTypes.BERRY_POUCH)); + globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.BERRY_POUCH); doEventReward(); } } @@ -372,7 +371,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with ); doEventReward(); } else { - globalScene.phaseManager.unshiftPhase(new ModifierRewardPhase(modifierTypes.HEALING_CHARM)); + globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.HEALING_CHARM); doEventReward(); } diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index ced04ce224d..8b0e5a08020 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -44,7 +44,6 @@ import { EncounterAnim } from "#enums/encounter-anims"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#enums/battler-tag-type"; -import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { Stat } from "#enums/stat"; import { Ability } from "#app/data/abilities/ability-class"; 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, tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.SPDEF, Stat.SPD], 1), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + [Stat.SPDEF, Stat.SPD], + 1, ); }, }, @@ -103,8 +106,12 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w gender: Gender.FEMALE, tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.SPDEF, Stat.SPD], 1), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + [Stat.SPDEF, Stat.SPD], + 1, ); }, }, diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index 6df8ca8b167..83538e9e0e9 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -32,7 +32,6 @@ import PokemonData from "#app/system/pokemon-data"; import { BattlerTagType } from "#enums/battler-tag-type"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { randSeedInt } from "#app/utils/common"; -import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; /** the i18n namespace for the encounter */ @@ -76,8 +75,12 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder. queueEncounterMessage(`${namespace}:option.1.stat_boost`); // Randomly boost 1 stat 2 stages // Cannot boost Spd, Acc, or Evasion - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, [randSeedInt(4, 1)], 2), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + [randSeedInt(4, 1)], + 2, ); }, }, diff --git a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts index 42bfe76d98e..a52641f857d 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -25,9 +25,7 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { PlayerGender } from "#enums/player-gender"; import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball"; import { addPokeballOpenParticles } from "#app/field/anims"; -import { ShinySparklePhase } from "#app/phases/shiny-sparkle-phase"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; -import { PostSummonPhase } from "#app/phases/post-summon-phase"; import { modifierTypes } from "#app/modifier/modifier-type"; import { Nature } from "#enums/nature"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; @@ -411,13 +409,13 @@ function summonPlayerPokemonAnimation(pokemon: PlayerPokemon): Promise { pokemon.resetSummonData(); globalScene.time.delayedCall(1000, () => { if (pokemon.isShiny()) { - globalScene.phaseManager.unshiftPhase(new ShinySparklePhase(pokemon.getBattlerIndex())); + globalScene.phaseManager.unshiftNew("ShinySparklePhase", pokemon.getBattlerIndex()); } pokemon.resetTurnData(); globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); - globalScene.phaseManager.pushPhase(new PostSummonPhase(pokemon.getBattlerIndex())); + globalScene.phaseManager.pushNew("PostSummonPhase", pokemon.getBattlerIndex()); resolve(); }); }, diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index 42167d240f9..62029eb1847 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -17,7 +17,6 @@ import { import { getPokemonSpecies } from "#app/data/pokemon-species"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { ModifierTier } from "#app/modifier/modifier-tier"; -import { GameOverPhase } from "#app/phases/game-over-phase"; import { randSeedInt } from "#app/utils/common"; import { MoveId } from "#enums/move-id"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -190,7 +189,7 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde if (allowedPokemon.length === 0) { // If there are no longer any legal pokemon in the party, game over. globalScene.phaseManager.clearPhaseQueue(); - globalScene.phaseManager.unshiftPhase(new GameOverPhase()); + globalScene.phaseManager.unshiftNew("GameOverPhase"); } else { // Show which Pokemon was KOed, then start battle against Gimmighoul await transitionMysteryEncounterIntroVisuals(true, true, 500); diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index 54e15dcb102..d324e9f9b6c 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -29,8 +29,6 @@ import { getEncounterText, showEncounterText } from "#app/data/mystery-encounter import { getPokemonNameWithAffix } from "#app/messages"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; 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 { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-groups"; @@ -325,7 +323,7 @@ async function summonSafariPokemon() { encounter.misc.pokemon = pokemon; encounter.misc.safariPokemonRemaining -= 1; - globalScene.phaseManager.unshiftPhase(new SummonPhase(0, false)); + globalScene.phaseManager.unshiftNew("SummonPhase", 0, false); encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon)); @@ -336,7 +334,7 @@ async function summonSafariPokemon() { const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier); if (ivScannerModifier) { - globalScene.phaseManager.pushPhase(new ScanIvsPhase(pokemon.getBattlerIndex())); + globalScene.phaseManager.pushNew("ScanIvsPhase", pokemon.getBattlerIndex()); } } diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index a0898f7cfec..bb1529e3695 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -26,7 +26,6 @@ import { AiType, PokemonMove } from "#app/field/pokemon"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import { PartyHealPhase } from "#app/phases/party-heal-phase"; import { BerryType } from "#enums/berry-type"; import { Stat } from "#enums/stat"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; @@ -155,7 +154,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil async () => { // Fall asleep waiting for Snorlax // Full heal party - globalScene.phaseManager.unshiftPhase(new PartyHealPhase(true)); + globalScene.phaseManager.unshiftNew("PartyHealPhase", true); queueEncounterMessage(`${namespace}:option.2.rest_result`); leaveEncounterWithoutBattle(); }, diff --git a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts index b101837adb9..edc9cf13834 100644 --- a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts +++ b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts @@ -27,7 +27,6 @@ import { getPartyLuckValue, modifierTypes } from "#app/modifier/modifier-type"; import { TrainerSlot } from "#enums/trainer-slot"; import { BattlerTagType } from "#enums/battler-tag-type"; import { getPokemonNameWithAffix } from "#app/messages"; -import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { @@ -227,8 +226,12 @@ async function doBiomeTransitionDialogueAndBattleInit() { tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { queueEncounterMessage(`${namespace}:boss_enraged`); - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + statChangesForBattle, + 1, ); }, }, diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index e2b6d62745b..be347fb0035 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -27,7 +27,6 @@ import { BerryType } from "#enums/berry-type"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { Stat } from "#enums/stat"; -import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; /** the i18n namespace for the encounter */ @@ -116,8 +115,12 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { queueEncounterMessage(`${namespace}:option.2.stat_boost`); - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.DEF, Stat.SPDEF], 1), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + [Stat.DEF, Stat.SPDEF], + 1, ); }, }, diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index 05e036e9189..4b17260098f 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -27,9 +27,6 @@ import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms"; import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/abilities/ability"; import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; 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 { ModifierTier } from "#app/modifier/modifier-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; @@ -143,7 +140,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = MysteryEncounter }, async () => { // Refuse the challenge, they full heal the party and give the player a Rarer Candy - globalScene.phaseManager.unshiftPhase(new PartyHealPhase(true)); + globalScene.phaseManager.unshiftNew("PartyHealPhase", true); setEncounterRewards({ guaranteedModifierTypeFuncs: [modifierTypes.RARER_CANDY], fillRemaining: false, @@ -209,7 +206,7 @@ function endTrainerBattleAndShowDialogue(): Promise { for (const pokemon of playerField) { pokemon.lapseTag(BattlerTagType.COMMANDED); } - playerField.forEach((_, p) => globalScene.phaseManager.unshiftPhase(new ReturnPhase(p))); + playerField.forEach((_, p) => globalScene.phaseManager.unshiftNew("ReturnPhase", p)); for (const pokemon of globalScene.getPlayerParty()) { // Only trigger form change when Eiscue is in Noice form @@ -227,7 +224,7 @@ function endTrainerBattleAndShowDialogue(): Promise { applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon); } - globalScene.phaseManager.unshiftPhase(new ShowTrainerPhase()); + globalScene.phaseManager.unshiftNew("ShowTrainerPhase"); // Hide the trainer and init next battle const trainer = globalScene.currentBattle.trainer; // Unassign previous trainer from battle so it isn't destroyed before animation completes diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index e917574a065..411ecdec080 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -35,7 +35,6 @@ import { PokeballType } from "#enums/pokeball"; import { BattlerTagType } from "#enums/battler-tag-type"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { BerryModifier } from "#app/modifier/modifier"; -import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; @@ -103,8 +102,12 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder. tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { queueEncounterMessage(`${namespace}:option.1.stat_boost`); - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + pokemon.getBattlerIndex(), + true, + statChangesForBattle, + 1, ); }, }, diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index c566ed68476..b86cbaa18c9 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -20,12 +20,6 @@ import { modifierTypes, regenerateModifierPoolThresholds, } 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 { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler"; @@ -51,13 +45,6 @@ import type { IEggOptions } from "#app/data/egg"; import { Egg } from "#app/data/egg"; import type { CustomPokemonData } from "#app/data/custom-pokemon-data"; 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 { StatusEffect } from "#enums/status-effect"; import { globalScene } from "#app/global-scene"; @@ -428,7 +415,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): console.log("Moveset:", moveset); }); - globalScene.phaseManager.pushPhase(new MysteryEncounterBattlePhase(partyConfig.disableSwitch)); + globalScene.phaseManager.pushNew("MysteryEncounterBattlePhase", partyConfig.disableSwitch); await Promise.all(loadEnemyAssets); battle.enemyParty.forEach((enemyPokemon_2, e_1) => { @@ -767,7 +754,7 @@ export function setEncounterRewards( } if (customShopRewards) { - globalScene.phaseManager.unshiftPhase(new SelectModifierPhase(0, undefined, customShopRewards)); + globalScene.phaseManager.unshiftNew("SelectModifierPhase", 0, undefined, customShopRewards); } else { globalScene.phaseManager.tryRemovePhase(p => p.is("MysteryEncounterRewardsPhase")); } @@ -807,7 +794,7 @@ export function setEncounterExp(participantId: number | number[], baseExpValue: const participantIds = Array.isArray(participantId) ? participantId : [participantId]; globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => { - globalScene.phaseManager.unshiftPhase(new PartyExpPhase(baseExpValue, useWaveIndex, new Set(participantIds))); + globalScene.phaseManager.unshiftNew("PartyExpPhase", baseExpValue, useWaveIndex, new Set(participantIds)); return true; }; @@ -829,7 +816,7 @@ export class OptionSelectSettings { * @param optionSelectSettings */ export function initSubsequentOptionSelect(optionSelectSettings: OptionSelectSettings) { - globalScene.phaseManager.pushPhase(new MysteryEncounterPhase(optionSelectSettings)); + globalScene.phaseManager.pushNew("MysteryEncounterPhase", optionSelectSettings); } /** @@ -858,7 +845,7 @@ export function handleMysteryEncounterVictory(addHealPhase = false, doNotContinu if (allowedPkm.length === 0) { globalScene.phaseManager.clearPhaseQueue(); - globalScene.phaseManager.unshiftPhase(new GameOverPhase()); + globalScene.phaseManager.unshiftNew("GameOverPhase"); return; } @@ -869,8 +856,8 @@ export function handleMysteryEncounterVictory(addHealPhase = false, doNotContinu return; } if (encounter.encounterMode === MysteryEncounterMode.NO_BATTLE) { - globalScene.phaseManager.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase)); - globalScene.phaseManager.pushPhase(new EggLapsePhase()); + globalScene.phaseManager.pushNew("MysteryEncounterRewardsPhase", addHealPhase); + globalScene.phaseManager.pushNew("EggLapsePhase"); } else if ( !globalScene .getEnemyParty() @@ -878,15 +865,15 @@ export function handleMysteryEncounterVictory(addHealPhase = false, doNotContinu encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true), ) ) { - globalScene.phaseManager.pushPhase(new BattleEndPhase(true)); + globalScene.phaseManager.pushNew("BattleEndPhase", true); if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) { - globalScene.phaseManager.pushPhase(new TrainerVictoryPhase()); + globalScene.phaseManager.pushNew("TrainerVictoryPhase"); } if (globalScene.gameMode.isEndless || !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) { - globalScene.phaseManager.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase)); + globalScene.phaseManager.pushNew("MysteryEncounterRewardsPhase", addHealPhase); if (!encounter.doContinueEncounter) { // Only lapse eggs once for multi-battle encounters - globalScene.phaseManager.pushPhase(new EggLapsePhase()); + globalScene.phaseManager.pushNew("EggLapsePhase"); } } } @@ -901,7 +888,7 @@ export function handleMysteryEncounterBattleFailed(addHealPhase = false, doNotCo if (allowedPkm.length === 0) { globalScene.phaseManager.clearPhaseQueue(); - globalScene.phaseManager.unshiftPhase(new GameOverPhase()); + globalScene.phaseManager.unshiftNew("GameOverPhase"); return; } @@ -912,14 +899,14 @@ export function handleMysteryEncounterBattleFailed(addHealPhase = false, doNotCo return; } if (encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE) { - globalScene.phaseManager.pushPhase(new BattleEndPhase(false)); + globalScene.phaseManager.pushNew("BattleEndPhase", false); } - globalScene.phaseManager.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase)); + globalScene.phaseManager.pushNew("MysteryEncounterRewardsPhase", addHealPhase); if (!encounter.doContinueEncounter) { // Only lapse eggs once for multi-battle encounters - globalScene.phaseManager.pushPhase(new EggLapsePhase()); + globalScene.phaseManager.pushNew("EggLapsePhase"); } } @@ -1004,14 +991,19 @@ export function handleMysteryEncounterBattleStartEffects() { } else { source = globalScene.getEnemyField()[0]; } - globalScene.phaseManager.pushPhase( - // @ts-ignore: source cannot be undefined - new MovePhase(source, effect.targets, effect.move, effect.followUp, effect.ignorePp), + globalScene.phaseManager.pushNew( + "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. - globalScene.phaseManager.pushPhase(new MysteryEncounterBattleStartCleanupPhase()); + globalScene.phaseManager.pushNew("MysteryEncounterBattleStartCleanupPhase"); encounter.startOfBattleEffectsComplete = true; } diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index 757d8173820..e8a3db46cff 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -32,7 +32,6 @@ import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type"; import { Gender } from "#app/data/gender"; import type { PermanentStat } from "#enums/stat"; -import { VictoryPhase } from "#app/phases/victory-phase"; import { SummaryUiMode } from "#app/ui/summary-ui-handler"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import type { AbilityId } from "#enums/ability-id"; @@ -675,7 +674,7 @@ export async function catchPokemon( if (!globalScene.getEnemyParty().some(p => p.id === pokemon.id)) { globalScene.getEnemyParty().push(pokemon); } - globalScene.phaseManager.unshiftPhase(new VictoryPhase(pokemon.id, true)); + globalScene.phaseManager.unshiftNew("VictoryPhase", pokemon.id, true); globalScene.pokemonInfoContainer.hide(); if (pokeball) { removePb(pokeball); diff --git a/src/field/arena.ts b/src/field/arena.ts index 1c54382c89b..82a8afbedad 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -38,7 +38,6 @@ import { TimeOfDay } from "#enums/time-of-day"; import { TrainerType } from "#enums/trainer-type"; import { AbilityId } from "#enums/ability-id"; import { SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "#app/data/pokemon-forms"; -import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { WeatherType } from "#enums/weather-type"; import { FieldEffectModifier } from "#app/modifier/modifier"; @@ -297,7 +296,7 @@ export class Arena { */ trySetWeatherOverride(weather: WeatherType): boolean { this.weather = new Weather(weather, 0); - globalScene.phaseManager.unshiftPhase(new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (weather - 1))); + globalScene.phaseManager.unshiftNew("CommonAnimPhase", undefined, undefined, CommonAnim.SUNNY + (weather - 1)); globalScene.phaseManager.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct? return true; } @@ -328,8 +327,12 @@ export class Arena { this.weather?.isImmutable() && ![WeatherType.HARSH_SUN, WeatherType.HEAVY_RAIN, WeatherType.STRONG_WINDS, WeatherType.NONE].includes(weather) ) { - globalScene.phaseManager.unshiftPhase( - new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (oldWeatherType - 1), true), + globalScene.phaseManager.unshiftNew( + "CommonAnimPhase", + undefined, + undefined, + CommonAnim.SUNNY + (oldWeatherType - 1), + true, ); globalScene.phaseManager.queueMessage(getLegendaryWeatherContinuesMessage(oldWeatherType)!); return false; @@ -348,8 +351,12 @@ export class Arena { ); // TODO: is this bang correct? if (this.weather) { - globalScene.phaseManager.unshiftPhase( - new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (weather - 1), true), + globalScene.phaseManager.unshiftNew( + "CommonAnimPhase", + undefined, + undefined, + CommonAnim.SUNNY + (weather - 1), + true, ); globalScene.phaseManager.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct? } else { @@ -433,8 +440,11 @@ export class Arena { if (this.terrain) { if (!ignoreAnim) { - globalScene.phaseManager.unshiftPhase( - new CommonAnimPhase(undefined, undefined, CommonAnim.MISTY_TERRAIN + (terrain - 1)), + globalScene.phaseManager.unshiftNew( + "CommonAnimPhase", + undefined, + undefined, + CommonAnim.MISTY_TERRAIN + (terrain - 1), ); } globalScene.phaseManager.queueMessage(getTerrainStartMessage(terrain)!); // TODO: is this bang correct? diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 5b38419e708..71b21076ae6 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -230,13 +230,6 @@ import { BiomeId } from "#enums/biome-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; 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 { 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 { PokemonAnimType } from "#enums/pokemon-anim-type"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; @@ -256,7 +249,6 @@ import { doShinySparkleAnim } from "#app/field/anims"; import { MoveFlags } from "#enums/MoveFlags"; import { timedEventManager } from "#app/global-event-manager"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; -import { ResetStatusPhase } from "#app/phases/reset-status-phase"; export enum LearnMoveSituation { MISC, @@ -4013,7 +4005,7 @@ 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() ) */ globalScene.phaseManager.setPhaseQueueSplice(); - globalScene.phaseManager.unshiftPhase(new FaintPhase(this.getBattlerIndex(), preventEndure)); + globalScene.phaseManager.unshiftNew("FaintPhase", this.getBattlerIndex(), preventEndure); this.destroySubstitute(); this.lapseTag(BattlerTagType.COMMANDED); } @@ -4049,7 +4041,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } = {}, ): number { 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( + "DamageAnimPhase", + this.getBattlerIndex(), + damage, + result as DamageResult, + isCritical, + ); globalScene.phaseManager.unshiftPhase(damagePhase); if (this.switchOutStatus && source) { damage = 0; @@ -4778,8 +4776,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (overrideStatus) { this.resetStatus(false); } - globalScene.phaseManager.unshiftPhase( - new ObtainStatusEffectPhase(this.getBattlerIndex(), effect, turnsRemaining, sourceText, sourcePokemon), + globalScene.phaseManager.unshiftNew( + "ObtainStatusEffectPhase", + this.getBattlerIndex(), + effect, + turnsRemaining, + sourceText, + sourcePokemon, ); return true; } @@ -4828,7 +4831,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (asPhase) { - globalScene.phaseManager.unshiftPhase(new ResetStatusPhase(this, confusion, reloadAssets)); + globalScene.phaseManager.unshiftNew("ResetStatusPhase", this, confusion, reloadAssets); } else { this.clearStatus(confusion, reloadAssets); } @@ -5635,9 +5638,13 @@ export class PlayerPokemon extends Pokemon { this.getFieldIndex(), (slotIndex: number, _option: PartyOption) => { if (slotIndex >= globalScene.currentBattle.getBattlerCount() && slotIndex < 6) { - globalScene.phaseManager.prependToPhase( - new SwitchSummonPhase(switchType, this.getFieldIndex(), slotIndex, false), - MoveEndPhase, + globalScene.phaseManager.prependNewToPhase( + "MoveEndPhase", + "SwitchSummonPhase", + switchType, + this.getFieldIndex(), + slotIndex, + false, ); } globalScene.ui.setMode(UiMode.MESSAGE).then(resolve); @@ -6001,7 +6008,7 @@ export class PlayerPokemon extends Pokemon { pokemon .getMoveset(true) .map((m: PokemonMove) => - globalScene.phaseManager.unshiftPhase(new LearnMovePhase(newPartyMemberIndex, m.getMove().id)), + globalScene.phaseManager.unshiftNew("LearnMovePhase", newPartyMemberIndex, m.getMove().id), ); pokemon.destroy(); this.updateFusionPalette(); @@ -6644,8 +6651,14 @@ export class EnemyPokemon extends Pokemon { stages++; } - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase(this.getBattlerIndex(), true, [boostedStat!], stages, true, true), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + this.getBattlerIndex(), + true, + [boostedStat!], + stages, + true, + true, ); this.bossSegmentIndex--; } diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 81d9bf7189c..b6f96db751a 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -8,10 +8,7 @@ import { getStatusEffectHealText } from "#app/data/status-effect"; import Pokemon, { type PlayerPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; -import { EvolutionPhase } from "#app/phases/evolution-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 { LearnMoveType } from "#app/phases/learn-move-phase"; import type { VoucherType } from "#app/system/voucher"; import { Command } from "#app/ui/command-ui-handler"; import { addTextObject, TextStyle } from "#app/ui/text"; @@ -1684,16 +1681,15 @@ export class TurnHealModifier extends PokemonHeldItemModifier { */ override apply(pokemon: Pokemon): boolean { if (!pokemon.isFullHp()) { - globalScene.phaseManager.unshiftPhase( - new PokemonHealPhase( - pokemon.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / 16) * this.stackCount, - i18next.t("modifier:turnHealApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - typeName: this.type.name, - }), - true, - ), + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + pokemon.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / 16) * this.stackCount, + i18next.t("modifier:turnHealApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + typeName: this.type.name, + }), + true, ); return true; } @@ -1782,16 +1778,15 @@ export class HitHealModifier extends PokemonHeldItemModifier { override apply(pokemon: Pokemon): boolean { if (pokemon.turnData.totalDamageDealt && !pokemon.isFullHp()) { // TODO: this shouldn't be undefined AFAIK - globalScene.phaseManager.unshiftPhase( - new PokemonHealPhase( - pokemon.getBattlerIndex(), - toDmgValue(pokemon.turnData.totalDamageDealt / 8) * this.stackCount, - i18next.t("modifier:hitHealApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - typeName: this.type.name, - }), - true, - ), + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + pokemon.getBattlerIndex(), + toDmgValue(pokemon.turnData.totalDamageDealt / 8) * this.stackCount, + i18next.t("modifier:hitHealApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + typeName: this.type.name, + }), + true, ); } @@ -1950,18 +1945,17 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier { */ override apply(pokemon: Pokemon): boolean { // Restore the Pokemon to half HP - globalScene.phaseManager.unshiftPhase( - new PokemonHealPhase( - pokemon.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / 2), - i18next.t("modifier:pokemonInstantReviveApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - typeName: this.type.name, - }), - false, - false, - true, - ), + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + pokemon.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / 2), + i18next.t("modifier:pokemonInstantReviveApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + typeName: this.type.name, + }), + false, + false, + true, ); // Remove the Pokemon's FAINT status @@ -2323,12 +2317,11 @@ export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier { playerPokemon.addFriendship(FRIENDSHIP_GAIN_FROM_RARE_CANDY); - globalScene.phaseManager.unshiftPhase( - new LevelUpPhase( - globalScene.getPlayerParty().indexOf(playerPokemon), - playerPokemon.level - levelCount.value, - playerPokemon.level, - ), + globalScene.phaseManager.unshiftNew( + "LevelUpPhase", + globalScene.getPlayerParty().indexOf(playerPokemon), + playerPokemon.level - levelCount.value, + playerPokemon.level, ); return true; @@ -2344,8 +2337,11 @@ export class TmModifier extends ConsumablePokemonModifier { * @returns always `true` */ override apply(playerPokemon: PlayerPokemon): boolean { - globalScene.phaseManager.unshiftPhase( - new LearnMovePhase(globalScene.getPlayerParty().indexOf(playerPokemon), this.type.moveId, LearnMoveType.TM), + globalScene.phaseManager.unshiftNew( + "LearnMovePhase", + globalScene.getPlayerParty().indexOf(playerPokemon), + this.type.moveId, + LearnMoveType.TM, ); return true; @@ -2367,13 +2363,12 @@ export class RememberMoveModifier extends ConsumablePokemonModifier { * @returns always `true` */ override apply(playerPokemon: PlayerPokemon, cost?: number): boolean { - globalScene.phaseManager.unshiftPhase( - new LearnMovePhase( - globalScene.getPlayerParty().indexOf(playerPokemon), - playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex], - LearnMoveType.MEMORY, - cost, - ), + globalScene.phaseManager.unshiftNew( + "LearnMovePhase", + globalScene.getPlayerParty().indexOf(playerPokemon), + playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex], + LearnMoveType.MEMORY, + cost, ); return true; @@ -2410,9 +2405,7 @@ export class EvolutionItemModifier extends ConsumablePokemonModifier { } if (matchingEvolution) { - globalScene.phaseManager.unshiftPhase( - new EvolutionPhase(playerPokemon, matchingEvolution, playerPokemon.level - 1), - ); + globalScene.phaseManager.unshiftNew("EvolutionPhase", playerPokemon, matchingEvolution, playerPokemon.level - 1); return true; } @@ -3574,19 +3567,18 @@ export class EnemyTurnHealModifier extends EnemyPersistentModifier { */ override apply(enemyPokemon: Pokemon): boolean { if (!enemyPokemon.isFullHp()) { - globalScene.phaseManager.unshiftPhase( - new PokemonHealPhase( - enemyPokemon.getBattlerIndex(), - Math.max(Math.floor(enemyPokemon.getMaxHp() / (100 / this.healPercent)) * this.stackCount, 1), - i18next.t("modifier:enemyTurnHealApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(enemyPokemon), - }), - true, - false, - false, - false, - true, - ), + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + enemyPokemon.getBattlerIndex(), + Math.max(Math.floor(enemyPokemon.getMaxHp() / (100 / this.healPercent)) * this.stackCount, 1), + i18next.t("modifier:enemyTurnHealApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(enemyPokemon), + }), + true, + false, + false, + false, + true, ); return true; } diff --git a/src/phase-manager.ts b/src/phase-manager.ts index 8869f4b27b7..230e0331caf 100644 --- a/src/phase-manager.ts +++ b/src/phase-manager.ts @@ -1,11 +1,100 @@ -import { HideAbilityPhase } from "./phases/hide-ability-phase"; -import { ShowAbilityPhase } from "./phases/show-ability-phase"; -import { TurnInitPhase } from "./phases/turn-init-phase"; import type { Phase } from "#app/phase"; import type { default as Pokemon } from "#app/field/pokemon"; -import type { Constructor } from "#app/utils/common"; -import { MessagePhase } from "./phases/message-phase"; +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. @@ -13,6 +102,115 @@ import { globalScene } from "#app/global-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[] = []; @@ -213,15 +411,16 @@ export class PhaseManager { /** * 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 + * @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: Constructor): boolean { + prependToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean { if (!Array.isArray(phase)) { phase = [phase]; } - const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof targetPhase); + const target = PHASES[targetPhase]; + const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target); if (targetIndex !== -1) { this.phaseQueue.splice(targetIndex, 0, ...phase); @@ -234,14 +433,15 @@ export class PhaseManager { /** * 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 type of phase to search for in {@linkcode phaseQueue} + * @param targetPhase - The phase to search for in phaseQueue * @returns `true` if a `targetPhase` was found to append to */ - appendToPhase(phase: Phase | Phase[], targetPhase: Constructor): boolean { + appendToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean { if (!Array.isArray(phase)) { phase = [phase]; } - const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof targetPhase); + 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); @@ -308,4 +508,74 @@ export class PhaseManager { } 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(phase: T, ...args: ConstructorParameters): 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(phase: T, ...args: ConstructorParameters): 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(phase: T, ...args: ConstructorParameters): 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( + targetPhase: PhaseString, + phase: T, + ...args: ConstructorParameters + ): 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( + targetPhase: PhaseString, + phase: T, + ...args: ConstructorParameters + ): boolean { + return this.appendToPhase(this.create(phase, ...args), targetPhase); + } } diff --git a/src/phases/attempt-capture-phase.ts b/src/phases/attempt-capture-phase.ts index 09eeb6e0a19..4f3f54a7e5b 100644 --- a/src/phases/attempt-capture-phase.ts +++ b/src/phases/attempt-capture-phase.ts @@ -14,7 +14,6 @@ import type { EnemyPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { PokemonPhase } from "#app/phases/pokemon-phase"; -import { VictoryPhase } from "#app/phases/victory-phase"; import { achvs } from "#app/system/achv"; import type { PartyOption } from "#app/ui/party-ui-handler"; import { PartyUiMode } from "#app/ui/party-ui-handler"; @@ -257,7 +256,7 @@ export class AttemptCapturePhase extends PokemonPhase { null, () => { const end = () => { - globalScene.phaseManager.unshiftPhase(new VictoryPhase(this.battlerIndex)); + globalScene.phaseManager.unshiftNew("VictoryPhase", this.battlerIndex); globalScene.pokemonInfoContainer.hide(); this.removePb(); this.end(); diff --git a/src/phases/attempt-run-phase.ts b/src/phases/attempt-run-phase.ts index 2bf6ba2fb5b..525be8c21ab 100644 --- a/src/phases/attempt-run-phase.ts +++ b/src/phases/attempt-run-phase.ts @@ -10,11 +10,8 @@ import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import i18next from "i18next"; import { NumberHolder } from "#app/utils/common"; -import { BattleEndPhase } from "./battle-end-phase"; -import { NewBattlePhase } from "./new-battle-phase"; import { PokemonPhase } from "./pokemon-phase"; import { globalScene } from "#app/global-scene"; -import { SelectBiomePhase } from "./select-biome-phase"; export class AttemptRunPhase extends PokemonPhase { public readonly phaseName = "AttemptRunPhase"; @@ -60,13 +57,13 @@ export class AttemptRunPhase extends PokemonPhase { enemyPokemon.trySetStatus(StatusEffect.FAINT); }); - globalScene.phaseManager.pushPhase(new BattleEndPhase(false)); + globalScene.phaseManager.pushNew("BattleEndPhase", false); if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) { - globalScene.phaseManager.pushPhase(new SelectBiomePhase()); + globalScene.phaseManager.pushNew("SelectBiomePhase"); } - globalScene.phaseManager.pushPhase(new NewBattlePhase()); + globalScene.phaseManager.pushNew("NewBattlePhase"); } else { playerPokemon.turnData.failedRunAway = true; globalScene.phaseManager.queueMessage(i18next.t("battle:runAwayCannotEscape"), null, true, 500); diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index 4e3543be03a..e169de58cb3 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -2,7 +2,6 @@ import { globalScene } from "#app/global-scene"; import { applyPostBattleAbAttrs, PostBattleAbAttr } from "#app/data/abilities/ability"; import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier"; import { BattlePhase } from "./battle-phase"; -import { GameOverPhase } from "./game-over-phase"; export class BattleEndPhase extends BattlePhase { public readonly phaseName = "BattleEndPhase"; @@ -56,7 +55,7 @@ export class BattleEndPhase extends BattlePhase { // Endless graceful end if (globalScene.gameMode.isEndless && globalScene.currentBattle.waveIndex >= 5850) { globalScene.phaseManager.clearPhaseQueue(); - globalScene.phaseManager.unshiftPhase(new GameOverPhase(true)); + globalScene.phaseManager.unshiftNew("GameOverPhase", true); } for (const pokemon of globalScene.getField()) { diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index e9e177a8fe4..6e40e299e7c 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -11,7 +11,6 @@ import { BerryModifier } from "#app/modifier/modifier"; import i18next from "i18next"; import { BooleanHolder } from "#app/utils/common"; import { FieldPhase } from "./field-phase"; -import { CommonAnimPhase } from "./common-anim-phase"; import { globalScene } from "#app/global-scene"; import type Pokemon from "#app/field/pokemon"; @@ -58,8 +57,11 @@ export class BerryPhase extends FieldPhase { return; } - globalScene.phaseManager.unshiftPhase( - new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM), + globalScene.phaseManager.unshiftNew( + "CommonAnimPhase", + pokemon.getBattlerIndex(), + pokemon.getBattlerIndex(), + CommonAnim.USE_ITEM, ); for (const berryModifier of globalScene.applyModifiers(BerryModifier, pokemon.isPlayer(), pokemon)) { diff --git a/src/phases/check-status-effect-phase.ts b/src/phases/check-status-effect-phase.ts index ec15676ebcc..e4793fae076 100644 --- a/src/phases/check-status-effect-phase.ts +++ b/src/phases/check-status-effect-phase.ts @@ -1,4 +1,3 @@ -import { PostTurnStatusEffectPhase } from "#app/phases/post-turn-status-effect-phase"; import { Phase } from "#app/phase"; import type { BattlerIndex } from "#app/battle"; import { globalScene } from "#app/global-scene"; @@ -15,7 +14,7 @@ export class CheckStatusEffectPhase extends Phase { const field = globalScene.getField(); for (const o of this.order) { if (field[o].status?.isPostTurn()) { - globalScene.phaseManager.unshiftPhase(new PostTurnStatusEffectPhase(o)); + globalScene.phaseManager.unshiftNew("PostTurnStatusEffectPhase", o); } } this.end(); diff --git a/src/phases/check-switch-phase.ts b/src/phases/check-switch-phase.ts index 2299bf0c0a5..97f4092096f 100644 --- a/src/phases/check-switch-phase.ts +++ b/src/phases/check-switch-phase.ts @@ -5,8 +5,6 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; import { BattlePhase } from "./battle-phase"; -import { SummonMissingPhase } from "./summon-missing-phase"; -import { SwitchPhase } from "./switch-phase"; import { SwitchType } from "#enums/switch-type"; export class CheckSwitchPhase extends BattlePhase { @@ -35,7 +33,7 @@ export class CheckSwitchPhase extends BattlePhase { // ...if the checked Pokemon is somehow not on the field if (globalScene.field.getAll().indexOf(pokemon) === -1) { - globalScene.phaseManager.unshiftPhase(new SummonMissingPhase(this.fieldIndex)); + globalScene.phaseManager.unshiftNew("SummonMissingPhase", this.fieldIndex); return super.end(); } @@ -68,9 +66,7 @@ export class CheckSwitchPhase extends BattlePhase { UiMode.CONFIRM, () => { globalScene.ui.setMode(UiMode.MESSAGE); - globalScene.phaseManager.unshiftPhase( - new SwitchPhase(SwitchType.INITIAL_SWITCH, this.fieldIndex, false, true), - ); + globalScene.phaseManager.unshiftNew("SwitchPhase", SwitchType.INITIAL_SWITCH, this.fieldIndex, false, true); this.end(); }, () => { diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index 7a2e427ecce..afd9cb3bf93 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -18,7 +18,6 @@ import { Command } from "#app/ui/command-ui-handler"; import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; import { FieldPhase } from "./field-phase"; -import { SelectTargetPhase } from "./select-target-phase"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { isNullOrUndefined } from "#app/utils/common"; import { ArenaTagSide } from "#app/data/arena-tag"; @@ -192,7 +191,7 @@ export class CommandPhase extends FieldPhase { } console.log(moveTargets, getPokemonNameWithAffix(playerPokemon)); if (moveTargets.targets.length > 1 && moveTargets.multiple) { - globalScene.phaseManager.unshiftPhase(new SelectTargetPhase(this.fieldIndex)); + globalScene.phaseManager.unshiftNew("SelectTargetPhase", this.fieldIndex); } if (turnCommand.move && (moveTargets.targets.length <= 1 || moveTargets.multiple)) { turnCommand.move.targets = moveTargets.targets; @@ -203,7 +202,7 @@ export class CommandPhase extends FieldPhase { ) { turnCommand.move.targets = playerPokemon.getMoveQueue()[0].targets; } else { - globalScene.phaseManager.unshiftPhase(new SelectTargetPhase(this.fieldIndex)); + globalScene.phaseManager.unshiftNew("SelectTargetPhase", this.fieldIndex); } globalScene.currentBattle.preTurnCommands[this.fieldIndex] = preTurnCommand; globalScene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; @@ -457,8 +456,8 @@ export class CommandPhase extends FieldPhase { cancel() { if (this.fieldIndex) { - globalScene.phaseManager.unshiftPhase(new CommandPhase(0)); - globalScene.phaseManager.unshiftPhase(new CommandPhase(1)); + globalScene.phaseManager.unshiftNew("CommandPhase", 0); + globalScene.phaseManager.unshiftNew("CommandPhase", 1); this.end(); } } diff --git a/src/phases/egg-lapse-phase.ts b/src/phases/egg-lapse-phase.ts index a19bf8f50e5..206bef1a33c 100644 --- a/src/phases/egg-lapse-phase.ts +++ b/src/phases/egg-lapse-phase.ts @@ -4,11 +4,9 @@ import { EGG_SEED } from "#app/data/egg"; import { Phase } from "#app/phase"; import i18next from "i18next"; import Overrides from "#app/overrides"; -import { EggHatchPhase } from "./egg-hatch-phase"; import { UiMode } from "#enums/ui-mode"; import { achvs } from "#app/system/achv"; import type { PlayerPokemon } from "#app/field/pokemon"; -import { EggSummaryPhase } from "./egg-summary-phase"; import { EggHatchData } from "#app/data/egg-hatch-data"; /** @@ -83,7 +81,7 @@ export class EggLapsePhase extends Phase { hatchEggsRegular(eggsToHatch: Egg[]) { let eggsToHatchCount: number = eggsToHatch.length; for (const egg of eggsToHatch) { - globalScene.phaseManager.unshiftPhase(new EggHatchPhase(this, egg, eggsToHatchCount)); + globalScene.phaseManager.unshiftNew("EggHatchPhase", this, egg, eggsToHatchCount); eggsToHatchCount--; } } @@ -99,7 +97,7 @@ export class EggLapsePhase extends Phase { } showSummary() { - globalScene.phaseManager.unshiftPhase(new EggSummaryPhase(this.eggHatchData)); + globalScene.phaseManager.unshiftNew("EggSummaryPhase", this.eggHatchData); this.end(); } diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index cdd17c6d6d6..e3b33122ac2 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -23,15 +23,6 @@ import { BoostBugSpawnModifier, IvScannerModifier, TurnHeldItemTransferModifier import { ModifierPoolType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import Overrides from "#app/overrides"; 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 { handleTutorial, Tutorial } from "#app/tutorial"; import { UiMode } from "#enums/ui-mode"; @@ -68,7 +59,7 @@ export class EncounterPhase extends BattlePhase { // Failsafe if players somehow skip floor 200 in classic mode if (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex > 200) { - globalScene.phaseManager.unshiftPhase(new GameOverPhase()); + globalScene.phaseManager.unshiftNew("GameOverPhase"); } const loadEnemyAssets: Promise[] = []; @@ -438,9 +429,9 @@ export class EncounterPhase extends BattlePhase { const doTrainerSummon = () => { this.hideEnemyTrainer(); const availablePartyMembers = globalScene.getEnemyParty().filter(p => !p.isFainted()).length; - globalScene.phaseManager.unshiftPhase(new SummonPhase(0, false)); + globalScene.phaseManager.unshiftNew("SummonPhase", 0, false); if (globalScene.currentBattle.double && availablePartyMembers > 1) { - globalScene.phaseManager.unshiftPhase(new SummonPhase(1, false)); + globalScene.phaseManager.unshiftNew("SummonPhase", 1, false); } this.end(); }; @@ -496,7 +487,7 @@ export class EncounterPhase extends BattlePhase { globalScene.ui.clearText(); globalScene.ui.getMessageHandler().hideNameText(); - globalScene.phaseManager.unshiftPhase(new MysteryEncounterPhase()); + globalScene.phaseManager.unshiftNew("MysteryEncounterPhase"); this.end(); }; @@ -554,7 +545,7 @@ export class EncounterPhase extends BattlePhase { enemyField.forEach((enemyPokemon, e) => { if (enemyPokemon.isShiny(true)) { - globalScene.phaseManager.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 */ if ( @@ -576,25 +567,31 @@ export class EncounterPhase extends BattlePhase { if (![BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(globalScene.currentBattle.battleType)) { enemyField.map(p => - globalScene.phaseManager.pushConditionalPhase(new PostSummonPhase(p.getBattlerIndex()), () => { - // if there is not a player party, we can't continue - if (!globalScene.getPlayerParty().length) { - return false; - } - // how many player pokemon are on the field ? - 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 - const requiredPokemonsOnField = Math.min(globalScene.getPlayerParty().filter(p => !p.isFainted()).length, 2); - // if it's a double, there should be 2, otherwise 1 - if (globalScene.currentBattle.double) { - return pokemonsOnFieldCount === requiredPokemonsOnField; - } - return pokemonsOnFieldCount === 1; - }), + globalScene.phaseManager.pushConditionalPhase( + globalScene.phaseManager.create("PostSummonPhase", p.getBattlerIndex()), + () => { + // if there is not a player party, we can't continue + if (!globalScene.getPlayerParty().length) { + return false; + } + // how many player pokemon are on the field ? + 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 + const requiredPokemonsOnField = Math.min( + globalScene.getPlayerParty().filter(p => !p.isFainted()).length, + 2, + ); + // 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); if (ivScannerModifier) { - enemyField.map(p => globalScene.phaseManager.pushPhase(new ScanIvsPhase(p.getBattlerIndex()))); + enemyField.map(p => globalScene.phaseManager.pushNew("ScanIvsPhase", p.getBattlerIndex())); } } @@ -602,21 +599,21 @@ export class EncounterPhase extends BattlePhase { const availablePartyMembers = globalScene.getPokemonAllowedInBattle(); if (!availablePartyMembers[0].isOnField()) { - globalScene.phaseManager.pushPhase(new SummonPhase(0)); + globalScene.phaseManager.pushNew("SummonPhase", 0); } if (globalScene.currentBattle.double) { if (availablePartyMembers.length > 1) { - globalScene.phaseManager.pushPhase(new ToggleDoublePositionPhase(true)); + globalScene.phaseManager.pushNew("ToggleDoublePositionPhase", true); if (!availablePartyMembers[1].isOnField()) { - globalScene.phaseManager.pushPhase(new SummonPhase(1)); + globalScene.phaseManager.pushNew("SummonPhase", 1); } } } else { if (availablePartyMembers.length > 1 && availablePartyMembers[1].isOnField()) { - globalScene.phaseManager.pushPhase(new ReturnPhase(1)); + globalScene.phaseManager.pushNew("ReturnPhase", 1); } - globalScene.phaseManager.pushPhase(new ToggleDoublePositionPhase(false)); + globalScene.phaseManager.pushNew("ToggleDoublePositionPhase", false); } if ( @@ -625,9 +622,9 @@ export class EncounterPhase extends BattlePhase { ) { const minPartySize = globalScene.currentBattle.double ? 2 : 1; if (availablePartyMembers.length > minPartySize) { - globalScene.phaseManager.pushPhase(new CheckSwitchPhase(0, globalScene.currentBattle.double)); + globalScene.phaseManager.pushNew("CheckSwitchPhase", 0, globalScene.currentBattle.double); if (globalScene.currentBattle.double) { - globalScene.phaseManager.pushPhase(new CheckSwitchPhase(1, globalScene.currentBattle.double)); + globalScene.phaseManager.pushNew("CheckSwitchPhase", 1, globalScene.currentBattle.double); } } } diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index 937a7d31542..eaedb6d32b0 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -14,8 +14,6 @@ import { LearnMoveSituation } from "#app/field/pokemon"; import { getTypeRgb } from "#app/data/type"; import i18next from "i18next"; 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"; export class EvolutionPhase extends Phase { @@ -262,7 +260,7 @@ export class EvolutionPhase extends Phase { SoundFade.fadeOut(globalScene, this.evolutionBgm, 100); - globalScene.phaseManager.unshiftPhase(new EndEvolutionPhase()); + globalScene.phaseManager.unshiftNew("EndEvolutionPhase"); globalScene.ui.showText( i18next.t("menu:stoppedEvolving", { @@ -355,11 +353,13 @@ export class EvolutionPhase extends Phase { .getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation) .filter(lm => lm[0] === EVOLVE_MOVE); for (const lm of levelMoves) { - globalScene.phaseManager.unshiftPhase( - new LearnMovePhase(globalScene.getPlayerParty().indexOf(this.pokemon), lm[1]), + globalScene.phaseManager.unshiftNew( + "LearnMovePhase", + globalScene.getPlayerParty().indexOf(this.pokemon), + lm[1], ); } - globalScene.phaseManager.unshiftPhase(new EndEvolutionPhase()); + globalScene.phaseManager.unshiftNew("EndEvolutionPhase"); globalScene.playSound("se/shine"); this.doSpray(); diff --git a/src/phases/exp-phase.ts b/src/phases/exp-phase.ts index 8084ae78221..74768e86186 100644 --- a/src/phases/exp-phase.ts +++ b/src/phases/exp-phase.ts @@ -4,7 +4,6 @@ import { ExpBoosterModifier } from "#app/modifier/modifier"; import i18next from "i18next"; import { NumberHolder } from "#app/utils/common"; import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; -import { LevelUpPhase } from "./level-up-phase"; export class ExpPhase extends PlayerPartyMemberPokemonPhase { public readonly phaseName = "ExpPhase"; @@ -34,7 +33,7 @@ export class ExpPhase extends PlayerPartyMemberPokemonPhase { pokemon.addExp(exp.value); const newLevel = pokemon.level; if (newLevel > lastLevel) { - globalScene.phaseManager.unshiftPhase(new LevelUpPhase(this.partyMemberIndex, lastLevel, newLevel)); + globalScene.phaseManager.unshiftNew("LevelUpPhase", this.partyMemberIndex, lastLevel, newLevel); } pokemon.updateInfo().then(() => this.end()); }, diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 41e21162dbf..38376af4356 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -24,13 +24,7 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { SwitchType } from "#enums/switch-type"; import i18next from "i18next"; -import { DamageAnimPhase } from "./damage-anim-phase"; -import { GameOverPhase } from "./game-over-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 { FRIENDSHIP_LOSS_FROM_FAINT } from "#app/data/balance/starters"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -166,7 +160,7 @@ export class FaintPhase extends PokemonPhase { const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true)); if (!legalPlayerPokemon.length) { /** If the player doesn't have any legal Pokemon, end the game */ - globalScene.phaseManager.unshiftPhase(new GameOverPhase()); + globalScene.phaseManager.unshiftNew("GameOverPhase"); } else if ( globalScene.currentBattle.double && legalPlayerPokemon.length === 1 && @@ -176,25 +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 * is already on the field, unshift a phase that moves that Pokemon to center position. */ - globalScene.phaseManager.unshiftPhase(new ToggleDoublePositionPhase(true)); + globalScene.phaseManager.unshiftNew("ToggleDoublePositionPhase", true); } else if (legalPlayerPartyPokemon.length > 0) { /** * 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. */ - globalScene.phaseManager.pushPhase(new SwitchPhase(SwitchType.SWITCH, this.fieldIndex, true, false)); + globalScene.phaseManager.pushNew("SwitchPhase", SwitchType.SWITCH, this.fieldIndex, true, false); } } else { - globalScene.phaseManager.unshiftPhase(new VictoryPhase(this.battlerIndex)); + globalScene.phaseManager.unshiftNew("VictoryPhase", this.battlerIndex); if ([BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(globalScene.currentBattle.battleType)) { const hasReservePartyMember = !!globalScene .getEnemyParty() .filter(p => p.isActive() && !p.isOnField() && p.trainerSlot === (pokemon as EnemyPokemon).trainerSlot) .length; if (hasReservePartyMember) { - globalScene.phaseManager.pushPhase( - new SwitchSummonPhase(SwitchType.SWITCH, this.fieldIndex, -1, false, false), - ); + globalScene.phaseManager.pushNew("SwitchSummonPhase", SwitchType.SWITCH, this.fieldIndex, -1, false, false); } } } @@ -249,7 +241,7 @@ export class FaintPhase extends PokemonPhase { } else { // Final boss' HP threshold has been bypassed; cancel faint and force check for 2nd phase enemy.hp++; - globalScene.phaseManager.unshiftPhase(new DamageAnimPhase(enemy.getBattlerIndex(), 0, HitResult.INDIRECT)); + globalScene.phaseManager.unshiftNew("DamageAnimPhase", enemy.getBattlerIndex(), 0, HitResult.INDIRECT); this.end(); } return true; diff --git a/src/phases/form-change-phase.ts b/src/phases/form-change-phase.ts index c0d2a9a11eb..3813359d432 100644 --- a/src/phases/form-change-phase.ts +++ b/src/phases/form-change-phase.ts @@ -7,7 +7,6 @@ import type { PlayerPokemon } from "../field/pokemon"; import { UiMode } from "#enums/ui-mode"; import type PartyUiHandler from "../ui/party-ui-handler"; import { getPokemonNameWithAffix } from "../messages"; -import { EndEvolutionPhase } from "./end-evolution-phase"; import { EvolutionPhase } from "./evolution-phase"; import { BattlerTagType } from "#enums/battler-tag-type"; import { SpeciesFormKey } from "#enums/species-form-key"; @@ -100,7 +99,7 @@ export class FormChangePhase extends EvolutionPhase { globalScene.time.delayedCall(900, () => { this.pokemon.changeForm(this.formChange).then(() => { if (!this.modal) { - globalScene.phaseManager.unshiftPhase(new EndEvolutionPhase()); + globalScene.phaseManager.unshiftNew("EndEvolutionPhase"); } globalScene.playSound("se/shine"); diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts index 166bb955c24..5fabc5cee81 100644 --- a/src/phases/game-over-phase.ts +++ b/src/phases/game-over-phase.ts @@ -9,14 +9,7 @@ import { trainerConfigs } from "#app/data/trainers/trainer-config"; import type Pokemon from "#app/field/pokemon"; import { modifierTypes } from "#app/modifier/modifier-type"; import { BattlePhase } from "#app/phases/battle-phase"; -import { CheckSwitchPhase } from "#app/phases/check-switch-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 type { EndCardPhase } from "#app/phases/end-card-phase"; import { achvs, ChallengeAchv } from "#app/system/achv"; import { Unlockables } from "#app/system/unlockables"; import { UiMode } from "#enums/ui-mode"; @@ -31,7 +24,6 @@ import ChallengeData from "#app/system/challenge-data"; import TrainerData from "#app/system/trainer-data"; import ArenaData from "#app/system/arena-data"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; -import { MessagePhase } from "./message-phase"; export class GameOverPhase extends BattlePhase { public readonly phaseName = "GameOverPhase"; @@ -86,21 +78,21 @@ export class GameOverPhase extends BattlePhase { globalScene.reset(); globalScene.phaseManager.clearPhaseQueue(); globalScene.gameData.loadSession(globalScene.sessionSlotId).then(() => { - globalScene.phaseManager.pushPhase(new EncounterPhase(true)); + globalScene.phaseManager.pushNew("EncounterPhase", true); const availablePartyMembers = globalScene.getPokemonAllowedInBattle().length; - globalScene.phaseManager.pushPhase(new SummonPhase(0)); + globalScene.phaseManager.pushNew("SummonPhase", 0); if (globalScene.currentBattle.double && availablePartyMembers > 1) { - globalScene.phaseManager.pushPhase(new SummonPhase(1)); + globalScene.phaseManager.pushNew("SummonPhase", 1); } if ( globalScene.currentBattle.waveIndex > 1 && globalScene.currentBattle.battleType !== BattleType.TRAINER ) { - globalScene.phaseManager.pushPhase(new CheckSwitchPhase(0, globalScene.currentBattle.double)); + globalScene.phaseManager.pushNew("CheckSwitchPhase", 0, globalScene.currentBattle.double); if (globalScene.currentBattle.double && availablePartyMembers > 1) { - globalScene.phaseManager.pushPhase(new CheckSwitchPhase(1, globalScene.currentBattle.double)); + globalScene.phaseManager.pushNew("CheckSwitchPhase", 1, globalScene.currentBattle.double); } } @@ -160,17 +152,15 @@ export class GameOverPhase extends BattlePhase { this.handleUnlocks(); for (const species of this.firstRibbons) { - globalScene.phaseManager.unshiftPhase( - new RibbonModifierRewardPhase(modifierTypes.VOUCHER_PLUS, species), - ); + globalScene.phaseManager.unshiftNew("RibbonModifierRewardPhase", modifierTypes.VOUCHER_PLUS, species); } if (!firstClear) { - globalScene.phaseManager.unshiftPhase(new GameOverModifierRewardPhase(modifierTypes.VOUCHER_PREMIUM)); + globalScene.phaseManager.unshiftNew("GameOverModifierRewardPhase", modifierTypes.VOUCHER_PREMIUM); } } this.getRunHistoryEntry().then(runHistoryEntry => { globalScene.gameData.saveRunHistory(runHistoryEntry, this.isVictory); - globalScene.phaseManager.pushPhase(new PostGameOverPhase(endCardPhase)); + globalScene.phaseManager.pushNew("PostGameOverPhase", endCardPhase); this.end(); }); }; @@ -199,7 +189,7 @@ export class GameOverPhase extends BattlePhase { () => { globalScene.ui.fadeOut(500).then(() => { globalScene.charSprite.hide().then(() => { - const endCardPhase = new EndCardPhase(); + const endCardPhase = globalScene.phaseManager.create("EndCardPhase"); globalScene.phaseManager.unshiftPhase(endCardPhase); clear(endCardPhase); }); @@ -209,7 +199,7 @@ export class GameOverPhase extends BattlePhase { }); }); } else { - const endCardPhase = new EndCardPhase(); + const endCardPhase = globalScene.phaseManager.create("EndCardPhase"); globalScene.phaseManager.unshiftPhase(endCardPhase); clear(endCardPhase); } @@ -234,7 +224,7 @@ export class GameOverPhase extends BattlePhase { .catch(_err => { globalScene.phaseManager.clearPhaseQueue(); globalScene.phaseManager.clearPhaseQueueSplice(); - globalScene.phaseManager.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. setTimeout(() => { window.location.reload(); @@ -253,22 +243,22 @@ export class GameOverPhase extends BattlePhase { handleUnlocks(): void { if (this.isVictory && globalScene.gameMode.isClassic) { if (!globalScene.gameData.unlocks[Unlockables.ENDLESS_MODE]) { - globalScene.phaseManager.unshiftPhase(new UnlockPhase(Unlockables.ENDLESS_MODE)); + globalScene.phaseManager.unshiftNew("UnlockPhase", Unlockables.ENDLESS_MODE); } if ( globalScene.getPlayerParty().filter(p => p.fusionSpecies).length && !globalScene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE] ) { - globalScene.phaseManager.unshiftPhase(new UnlockPhase(Unlockables.SPLICED_ENDLESS_MODE)); + globalScene.phaseManager.unshiftNew("UnlockPhase", Unlockables.SPLICED_ENDLESS_MODE); } if (!globalScene.gameData.unlocks[Unlockables.MINI_BLACK_HOLE]) { - globalScene.phaseManager.unshiftPhase(new UnlockPhase(Unlockables.MINI_BLACK_HOLE)); + globalScene.phaseManager.unshiftNew("UnlockPhase", Unlockables.MINI_BLACK_HOLE); } if ( !globalScene.gameData.unlocks[Unlockables.EVIOLITE] && globalScene.getPlayerParty().some(p => p.getSpeciesForm(true).speciesId in pokemonEvolutions) ) { - globalScene.phaseManager.unshiftPhase(new UnlockPhase(Unlockables.EVIOLITE)); + globalScene.phaseManager.unshiftNew("UnlockPhase", Unlockables.EVIOLITE); } } } diff --git a/src/phases/level-up-phase.ts b/src/phases/level-up-phase.ts index b3b82f13f42..c78a1798304 100644 --- a/src/phases/level-up-phase.ts +++ b/src/phases/level-up-phase.ts @@ -2,8 +2,6 @@ import { globalScene } from "#app/global-scene"; import { ExpNotification } from "#app/enums/exp-notification"; import type { PlayerPokemon } from "#app/field/pokemon"; 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 { LevelAchv } from "#app/system/achv"; import { NumberHolder } from "#app/utils/common"; @@ -66,14 +64,14 @@ export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { // this feels like an unnecessary optimization const levelMoves = this.getPokemon().getLevelMoves(this.lastLevel + 1); for (const lm of levelMoves) { - globalScene.phaseManager.unshiftPhase(new LearnMovePhase(this.partyMemberIndex, lm[1])); + globalScene.phaseManager.unshiftNew("LearnMovePhase", this.partyMemberIndex, lm[1]); } } if (!this.pokemon.pauseEvolutions) { const evolution = this.pokemon.getEvolution(); if (evolution) { this.pokemon.breakIllusion(); - globalScene.phaseManager.unshiftPhase(new EvolutionPhase(this.pokemon, evolution, this.lastLevel)); + globalScene.phaseManager.unshiftNew("EvolutionPhase", this.pokemon, evolution, this.lastLevel); } } return super.end(); diff --git a/src/phases/login-phase.ts b/src/phases/login-phase.ts index 5e1728d4415..de426866baa 100644 --- a/src/phases/login-phase.ts +++ b/src/phases/login-phase.ts @@ -7,8 +7,6 @@ import { UiMode } from "#enums/ui-mode"; import i18next, { t } from "i18next"; import { sessionIdKey, executeIf } from "#app/utils/common"; import { getCookie, removeCookie } from "#app/utils/cookies"; -import { SelectGenderPhase } from "./select-gender-phase"; -import { UnavailablePhase } from "./unavailable-phase"; export class LoginPhase extends Phase { public readonly phaseName = "LoginPhase"; @@ -70,7 +68,7 @@ export class LoginPhase extends Phase { }); }, () => { - globalScene.phaseManager.unshiftPhase(new LoginPhase(false)); + globalScene.phaseManager.unshiftNew("LoginPhase", false); this.end(); }, ], @@ -94,7 +92,7 @@ export class LoginPhase extends Phase { removeCookie(sessionIdKey); globalScene.reset(true, true); } else { - globalScene.phaseManager.unshiftPhase(new UnavailablePhase()); + globalScene.phaseManager.unshiftNew("UnavailablePhase"); super.end(); } return null; @@ -114,7 +112,7 @@ export class LoginPhase extends Phase { globalScene.ui.setMode(UiMode.MESSAGE); if (!globalScene.gameData.gender) { - globalScene.phaseManager.unshiftPhase(new SelectGenderPhase()); + globalScene.phaseManager.unshiftNew("SelectGenderPhase"); } handleTutorial(Tutorial.Intro).then(() => super.end()); diff --git a/src/phases/message-phase.ts b/src/phases/message-phase.ts index 2a485d837b0..61f9b74a037 100644 --- a/src/phases/message-phase.ts +++ b/src/phases/message-phase.ts @@ -44,8 +44,13 @@ export class MessagePhase extends Phase { page0 = page0.split(repname[p]).join(pokename[p]); page1 = page1.split(repname[p]).join(pokename[p]); } - globalScene.phaseManager.unshiftPhase( - new MessagePhase(page1, this.callbackDelay, this.prompt, this.promptDelay, this.speaker), + globalScene.phaseManager.unshiftNew( + "MessagePhase", + page1, + this.callbackDelay, + this.prompt, + this.promptDelay, + this.speaker, ); this.text = page0.trim(); } else { diff --git a/src/phases/move-charge-phase.ts b/src/phases/move-charge-phase.ts index 263306acbf2..a481f4e37b8 100644 --- a/src/phases/move-charge-phase.ts +++ b/src/phases/move-charge-phase.ts @@ -6,7 +6,6 @@ import type { PokemonMove } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { MoveResult } from "#app/field/pokemon"; import { BooleanHolder } from "#app/utils/common"; -import { MovePhase } from "#app/phases/move-phase"; import { PokemonPhase } from "#app/phases/pokemon-phase"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -64,7 +63,7 @@ export class MoveChargePhase extends PokemonPhase { // this MoveEndPhase will be duplicated by the queued MovePhase if not removed globalScene.phaseManager.tryRemovePhase(phase => phase.is("MoveEndPhase") && phase.getPokemon() === user); // queue a new MovePhase for this move's attack phase - globalScene.phaseManager.unshiftPhase(new MovePhase(user, [this.targetIndex], this.move, false)); + globalScene.phaseManager.unshiftNew("MovePhase", user, [this.targetIndex], this.move, false); } else { user.getMoveQueue().push({ move: move.id, targets: [this.targetIndex] }); } diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index e135f5bd161..e0fa381447b 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -68,15 +68,10 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { MoveId } from "#enums/move-id"; import i18next from "i18next"; 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 { HitCheckResult } from "#enums/hit-check-result"; import type Move from "#app/data/moves/move"; import { isFieldTargeted } from "#app/data/moves/move-utils"; -import { FaintPhase } from "./faint-phase"; import { DamageAchv } from "#app/system/achv"; type HitCheckEntry = [HitCheckResult, TypeDamageMultiplier]; @@ -191,13 +186,25 @@ export class MoveEffectPhase extends PokemonPhase { // TODO: ability displays should be handled by the ability if (!target.getTag(BattlerTagType.MAGIC_COAT)) { 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( - 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, + ), ); } @@ -384,7 +391,7 @@ export class MoveEffectPhase extends PokemonPhase { } if (this.queuedPhases.length) { - globalScene.phaseManager.appendToPhase(this.queuedPhases, MoveEndPhase); + globalScene.phaseManager.appendToPhase(this.queuedPhases, "MoveEndPhase"); } const moveType = user.getMoveType(this.move, true); if (this.move.category !== MoveCategory.STATUS && !user.stellarTypesBoosted.includes(moveType)) { @@ -903,7 +910,7 @@ export class MoveEffectPhase extends PokemonPhase { // set splice index here, so future scene queues happen before FaintedPhase globalScene.phaseManager.setPhaseQueueSplice(); - globalScene.phaseManager.unshiftPhase(new FaintPhase(target.getBattlerIndex(), false, user)); + globalScene.phaseManager.unshiftNew("FaintPhase", target.getBattlerIndex(), false, user); target.destroySubstitute(); target.lapseTag(BattlerTagType.COMMANDED); diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 03f94ad3d1d..7fc6a86e3f7 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -39,10 +39,6 @@ import { MoveResult } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; 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 { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#enums/arena-tag-type"; @@ -271,12 +267,11 @@ export class MovePhase extends BattlePhase { globalScene.phaseManager.queueMessage( getStatusEffectActivationText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)), ); - globalScene.phaseManager.unshiftPhase( - new CommonAnimPhase( - this.pokemon.getBattlerIndex(), - undefined, - CommonAnim.POISON + (this.pokemon.status.effect - 1), - ), + globalScene.phaseManager.unshiftNew( + "CommonAnimPhase", + this.pokemon.getBattlerIndex(), + undefined, + CommonAnim.POISON + (this.pokemon.status.effect - 1), ); } else if (healed) { globalScene.phaseManager.queueMessage( @@ -407,8 +402,13 @@ export class MovePhase extends BattlePhase { if (success) { const move = this.move.getMove(); applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, move); - globalScene.phaseManager.unshiftPhase( - new MoveEffectPhase(this.pokemon.getBattlerIndex(), this.targets, move, this.reflected, this.move.virtual), + globalScene.phaseManager.unshiftNew( + "MoveEffectPhase", + this.pokemon.getBattlerIndex(), + this.targets, + move, + this.reflected, + this.move.virtual, ); } else { if ([MoveId.ROAR, MoveId.WHIRLWIND, MoveId.TRICK_OR_TREAT, MoveId.FORESTS_CURSE].includes(this.move.moveId)) { @@ -457,8 +457,11 @@ export class MovePhase extends BattlePhase { applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove()); this.showMoveText(); - globalScene.phaseManager.unshiftPhase( - new MoveChargePhase(this.pokemon.getBattlerIndex(), this.targets[0], this.move), + globalScene.phaseManager.unshiftNew( + "MoveChargePhase", + this.pokemon.getBattlerIndex(), + this.targets[0], + this.move, ); } else { this.pokemon.pushMoveHistory({ @@ -481,8 +484,11 @@ export class MovePhase extends BattlePhase { * Queues a {@linkcode MoveEndPhase} and then ends the phase */ public end(): void { - globalScene.phaseManager.unshiftPhase( - new MoveEndPhase(this.pokemon.getBattlerIndex(), this.getActiveTargetPokemon(), this.followUp), + globalScene.phaseManager.unshiftNew( + "MoveEndPhase", + this.pokemon.getBattlerIndex(), + this.getActiveTargetPokemon(), + this.followUp, ); super.end(); diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index b1ca11d45a5..1f27db7ff64 100644 --- a/src/phases/mystery-encounter-phases.ts +++ b/src/phases/mystery-encounter-phases.ts @@ -3,15 +3,6 @@ import type { OptionPhaseCallback } from "#app/data/mystery-encounters/mystery-e import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; import { SeenEncounterData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; 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 { 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 { BattlerTagType } from "#enums/battler-tag-type"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; @@ -26,7 +17,6 @@ import { IvScannerModifier } from "../modifier/modifier"; import { Phase } from "../phase"; import { UiMode } from "#enums/ui-mode"; import { isNullOrUndefined, randSeedItem } from "#app/utils/common"; -import { SelectBiomePhase } from "./select-biome-phase"; /** * Will handle (in order): @@ -124,7 +114,7 @@ export class MysteryEncounterPhase extends Phase { */ continueEncounter() { const endDialogueAndContinueEncounter = () => { - globalScene.phaseManager.pushPhase(new MysteryEncounterOptionSelectedPhase()); + globalScene.phaseManager.pushNew("MysteryEncounterOptionSelectedPhase"); this.end(); }; @@ -256,7 +246,7 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase { // The total number of legal player Pokemon that aren't currently on the field const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true)); if (!legalPlayerPokemon.length) { - globalScene.phaseManager.unshiftPhase(new GameOverPhase()); + globalScene.phaseManager.unshiftNew("GameOverPhase"); return this.end(); } @@ -265,13 +255,13 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase { const playerField = globalScene.getPlayerField(); playerField.forEach((pokemon, i) => { if (!pokemon.isAllowedInBattle() && legalPlayerPartyPokemon.length > i) { - globalScene.phaseManager.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 if (globalScene.currentBattle.double && legalPlayerPokemon.length === 1 && legalPlayerPartyPokemon.length === 0) { - globalScene.phaseManager.unshiftPhase(new ToggleDoublePositionPhase(true)); + globalScene.phaseManager.unshiftNew("ToggleDoublePositionPhase", true); } this.end(); @@ -348,9 +338,9 @@ export class MysteryEncounterBattlePhase extends Phase { globalScene.playBgm(); } const availablePartyMembers = globalScene.getEnemyParty().filter(p => !p.isFainted()).length; - globalScene.phaseManager.unshiftPhase(new SummonPhase(0, false)); + globalScene.phaseManager.unshiftNew("SummonPhase", 0, false); if (globalScene.currentBattle.double && availablePartyMembers > 1) { - globalScene.phaseManager.unshiftPhase(new SummonPhase(1, false)); + globalScene.phaseManager.unshiftNew("SummonPhase", 1, false); } if (!globalScene.currentBattle.mysteryEncounter?.hideBattleIntroMessage) { @@ -368,9 +358,9 @@ export class MysteryEncounterBattlePhase extends Phase { const doTrainerSummon = () => { this.hideEnemyTrainer(); const availablePartyMembers = globalScene.getEnemyParty().filter(p => !p.isFainted()).length; - globalScene.phaseManager.unshiftPhase(new SummonPhase(0, false)); + globalScene.phaseManager.unshiftNew("SummonPhase", 0, false); if (globalScene.currentBattle.double && availablePartyMembers > 1) { - globalScene.phaseManager.unshiftPhase(new SummonPhase(1, false)); + globalScene.phaseManager.unshiftNew("SummonPhase", 1, false); } this.endBattleSetup(); }; @@ -426,37 +416,37 @@ export class MysteryEncounterBattlePhase extends Phase { if (encounterMode !== MysteryEncounterMode.TRAINER_BATTLE) { const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier); if (ivScannerModifier) { - enemyField.map(p => globalScene.phaseManager.pushPhase(new ScanIvsPhase(p.getBattlerIndex()))); + enemyField.map(p => globalScene.phaseManager.pushNew("ScanIvsPhase", p.getBattlerIndex())); } } const availablePartyMembers = globalScene.getPlayerParty().filter(p => p.isAllowedInBattle()); if (!availablePartyMembers[0].isOnField()) { - globalScene.phaseManager.pushPhase(new SummonPhase(0)); + globalScene.phaseManager.pushNew("SummonPhase", 0); } if (globalScene.currentBattle.double) { if (availablePartyMembers.length > 1) { - globalScene.phaseManager.pushPhase(new ToggleDoublePositionPhase(true)); + globalScene.phaseManager.pushNew("ToggleDoublePositionPhase", true); if (!availablePartyMembers[1].isOnField()) { - globalScene.phaseManager.pushPhase(new SummonPhase(1)); + globalScene.phaseManager.pushNew("SummonPhase", 1); } } } else { if (availablePartyMembers.length > 1 && availablePartyMembers[1].isOnField()) { globalScene.getPlayerField().forEach(pokemon => pokemon.lapseTag(BattlerTagType.COMMANDED)); - globalScene.phaseManager.pushPhase(new ReturnPhase(1)); + globalScene.phaseManager.pushNew("ReturnPhase", 1); } - globalScene.phaseManager.pushPhase(new ToggleDoublePositionPhase(false)); + globalScene.phaseManager.pushNew("ToggleDoublePositionPhase", false); } if (encounterMode !== MysteryEncounterMode.TRAINER_BATTLE && !this.disableSwitch) { const minPartySize = globalScene.currentBattle.double ? 2 : 1; if (availablePartyMembers.length > minPartySize) { - globalScene.phaseManager.pushPhase(new CheckSwitchPhase(0, globalScene.currentBattle.double)); + globalScene.phaseManager.pushNew("CheckSwitchPhase", 0, globalScene.currentBattle.double); if (globalScene.currentBattle.double) { - globalScene.phaseManager.pushPhase(new CheckSwitchPhase(1, globalScene.currentBattle.double)); + globalScene.phaseManager.pushNew("CheckSwitchPhase", 1, globalScene.currentBattle.double); } } } @@ -563,15 +553,13 @@ export class MysteryEncounterRewardsPhase extends Phase { encounter.doEncounterRewards(); } else if (this.addHealPhase) { globalScene.phaseManager.tryRemovePhase(p => p.is("SelectModifierPhase")); - globalScene.phaseManager.unshiftPhase( - new SelectModifierPhase(0, undefined, { - fillRemaining: false, - rerollMultiplier: -1, - }), - ); + globalScene.phaseManager.unshiftNew("SelectModifierPhase", 0, undefined, { + fillRemaining: false, + rerollMultiplier: -1, + }); } - globalScene.phaseManager.pushPhase(new PostMysteryEncounterPhase()); + globalScene.phaseManager.pushNew("PostMysteryEncounterPhase"); this.end(); } } @@ -618,10 +606,10 @@ export class PostMysteryEncounterPhase extends Phase { continueEncounter() { const endPhase = () => { if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) { - globalScene.phaseManager.pushPhase(new SelectBiomePhase()); + globalScene.phaseManager.pushNew("SelectBiomePhase"); } - globalScene.phaseManager.pushPhase(new NewBattlePhase()); + globalScene.phaseManager.pushNew("NewBattlePhase"); this.end(); }; diff --git a/src/phases/post-game-over-phase.ts b/src/phases/post-game-over-phase.ts index 37a3297cc52..8e19dcd5498 100644 --- a/src/phases/post-game-over-phase.ts +++ b/src/phases/post-game-over-phase.ts @@ -1,7 +1,6 @@ import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; import type { EndCardPhase } from "./end-card-phase"; -import { TitlePhase } from "./title-phase"; export class PostGameOverPhase extends Phase { public readonly phaseName = "PostGameOverPhase"; @@ -28,7 +27,7 @@ export class PostGameOverPhase extends Phase { return globalScene.reset(true); } globalScene.reset(); - globalScene.phaseManager.unshiftPhase(new TitlePhase()); + globalScene.phaseManager.unshiftNew("TitlePhase"); this.end(); }); }); diff --git a/src/phases/quiet-form-change-phase.ts b/src/phases/quiet-form-change-phase.ts index 363b026831f..6b65c2a5140 100644 --- a/src/phases/quiet-form-change-phase.ts +++ b/src/phases/quiet-form-change-phase.ts @@ -9,7 +9,6 @@ import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { BattlePhase } from "./battle-phase"; import type { MovePhase } from "./move-phase"; -import { PokemonHealPhase } from "./pokemon-heal-phase"; import { applyAbAttrs, ClearTerrainAbAttr, @@ -159,8 +158,15 @@ export class QuietFormChangePhase extends BattlePhase { this.pokemon.findAndRemoveTags(t => t.tagType === BattlerTagType.AUTOTOMIZED); if (globalScene?.currentBattle.battleSpec === BattleSpec.FINAL_BOSS && this.pokemon.isEnemy()) { globalScene.playBgm(); - globalScene.phaseManager.unshiftPhase( - new PokemonHealPhase(this.pokemon.getBattlerIndex(), this.pokemon.getMaxHp(), null, false, false, false, true), + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + this.pokemon.getBattlerIndex(), + this.pokemon.getMaxHp(), + null, + false, + false, + false, + true, ); this.pokemon.findAndRemoveTags(() => true); this.pokemon.bossSegments = 5; diff --git a/src/phases/revival-blessing-phase.ts b/src/phases/revival-blessing-phase.ts index 2db73103a88..e3e69f7ef25 100644 --- a/src/phases/revival-blessing-phase.ts +++ b/src/phases/revival-blessing-phase.ts @@ -6,8 +6,6 @@ import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; import { toDmgValue, isNullOrUndefined } from "#app/utils/common"; import { BattlePhase } from "#app/phases/battle-phase"; -import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; -import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase"; import type { PlayerPokemon } from "#app/field/pokemon"; /** @@ -51,16 +49,26 @@ export class RevivalBlessingPhase extends BattlePhase { ) { if (slotIndex <= 1) { // Revived ally pokemon - globalScene.phaseManager.unshiftPhase( - new SwitchSummonPhase(SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, true), + globalScene.phaseManager.unshiftNew( + "SwitchSummonPhase", + SwitchType.SWITCH, + pokemon.getFieldIndex(), + slotIndex, + false, + true, ); - globalScene.phaseManager.unshiftPhase(new ToggleDoublePositionPhase(true)); + globalScene.phaseManager.unshiftNew("ToggleDoublePositionPhase", true); } else if (allyPokemon.isFainted()) { // Revived party pokemon, and ally pokemon is fainted - globalScene.phaseManager.unshiftPhase( - new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, true), + globalScene.phaseManager.unshiftNew( + "SwitchSummonPhase", + SwitchType.SWITCH, + allyPokemon.getFieldIndex(), + slotIndex, + false, + true, ); - globalScene.phaseManager.unshiftPhase(new ToggleDoublePositionPhase(true)); + globalScene.phaseManager.unshiftNew("ToggleDoublePositionPhase", true); } } } diff --git a/src/phases/select-biome-phase.ts b/src/phases/select-biome-phase.ts index 633bf20d34c..e8b4946b6d1 100644 --- a/src/phases/select-biome-phase.ts +++ b/src/phases/select-biome-phase.ts @@ -6,8 +6,6 @@ import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler" import { UiMode } from "#enums/ui-mode"; import { BattlePhase } from "./battle-phase"; import { randSeedInt } from "#app/utils/common"; -import { PartyHealPhase } from "./party-heal-phase"; -import { SwitchBiomePhase } from "./switch-biome-phase"; export class SelectBiomePhase extends BattlePhase { public readonly phaseName = "SelectBiomePhase"; @@ -22,9 +20,9 @@ export class SelectBiomePhase extends BattlePhase { const setNextBiome = (nextBiome: BiomeId) => { if (nextWaveIndex % 10 === 1) { globalScene.applyModifiers(MoneyInterestModifier, true); - globalScene.phaseManager.unshiftPhase(new PartyHealPhase(false)); + globalScene.phaseManager.unshiftNew("PartyHealPhase", false); } - globalScene.phaseManager.unshiftPhase(new SwitchBiomePhase(nextBiome)); + globalScene.phaseManager.unshiftNew("SwitchBiomePhase", nextBiome); this.end(); }; diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 5ac3b9e6d76..4d790e70e1b 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -123,11 +123,10 @@ export class SelectModifierPhase extends BattlePhase { return false; } globalScene.reroll = true; - globalScene.phaseManager.unshiftPhase( - new SelectModifierPhase( - this.rerollCount + 1, - this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[], - ), + globalScene.phaseManager.unshiftNew( + "SelectModifierPhase", + this.rerollCount + 1, + this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[], ); globalScene.ui.clearText(); globalScene.ui.setMode(UiMode.MESSAGE).then(() => super.end()); @@ -419,7 +418,8 @@ export class SelectModifierPhase extends BattlePhase { } copy(): SelectModifierPhase { - return new SelectModifierPhase( + return globalScene.phaseManager.create( + "SelectModifierPhase", this.rerollCount, this.modifierTiers, { diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index 2b60fcaf054..88d4fe06a9b 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -6,7 +6,6 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { overrideHeldItems, overrideModifiers } from "#app/modifier/modifier"; import Overrides from "#app/overrides"; import { Phase } from "#app/phase"; -import { TitlePhase } from "#app/phases/title-phase"; import { SaveSlotUiMode } from "#app/ui/save-slot-select-ui-handler"; import type { Starter } from "#app/ui/starter-select-ui-handler"; import { UiMode } from "#enums/ui-mode"; @@ -26,7 +25,7 @@ export class SelectStarterPhase extends Phase { globalScene.ui.setMode(UiMode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: number) => { if (slotId === -1) { globalScene.phaseManager.clearPhaseQueue(); - globalScene.phaseManager.pushPhase(new TitlePhase()); + globalScene.phaseManager.pushNew("TitlePhase"); return this.end(); } globalScene.sessionSlotId = slotId; diff --git a/src/phases/select-target-phase.ts b/src/phases/select-target-phase.ts index 515f7ed98ca..fcbd3aeb679 100644 --- a/src/phases/select-target-phase.ts +++ b/src/phases/select-target-phase.ts @@ -2,7 +2,6 @@ import { globalScene } from "#app/global-scene"; import type { BattlerIndex } from "#app/battle"; import { Command } from "#app/ui/command-ui-handler"; import { UiMode } from "#enums/ui-mode"; -import { CommandPhase } from "./command-phase"; import { PokemonPhase } from "./pokemon-phase"; import i18next from "#app/plugins/i18n"; import { allMoves } from "#app/data/data-lists"; @@ -33,7 +32,7 @@ export class SelectTargetPhase extends PokemonPhase { } if (targets.length < 1) { globalScene.currentBattle.turnCommands[this.fieldIndex] = null; - globalScene.phaseManager.unshiftPhase(new CommandPhase(this.fieldIndex)); + globalScene.phaseManager.unshiftNew("CommandPhase", this.fieldIndex); } else { turnCommand!.targets = targets; //TODO: is the bang correct here? } diff --git a/src/phases/show-ability-phase.ts b/src/phases/show-ability-phase.ts index e4be6124784..af295b72622 100644 --- a/src/phases/show-ability-phase.ts +++ b/src/phases/show-ability-phase.ts @@ -2,7 +2,6 @@ import { globalScene } from "#app/global-scene"; import type { BattlerIndex } from "#app/battle"; import { PokemonPhase } from "./pokemon-phase"; import { getPokemonNameWithAffix } from "#app/messages"; -import { HideAbilityPhase } from "#app/phases/hide-ability-phase"; export class ShowAbilityPhase extends PokemonPhase { public readonly phaseName = "ShowAbilityPhase"; @@ -36,8 +35,8 @@ export class ShowAbilityPhase extends PokemonPhase { // If the bar is already out, hide it before showing the new one if (globalScene.abilityBar.isVisible()) { - globalScene.phaseManager.unshiftPhase(new HideAbilityPhase()); - globalScene.phaseManager.unshiftPhase(new ShowAbilityPhase(this.battlerIndex, this.passive)); + globalScene.phaseManager.unshiftNew("HideAbilityPhase"); + globalScene.phaseManager.unshiftNew("ShowAbilityPhase", this.battlerIndex, this.passive); return this.end(); } diff --git a/src/phases/show-party-exp-bar-phase.ts b/src/phases/show-party-exp-bar-phase.ts index 765bd498f66..4849526b639 100644 --- a/src/phases/show-party-exp-bar-phase.ts +++ b/src/phases/show-party-exp-bar-phase.ts @@ -3,8 +3,6 @@ import { ExpGainsSpeed } from "#app/enums/exp-gains-speed"; import { ExpNotification } from "#app/enums/exp-notification"; import { ExpBoosterModifier } from "#app/modifier/modifier"; import { NumberHolder } from "#app/utils/common"; -import { HidePartyExpBarPhase } from "./hide-party-exp-bar-phase"; -import { LevelUpPhase } from "./level-up-phase"; import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { @@ -29,9 +27,9 @@ export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { pokemon.addExp(exp.value); const newLevel = pokemon.level; if (newLevel > lastLevel) { - globalScene.phaseManager.unshiftPhase(new LevelUpPhase(this.partyMemberIndex, lastLevel, newLevel)); + globalScene.phaseManager.unshiftNew("LevelUpPhase", this.partyMemberIndex, lastLevel, newLevel); } - globalScene.phaseManager.unshiftPhase(new HidePartyExpBarPhase()); + globalScene.phaseManager.unshiftNew("HidePartyExpBarPhase"); pokemon.updateInfo(); if (globalScene.expParty === ExpNotification.SKIP) { diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index 49f3952ef01..ad2eeae1c48 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -72,18 +72,17 @@ export class StatStageChangePhase extends PokemonPhase { if (this.stats.length > 1) { for (let i = 0; i < this.stats.length; i++) { const stat = [this.stats[i]]; - globalScene.phaseManager.unshiftPhase( - new StatStageChangePhase( - this.battlerIndex, - this.selfTarget, - stat, - this.stages, - this.showMessage, - this.ignoreAbilities, - this.canBeCopied, - this.onChange, - this.comingFromMirrorArmorUser, - ), + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + this.battlerIndex, + this.selfTarget, + stat, + this.stages, + this.showMessage, + this.ignoreAbilities, + this.canBeCopied, + this.onChange, + this.comingFromMirrorArmorUser, ); } return this.end(); diff --git a/src/phases/summon-phase.ts b/src/phases/summon-phase.ts index 15f42e76a5a..921466dfead 100644 --- a/src/phases/summon-phase.ts +++ b/src/phases/summon-phase.ts @@ -9,9 +9,6 @@ import { FieldPosition } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import i18next from "i18next"; import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase"; -import { PostSummonPhase } from "./post-summon-phase"; -import { GameOverPhase } from "./game-over-phase"; -import { ShinySparklePhase } from "./shiny-sparkle-phase"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { applyPreSummonAbAttrs, PreSummonAbAttr } from "#app/data/abilities/ability"; import { globalScene } from "#app/global-scene"; @@ -58,7 +55,7 @@ export class SummonPhase extends PartyMemberPokemonPhase { console.error("Party Details:\n", party); console.error("All available Pokemon were fainted or illegal!"); globalScene.phaseManager.clearPhaseQueue(); - globalScene.phaseManager.unshiftPhase(new GameOverPhase()); + globalScene.phaseManager.unshiftNew("GameOverPhase"); this.end(); return; } @@ -275,7 +272,7 @@ export class SummonPhase extends PartyMemberPokemonPhase { const pokemon = this.getPokemon(); if (pokemon.isShiny(true)) { - globalScene.phaseManager.unshiftPhase(new ShinySparklePhase(pokemon.getBattlerIndex())); + globalScene.phaseManager.unshiftNew("ShinySparklePhase", pokemon.getBattlerIndex()); } pokemon.resetTurnData(); @@ -291,7 +288,7 @@ export class SummonPhase extends PartyMemberPokemonPhase { } queuePostSummon(): void { - globalScene.phaseManager.pushPhase(new PostSummonPhase(this.getPokemon().getBattlerIndex())); + globalScene.phaseManager.pushNew("PostSummonPhase", this.getPokemon().getBattlerIndex()); } end() { diff --git a/src/phases/switch-phase.ts b/src/phases/switch-phase.ts index 5f2ae55900e..8d18e29e6a6 100644 --- a/src/phases/switch-phase.ts +++ b/src/phases/switch-phase.ts @@ -3,7 +3,6 @@ import PartyUiHandler, { PartyOption, PartyUiMode } from "#app/ui/party-ui-handl import { UiMode } from "#enums/ui-mode"; import { SwitchType } from "#enums/switch-type"; import { BattlePhase } from "./battle-phase"; -import { SwitchSummonPhase } from "./switch-summon-phase"; /** * Opens the party selector UI and transitions into a {@linkcode SwitchSummonPhase} @@ -80,9 +79,7 @@ export class SwitchPhase extends BattlePhase { p => p.is("PostSummonPhase") && p.player && p.fieldIndex === this.fieldIndex, ); const switchType = option === PartyOption.PASS_BATON ? SwitchType.BATON_PASS : this.switchType; - globalScene.phaseManager.unshiftPhase( - new SwitchSummonPhase(switchType, fieldIndex, slotIndex, this.doReturn), - ); + globalScene.phaseManager.unshiftNew("SwitchSummonPhase", switchType, fieldIndex, slotIndex, this.doReturn); } globalScene.ui.setMode(UiMode.MESSAGE).then(() => super.end()); }, diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index de8b9f3a5d9..103af3db275 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -16,7 +16,6 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { SwitchEffectTransferModifier } from "#app/modifier/modifier"; import { Command } from "#app/ui/command-ui-handler"; import i18next from "i18next"; -import { PostSummonPhase } from "./post-summon-phase"; import { SummonPhase } from "./summon-phase"; import { SubstituteTag } from "#app/data/battler-tags"; import { SwitchType } from "#enums/switch-type"; @@ -265,6 +264,6 @@ export class SwitchSummonPhase extends SummonPhase { } queuePostSummon(): void { - globalScene.phaseManager.unshiftPhase(new PostSummonPhase(this.getPokemon().getBattlerIndex())); + globalScene.phaseManager.unshiftNew("PostSummonPhase", this.getPokemon().getBattlerIndex()); } } diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index fb980d87359..37ce294f237 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -20,11 +20,6 @@ import { SaveSlotUiMode } from "#app/ui/save-slot-select-ui-handler"; import { UiMode } from "#enums/ui-mode"; import { isLocal, isLocalServerConnected, isNullOrUndefined } from "#app/utils/common"; import i18next from "i18next"; -import { CheckSwitchPhase } from "./check-switch-phase"; -import { EncounterPhase } from "./encounter-phase"; -import { SelectChallengePhase } from "./select-challenge-phase"; -import { SelectStarterPhase } from "./select-starter-phase"; -import { SummonPhase } from "./summon-phase"; import { globalScene } from "#app/global-scene"; import Overrides from "#app/overrides"; @@ -125,7 +120,7 @@ export class TitlePhase extends Phase { label: i18next.t("menu:cancel"), handler: () => { globalScene.phaseManager.clearPhaseQueue(); - globalScene.phaseManager.pushPhase(new TitlePhase()); + globalScene.phaseManager.pushNew("TitlePhase"); super.end(); return true; }, @@ -200,7 +195,7 @@ export class TitlePhase extends Phase { globalScene.ui.setMode(UiMode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: number) => { globalScene.phaseManager.clearPhaseQueue(); if (slotId === -1) { - globalScene.phaseManager.pushPhase(new TitlePhase()); + globalScene.phaseManager.pushNew("TitlePhase"); return super.end(); } globalScene.sessionSlotId = slotId; @@ -304,23 +299,23 @@ export class TitlePhase extends Phase { globalScene.arena.preloadBgm(); globalScene.gameMode = getGameMode(this.gameMode); if (this.gameMode === GameModes.CHALLENGE) { - globalScene.phaseManager.pushPhase(new SelectChallengePhase()); + globalScene.phaseManager.pushNew("SelectChallengePhase"); } else { - globalScene.phaseManager.pushPhase(new SelectStarterPhase()); + globalScene.phaseManager.pushNew("SelectStarterPhase"); } globalScene.newArena(globalScene.gameMode.getStartingBiome()); } else { globalScene.playBgm(); } - globalScene.phaseManager.pushPhase(new EncounterPhase(this.loaded)); + globalScene.phaseManager.pushNew("EncounterPhase", this.loaded); if (this.loaded) { const availablePartyMembers = globalScene.getPokemonAllowedInBattle().length; - globalScene.phaseManager.pushPhase(new SummonPhase(0, true, true)); + globalScene.phaseManager.pushNew("SummonPhase", 0, true, true); if (globalScene.currentBattle.double && availablePartyMembers > 1) { - globalScene.phaseManager.pushPhase(new SummonPhase(1, true, true)); + globalScene.phaseManager.pushNew("SummonPhase", 1, true, true); } if ( @@ -329,9 +324,9 @@ export class TitlePhase extends Phase { ) { const minPartySize = globalScene.currentBattle.double ? 2 : 1; if (availablePartyMembers > minPartySize) { - globalScene.phaseManager.pushPhase(new CheckSwitchPhase(0, globalScene.currentBattle.double)); + globalScene.phaseManager.pushNew("CheckSwitchPhase", 0, globalScene.currentBattle.double); if (globalScene.currentBattle.double) { - globalScene.phaseManager.pushPhase(new CheckSwitchPhase(1, globalScene.currentBattle.double)); + globalScene.phaseManager.pushNew("CheckSwitchPhase", 1, globalScene.currentBattle.double); } } } diff --git a/src/phases/trainer-victory-phase.ts b/src/phases/trainer-victory-phase.ts index 5b7b26d52fb..5d35dd5d375 100644 --- a/src/phases/trainer-victory-phase.ts +++ b/src/phases/trainer-victory-phase.ts @@ -5,8 +5,6 @@ import { vouchers } from "#app/system/voucher"; import i18next from "i18next"; import { randSeedItem } from "#app/utils/common"; import { BattlePhase } from "./battle-phase"; -import { ModifierRewardPhase } from "./modifier-reward-phase"; -import { MoneyRewardPhase } from "./money-reward-phase"; import { TrainerSlot } from "#enums/trainer-slot"; import { globalScene } from "#app/global-scene"; import { BiomeId } from "#enums/biome-id"; @@ -20,13 +18,11 @@ export class TrainerVictoryPhase extends BattlePhase { globalScene.playBgm(globalScene.currentBattle.trainer?.config.victoryBgm); - globalScene.phaseManager.unshiftPhase( - new MoneyRewardPhase(globalScene.currentBattle.trainer?.config.moneyMultiplier!), - ); // TODO: is this bang correct? + globalScene.phaseManager.unshiftNew("MoneyRewardPhase", globalScene.currentBattle.trainer?.config.moneyMultiplier!); // TODO: is this bang correct? const modifierRewardFuncs = globalScene.currentBattle.trainer?.config.modifierRewardFuncs!; // TODO: is this bang correct? for (const modifierRewardFunc of modifierRewardFuncs) { - globalScene.phaseManager.unshiftPhase(new ModifierRewardPhase(modifierRewardFunc)); + globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierRewardFunc); } const trainerType = globalScene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct? @@ -37,23 +33,21 @@ export class TrainerVictoryPhase extends BattlePhase { globalScene.currentBattle.trainer?.config.isBoss ) { if (timedEventManager.getUpgradeUnlockedVouchers()) { - globalScene.phaseManager.unshiftPhase( - new ModifierRewardPhase( - [ - modifierTypes.VOUCHER_PLUS, - modifierTypes.VOUCHER_PLUS, - modifierTypes.VOUCHER_PLUS, - modifierTypes.VOUCHER_PREMIUM, - ][vouchers[TrainerType[trainerType]].voucherType], - ), + globalScene.phaseManager.unshiftNew( + "ModifierRewardPhase", + [ + modifierTypes.VOUCHER_PLUS, + modifierTypes.VOUCHER_PLUS, + modifierTypes.VOUCHER_PLUS, + modifierTypes.VOUCHER_PREMIUM, + ][vouchers[TrainerType[trainerType]].voucherType], ); } else { - globalScene.phaseManager.unshiftPhase( - new ModifierRewardPhase( - [modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM][ - vouchers[TrainerType[trainerType]].voucherType - ], - ), + globalScene.phaseManager.unshiftNew( + "ModifierRewardPhase", + [modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM][ + vouchers[TrainerType[trainerType]].voucherType + ], ); } } diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index 832f60043ce..85590d667d6 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -14,7 +14,6 @@ import { } from "#app/modifier/modifier"; import i18next from "i18next"; import { FieldPhase } from "./field-phase"; -import { PokemonHealPhase } from "./pokemon-heal-phase"; import { globalScene } from "#app/global-scene"; export class TurnEndPhase extends FieldPhase { @@ -34,15 +33,14 @@ export class TurnEndPhase extends FieldPhase { globalScene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon); if (globalScene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { - globalScene.phaseManager.unshiftPhase( - new PokemonHealPhase( - pokemon.getBattlerIndex(), - Math.max(pokemon.getMaxHp() >> 4, 1), - i18next.t("battle:turnEndHpRestore", { - pokemonName: getPokemonNameWithAffix(pokemon), - }), - true, - ), + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + pokemon.getBattlerIndex(), + Math.max(pokemon.getMaxHp() >> 4, 1), + i18next.t("battle:turnEndHpRestore", { + pokemonName: getPokemonNameWithAffix(pokemon), + }), + true, ); } diff --git a/src/phases/turn-init-phase.ts b/src/phases/turn-init-phase.ts index 61ec5fd8a71..e9d6f60af5e 100644 --- a/src/phases/turn-init-phase.ts +++ b/src/phases/turn-init-phase.ts @@ -6,12 +6,7 @@ import { import { TurnInitEvent } from "#app/events/battle-scene"; import type { PlayerPokemon } from "#app/field/pokemon"; import i18next from "i18next"; -import { CommandPhase } from "./command-phase"; -import { EnemyCommandPhase } from "./enemy-command-phase"; import { FieldPhase } from "./field-phase"; -import { GameOverPhase } from "./game-over-phase"; -import { ToggleDoublePositionPhase } from "./toggle-double-position-phase"; -import { TurnStartPhase } from "./turn-start-phase"; import { globalScene } from "#app/global-scene"; export class TurnInitPhase extends FieldPhase { @@ -33,7 +28,7 @@ export class TurnInitPhase extends FieldPhase { if (!allowedPokemon.length) { // If there are no longer any legal pokemon in the party, game over. globalScene.phaseManager.clearPhaseQueue(); - globalScene.phaseManager.unshiftPhase(new GameOverPhase()); + globalScene.phaseManager.unshiftNew("GameOverPhase"); } else if ( allowedPokemon.length >= globalScene.currentBattle.getBattlerCount() || (globalScene.currentBattle.double && !allowedPokemon[0].isActive(true)) @@ -46,7 +41,7 @@ export class TurnInitPhase extends FieldPhase { p.leaveField(); } if (allowedPokemon.length === 1 && globalScene.currentBattle.double) { - globalScene.phaseManager.unshiftPhase(new ToggleDoublePositionPhase(true)); + globalScene.phaseManager.unshiftNew("ToggleDoublePositionPhase", true); } } }); @@ -69,13 +64,15 @@ export class TurnInitPhase extends FieldPhase { pokemon.resetTurnData(); - globalScene.phaseManager.pushPhase( - pokemon.isPlayer() ? new CommandPhase(i) : new EnemyCommandPhase(i - BattlerIndex.ENEMY), - ); + if (pokemon.isPlayer()) { + globalScene.phaseManager.pushNew("CommandPhase", i); + } else { + globalScene.phaseManager.pushNew("EnemyCommandPhase", i - BattlerIndex.ENEMY); + } } }); - globalScene.phaseManager.pushPhase(new TurnStartPhase()); + globalScene.phaseManager.pushNew("TurnStartPhase"); this.end(); } diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index c07feac0888..557b67b6091 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -8,21 +8,11 @@ import { PokemonMove } from "#app/field/pokemon"; import { BypassSpeedChanceModifier } from "#app/modifier/modifier"; import { Command } from "#app/ui/command-ui-handler"; import { randSeedShuffle, BooleanHolder } from "#app/utils/common"; -import { AttemptCapturePhase } from "./attempt-capture-phase"; -import { AttemptRunPhase } from "./attempt-run-phase"; -import { BerryPhase } from "./berry-phase"; import { FieldPhase } from "./field-phase"; -import { MoveHeaderPhase } from "./move-header-phase"; -import { MovePhase } from "./move-phase"; -import { SwitchSummonPhase } from "./switch-summon-phase"; -import { TurnEndPhase } from "./turn-end-phase"; -import { WeatherEffectPhase } from "./weather-effect-phase"; -import { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase"; import { BattlerIndex } from "#app/battle"; import { TrickRoomTag } from "#app/data/arena-tag"; import { SwitchType } from "#enums/switch-type"; import { globalScene } from "#app/global-scene"; -import { TeraPhase } from "./tera-phase"; export class TurnStartPhase extends FieldPhase { public readonly phaseName = "TurnStartPhase"; @@ -153,10 +143,12 @@ export class TurnStartPhase extends FieldPhase { switch (preTurnCommand?.command) { case Command.TERA: - globalScene.phaseManager.pushPhase(new TeraPhase(pokemon)); + globalScene.phaseManager.pushNew("TeraPhase", pokemon); } } + const phaseManager = globalScene.phaseManager; + for (const o of moveOrder) { const pokemon = field[o]; const turnCommand = globalScene.currentBattle.turnCommands[o]; @@ -167,88 +159,94 @@ export class TurnStartPhase extends FieldPhase { switch (turnCommand?.command) { case Command.FIGHT: - const queuedMove = turnCommand.move; - pokemon.turnData.order = orderIndex++; - if (!queuedMove) { - continue; - } - const move = - pokemon.getMoveset().find(m => m.moveId === queuedMove.move && m.ppUsed < m.getMovePp()) || - new PokemonMove(queuedMove.move); - if (move.getMove().hasAttr(MoveHeaderAttr)) { - globalScene.phaseManager.unshiftPhase(new MoveHeaderPhase(pokemon, move)); - } - if (pokemon.isPlayer()) { - if (turnCommand.cursor === -1) { - globalScene.phaseManager.pushPhase( - new MovePhase(pokemon, turnCommand.targets || turnCommand.move!.targets, move), - ); //TODO: is the bang correct here? - } else { - const playerPhase = new MovePhase( - pokemon, - turnCommand.targets || turnCommand.move!.targets, - move, - false, - queuedMove.ignorePP, - ); //TODO: is the bang correct here? - globalScene.phaseManager.pushPhase(playerPhase); + { + const queuedMove = turnCommand.move; + pokemon.turnData.order = orderIndex++; + if (!queuedMove) { + continue; } - } else { - globalScene.phaseManager.pushPhase( - new MovePhase( + const move = + pokemon.getMoveset().find(m => m.moveId === queuedMove.move && m.ppUsed < m.getMovePp()) || + new PokemonMove(queuedMove.move); + if (move.getMove().hasAttr(MoveHeaderAttr)) { + phaseManager.unshiftNew("MoveHeaderPhase", pokemon, move); + } + if (pokemon.isPlayer()) { + if (turnCommand.cursor === -1) { + phaseManager.pushNew("MovePhase", pokemon, turnCommand.targets || turnCommand.move!.targets, move); + } else { + phaseManager.pushNew( + "MovePhase", + pokemon, + turnCommand.targets || turnCommand.move!.targets, // TODO: is the bang correct here? + move, + false, + queuedMove.ignorePP, + ); + } + } else { + phaseManager.pushNew( + "MovePhase", pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP, - ), - ); //TODO: is the bang correct here? + ); + } } break; case Command.BALL: - globalScene.phaseManager.unshiftPhase( - new AttemptCapturePhase(turnCommand.targets![0] % 2, turnCommand.cursor!), - ); //TODO: is the bang correct here? + phaseManager.unshiftNew("AttemptCapturePhase", turnCommand.targets![0] % 2, turnCommand.cursor!); //TODO: is the bang correct here? break; case Command.POKEMON: - const switchType = turnCommand.args?.[0] ? SwitchType.BATON_PASS : SwitchType.SWITCH; - globalScene.phaseManager.unshiftPhase( - new SwitchSummonPhase(switchType, pokemon.getFieldIndex(), turnCommand.cursor!, true, pokemon.isPlayer()), - ); + { + const switchType = turnCommand.args?.[0] ? SwitchType.BATON_PASS : SwitchType.SWITCH; + phaseManager.unshiftNew( + "SwitchSummonPhase", + switchType, + pokemon.getFieldIndex(), + turnCommand.cursor!, + true, + pokemon.isPlayer(), + ); + } break; case Command.RUN: - let runningPokemon = pokemon; - if (globalScene.currentBattle.double) { - const playerActivePokemon = field.filter(pokemon => { - if (pokemon) { - return pokemon.isPlayer() && pokemon.isActive(); + { + let runningPokemon = pokemon; + if (globalScene.currentBattle.double) { + const playerActivePokemon = field.filter(pokemon => { + if (pokemon) { + return pokemon.isPlayer() && pokemon.isActive(); + } + return; + }); + // if only one pokemon is alive, use that one + if (playerActivePokemon.length > 1) { + // find which active pokemon has faster speed + const fasterPokemon = + playerActivePokemon[0].getStat(Stat.SPD) > playerActivePokemon[1].getStat(Stat.SPD) + ? playerActivePokemon[0] + : playerActivePokemon[1]; + // check if either active pokemon has the ability "Run Away" + const hasRunAway = playerActivePokemon.find(p => p.hasAbility(AbilityId.RUN_AWAY)); + runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon; } - return; - }); - // if only one pokemon is alive, use that one - if (playerActivePokemon.length > 1) { - // find which active pokemon has faster speed - const fasterPokemon = - playerActivePokemon[0].getStat(Stat.SPD) > playerActivePokemon[1].getStat(Stat.SPD) - ? playerActivePokemon[0] - : playerActivePokemon[1]; - // check if either active pokemon has the ability "Run Away" - const hasRunAway = playerActivePokemon.find(p => p.hasAbility(AbilityId.RUN_AWAY)); - runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon; } + phaseManager.unshiftNew("AttemptRunPhase", runningPokemon.getFieldIndex()); } - globalScene.phaseManager.unshiftPhase(new AttemptRunPhase(runningPokemon.getFieldIndex())); break; } } - globalScene.phaseManager.pushPhase(new WeatherEffectPhase()); - globalScene.phaseManager.pushPhase(new BerryPhase()); + phaseManager.pushNew("WeatherEffectPhase"); + phaseManager.pushNew("BerryPhase"); /** Add a new phase to check who should be taking status damage */ - globalScene.phaseManager.pushPhase(new CheckStatusEffectPhase(moveOrder)); + phaseManager.pushNew("CheckStatusEffectPhase", moveOrder); - globalScene.phaseManager.pushPhase(new TurnEndPhase()); + phaseManager.pushNew("TurnEndPhase"); /** * this.end() will call shiftPhase(), which dumps everything from PrependQueue (aka everything that is unshifted()) to the front diff --git a/src/phases/unavailable-phase.ts b/src/phases/unavailable-phase.ts index a6fc4a1be61..8b5bb5f7508 100644 --- a/src/phases/unavailable-phase.ts +++ b/src/phases/unavailable-phase.ts @@ -1,13 +1,12 @@ import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; import { UiMode } from "#enums/ui-mode"; -import { LoginPhase } from "./login-phase"; export class UnavailablePhase extends Phase { public readonly phaseName = "UnavailablePhase"; start(): void { globalScene.ui.setMode(UiMode.UNAVAILABLE, () => { - globalScene.phaseManager.unshiftPhase(new LoginPhase(true)); + globalScene.phaseManager.unshiftNew("LoginPhase", true); this.end(); }); } diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts index 0a08e010720..ca24e474cde 100644 --- a/src/phases/victory-phase.ts +++ b/src/phases/victory-phase.ts @@ -3,19 +3,10 @@ import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; import { BattleType } from "#enums/battle-type"; import type { CustomModifierSettings } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type"; -import { BattleEndPhase } from "./battle-end-phase"; -import { NewBattlePhase } from "./new-battle-phase"; import { PokemonPhase } from "./pokemon-phase"; -import { AddEnemyBuffModifierPhase } from "./add-enemy-buff-modifier-phase"; -import { EggLapsePhase } from "./egg-lapse-phase"; -import { GameOverPhase } from "./game-over-phase"; -import { ModifierRewardPhase } from "./modifier-reward-phase"; -import { SelectModifierPhase } from "./select-modifier-phase"; -import { TrainerVictoryPhase } from "./trainer-victory-phase"; import { handleMysteryEncounterVictory } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { globalScene } from "#app/global-scene"; import { timedEventManager } from "#app/global-event-manager"; -import { SelectBiomePhase } from "./select-biome-phase"; export class VictoryPhase extends PokemonPhase { public readonly phaseName = "VictoryPhase"; @@ -51,12 +42,12 @@ export class VictoryPhase extends PokemonPhase { .getEnemyParty() .find(p => (globalScene.currentBattle.battleType === BattleType.WILD ? p.isOnField() : !p?.isFainted(true))) ) { - globalScene.phaseManager.pushPhase(new BattleEndPhase(true)); + globalScene.phaseManager.pushNew("BattleEndPhase", true); if (globalScene.currentBattle.battleType === BattleType.TRAINER) { - globalScene.phaseManager.pushPhase(new TrainerVictoryPhase()); + globalScene.phaseManager.pushNew("TrainerVictoryPhase"); } if (globalScene.gameMode.isEndless || !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) { - globalScene.phaseManager.pushPhase(new EggLapsePhase()); + globalScene.phaseManager.pushNew("EggLapsePhase"); if (globalScene.gameMode.isClassic) { switch (globalScene.currentBattle.waveIndex) { case ClassicFixedBossWaves.RIVAL_1: @@ -64,68 +55,67 @@ export class VictoryPhase extends PokemonPhase { // Get event modifiers for this wave timedEventManager .getFixedBattleEventRewards(globalScene.currentBattle.waveIndex) - .map(r => globalScene.phaseManager.pushPhase(new ModifierRewardPhase(modifierTypes[r]))); + .map(r => globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes[r])); break; case ClassicFixedBossWaves.EVIL_BOSS_2: // Should get Lock Capsule on 165 before shop phase so it can be used in the rewards shop - globalScene.phaseManager.pushPhase(new ModifierRewardPhase(modifierTypes.LOCK_CAPSULE)); + globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.LOCK_CAPSULE); break; } } if (globalScene.currentBattle.waveIndex % 10) { - globalScene.phaseManager.pushPhase( - new SelectModifierPhase(undefined, undefined, this.getFixedBattleCustomModifiers()), + globalScene.phaseManager.pushNew( + "SelectModifierPhase", + undefined, + undefined, + this.getFixedBattleCustomModifiers(), ); } else if (globalScene.gameMode.isDaily) { - globalScene.phaseManager.pushPhase(new ModifierRewardPhase(modifierTypes.EXP_CHARM)); + globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.EXP_CHARM); if ( globalScene.currentBattle.waveIndex > 10 && !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex) ) { - globalScene.phaseManager.pushPhase(new ModifierRewardPhase(modifierTypes.GOLDEN_POKEBALL)); + globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.GOLDEN_POKEBALL); } } else { const superExpWave = !globalScene.gameMode.isEndless ? (globalScene.offsetGym ? 0 : 20) : 10; if (globalScene.gameMode.isEndless && globalScene.currentBattle.waveIndex === 10) { - globalScene.phaseManager.pushPhase(new ModifierRewardPhase(modifierTypes.EXP_SHARE)); + globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.EXP_SHARE); } if ( globalScene.currentBattle.waveIndex <= 750 && (globalScene.currentBattle.waveIndex <= 500 || globalScene.currentBattle.waveIndex % 30 === superExpWave) ) { - globalScene.phaseManager.pushPhase( - new ModifierRewardPhase( - globalScene.currentBattle.waveIndex % 30 !== superExpWave || globalScene.currentBattle.waveIndex > 250 - ? modifierTypes.EXP_CHARM - : modifierTypes.SUPER_EXP_CHARM, - ), + globalScene.phaseManager.pushNew( + "ModifierRewardPhase", + globalScene.currentBattle.waveIndex % 30 !== superExpWave || globalScene.currentBattle.waveIndex > 250 + ? modifierTypes.EXP_CHARM + : modifierTypes.SUPER_EXP_CHARM, ); } if (globalScene.currentBattle.waveIndex <= 150 && !(globalScene.currentBattle.waveIndex % 50)) { - globalScene.phaseManager.pushPhase(new ModifierRewardPhase(modifierTypes.GOLDEN_POKEBALL)); + globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.GOLDEN_POKEBALL); } if (globalScene.gameMode.isEndless && !(globalScene.currentBattle.waveIndex % 50)) { - globalScene.phaseManager.pushPhase( - new ModifierRewardPhase( - !(globalScene.currentBattle.waveIndex % 250) - ? modifierTypes.VOUCHER_PREMIUM - : modifierTypes.VOUCHER_PLUS, - ), + globalScene.phaseManager.pushNew( + "ModifierRewardPhase", + !(globalScene.currentBattle.waveIndex % 250) ? modifierTypes.VOUCHER_PREMIUM : modifierTypes.VOUCHER_PLUS, ); - globalScene.phaseManager.pushPhase(new AddEnemyBuffModifierPhase()); + globalScene.phaseManager.pushNew("AddEnemyBuffModifierPhase"); } } if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) { - globalScene.phaseManager.pushPhase(new SelectBiomePhase()); + globalScene.phaseManager.pushNew("SelectBiomePhase"); } - globalScene.phaseManager.pushPhase(new NewBattlePhase()); + globalScene.phaseManager.pushNew("NewBattlePhase"); } else { globalScene.currentBattle.battleType = BattleType.CLEAR; globalScene.score += globalScene.gameMode.getClearScoreBonus(); globalScene.updateScoreText(); - globalScene.phaseManager.pushPhase(new GameOverPhase(true)); + globalScene.phaseManager.pushNew("GameOverPhase", true); } } diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 9a24194b8e9..2949ecd51cf 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -49,7 +49,6 @@ import { SpeciesId } from "#enums/species-id"; import { applyChallenges, ChallengeType } from "#app/data/challenge"; import { WeatherType } from "#enums/weather-type"; import { TerrainType } from "#app/data/terrain"; -import { ReloadSessionPhase } from "#app/phases/reload-session-phase"; import { RUN_HISTORY_LIMIT } from "#app/ui/run-history-ui-handler"; import { applySessionVersionMigration, @@ -427,7 +426,7 @@ export class GameData { if (error) { if (error.startsWith("session out of date")) { globalScene.phaseManager.clearPhaseQueue(); - globalScene.phaseManager.unshiftPhase(new ReloadSessionPhase()); + globalScene.phaseManager.unshiftNew("ReloadSessionPhase"); } console.error(error); return resolve(false); @@ -747,7 +746,7 @@ export class GameData { if (systemData) { globalScene.phaseManager.clearPhaseQueue(); - globalScene.phaseManager.unshiftPhase(new ReloadSessionPhase(JSON.stringify(systemData))); + globalScene.phaseManager.unshiftNew("ReloadSessionPhase", JSON.stringify(systemData)); this.clearLocalData(); return false; } @@ -1249,7 +1248,7 @@ export class GameData { if (error) { if (error.startsWith("session out of date")) { globalScene.phaseManager.clearPhaseQueue(); - globalScene.phaseManager.unshiftPhase(new ReloadSessionPhase()); + globalScene.phaseManager.unshiftNew("ReloadSessionPhase"); } console.error(error); resolve(false); @@ -1321,7 +1320,7 @@ export class GameData { } else { if (jsonResponse?.error?.startsWith("session out of date")) { globalScene.phaseManager.clearPhaseQueue(); - globalScene.phaseManager.unshiftPhase(new ReloadSessionPhase()); + globalScene.phaseManager.unshiftNew("ReloadSessionPhase"); } console.error(jsonResponse); @@ -1459,7 +1458,7 @@ export class GameData { if (error) { if (error.startsWith("session out of date")) { globalScene.phaseManager.clearPhaseQueue(); - globalScene.phaseManager.unshiftPhase(new ReloadSessionPhase()); + globalScene.phaseManager.unshiftNew("ReloadSessionPhase"); } console.error(error); return resolve(false); diff --git a/src/ui/challenges-select-ui-handler.ts b/src/ui/challenges-select-ui-handler.ts index d2c2dbc6c0d..b02bf4abaef 100644 --- a/src/ui/challenges-select-ui-handler.ts +++ b/src/ui/challenges-select-ui-handler.ts @@ -9,8 +9,6 @@ import { getLocalizedSpriteKey } from "#app/utils/common"; import { Challenges } from "#app/enums/challenges"; import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { Color, ShadowColor } from "#app/enums/color"; -import { SelectStarterPhase } from "#app/phases/select-starter-phase"; -import { TitlePhase } from "#app/phases/title-phase"; import { globalScene } from "#app/global-scene"; /** @@ -384,14 +382,14 @@ export default class GameChallengesUiHandler extends UiHandler { this.updateChallengeArrows(this.startCursor.visible); } else { globalScene.phaseManager.clearPhaseQueue(); - globalScene.phaseManager.pushPhase(new TitlePhase()); + globalScene.phaseManager.pushNew("TitlePhase"); globalScene.phaseManager.getCurrentPhase()?.end(); } success = true; } else if (button === Button.SUBMIT || button === Button.ACTION) { if (this.hasSelectedChallenge) { if (this.startCursor.visible) { - globalScene.phaseManager.unshiftPhase(new SelectStarterPhase()); + globalScene.phaseManager.unshiftNew("SelectStarterPhase"); globalScene.phaseManager.getCurrentPhase()?.end(); } else { this.startCursor.setVisible(true); diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 973805e4ca1..47226de3354 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -51,9 +51,6 @@ import { StarterContainer } from "#app/ui/starter-container"; import { FilterBar } from "#app/ui/filter-bar"; import { DropDownColumn } from "#enums/drop-down-column"; import { ScrollBar } from "#app/ui/scroll-bar"; -import { SelectChallengePhase } from "#app/phases/select-challenge-phase"; -import { EncounterPhase } from "#app/phases/encounter-phase"; -import { TitlePhase } from "#app/phases/title-phase"; import { AbilityId } from "#enums/ability-id"; import { getPassiveCandyCount, @@ -4307,10 +4304,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler { ui.setMode(UiMode.STARTER_SELECT); globalScene.phaseManager.clearPhaseQueue(); if (globalScene.gameMode.isChallenge) { - globalScene.phaseManager.pushPhase(new SelectChallengePhase()); - globalScene.phaseManager.pushPhase(new EncounterPhase()); + globalScene.phaseManager.pushNew("SelectChallengePhase"); + globalScene.phaseManager.pushNew("EncounterPhase"); } else { - globalScene.phaseManager.pushPhase(new TitlePhase()); + globalScene.phaseManager.pushNew("TitlePhase"); } this.clearText(); globalScene.phaseManager.getCurrentPhase()?.end(); diff --git a/test/testUtils/gameManager.ts b/test/testUtils/gameManager.ts index edf0301447d..437c8d9f083 100644 --- a/test/testUtils/gameManager.ts +++ b/test/testUtils/gameManager.ts @@ -105,8 +105,8 @@ export default class GameManager { // Must be run after phase interceptor has been initialized. - this.scene.phaseManager.pushPhase(new LoginPhase()); - this.scene.phaseManager.pushPhase(new TitlePhase()); + this.scene.phaseManager.pushNew("LoginPhase"); + this.scene.phaseManager.pushNew("TitlePhase"); this.scene.phaseManager.shiftPhase(); this.gameWrapper.scene = this.scene; From 37767799cdd608608d77bd8ae33c5fcd11d6fdff Mon Sep 17 00:00:00 2001 From: Tiago Rodrigues Date: Sun, 8 Jun 2025 17:52:48 +0100 Subject: [PATCH 23/51] [Bug] Gorilla Tactics now activates on protect and miss (#5567) * [Bug] Fix #5112: Gorilla Tactics only registers succesful moves as move usage * Apply small fixes from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/abilities/ability.ts | 82 ++++++++++++++------------ src/phases/move-effect-phase.ts | 3 + test/abilities/gorilla_tactics.test.ts | 29 +++++++++ 3 files changed, 76 insertions(+), 38 deletions(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index a79e2206348..34ae8be78b6 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -2534,48 +2534,38 @@ export class AllyStatMultiplierAbAttr extends AbAttr { } /** - * Ability attribute for Gorilla Tactics - * @extends PostAttackAbAttr + * Takes effect whenever a move succesfully executes, such as gorilla tactics' move-locking. + * (More specifically, whenever a move is pushed to the move history) + * @extends AbAttr */ -export class GorillaTacticsAbAttr extends PostAttackAbAttr { - constructor() { - super((_user, _target, _move) => true, false); - } - - override canApplyPostAttack( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - defender: Pokemon, - move: Move, - hitResult: HitResult | null, - args: any[], +export class ExecutedMoveAbAttr extends AbAttr { + canApplyExecutedMove( + _pokemon: Pokemon, + _simulated: boolean, ): boolean { - return ( - (super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args) && simulated) || - !pokemon.getTag(BattlerTagType.GORILLA_TACTICS) - ); + return true; } - /** - * - * @param {Pokemon} pokemon the {@linkcode Pokemon} with this ability - * @param _passive n/a - * @param simulated whether the ability is being simulated - * @param _defender n/a - * @param _move n/a - * @param _hitResult n/a - * @param _args n/a - */ - override applyPostAttack( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _defender: Pokemon, - _move: Move, - _hitResult: HitResult | null, - _args: any[], - ): void { + applyExecutedMove( + _pokemon: Pokemon, + _simulated: boolean, + ): void {} +} + +/** + * Ability attribute for Gorilla Tactics + * @extends ExecutedMoveAbAttr + */ +export class GorillaTacticsAbAttr extends ExecutedMoveAbAttr { + constructor(showAbility: boolean = false) { + super(showAbility); + } + + override canApplyExecutedMove(pokemon: Pokemon, simulated: boolean): boolean { + return simulated || !pokemon.getTag(BattlerTagType.GORILLA_TACTICS); + } + + override applyExecutedMove(pokemon: Pokemon, simulated: boolean): void { if (!simulated) { pokemon.addTag(BattlerTagType.GORILLA_TACTICS); } @@ -7792,6 +7782,22 @@ export function applyPreAttackAbAttrs( ); } +export function applyExecutedMoveAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + simulated: boolean = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + attr => attr.applyExecutedMove(pokemon, simulated), + attr => attr.canApplyExecutedMove(pokemon, simulated), + args, + simulated, + ); +} + export function applyPostAttackAbAttrs( attrType: Constructor, pokemon: Pokemon, diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index e0fa381447b..84072b393f1 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -3,10 +3,12 @@ import { globalScene } from "#app/global-scene"; import { AddSecondStrikeAbAttr, AlwaysHitAbAttr, + applyExecutedMoveAbAttrs, applyPostAttackAbAttrs, applyPostDamageAbAttrs, applyPostDefendAbAttrs, applyPreAttackAbAttrs, + ExecutedMoveAbAttr, IgnoreMoveEffectsAbAttr, MaxMultiHitAbAttr, PostAttackAbAttr, @@ -380,6 +382,7 @@ export class MoveEffectPhase extends PokemonPhase { // Add to the move history entry if (this.firstHit) { user.pushMoveHistory(this.moveHistoryEntry); + applyExecutedMoveAbAttrs(ExecutedMoveAbAttr, user); } try { diff --git a/test/abilities/gorilla_tactics.test.ts b/test/abilities/gorilla_tactics.test.ts index 55b8a4addcd..3ad138749a8 100644 --- a/test/abilities/gorilla_tactics.test.ts +++ b/test/abilities/gorilla_tactics.test.ts @@ -73,9 +73,38 @@ describe("Abilities - Gorilla Tactics", () => { await game.toNextTurn(); game.move.select(MoveId.TACKLE); + await game.move.forceEnemyMove(MoveId.SPLASH); //prevent protect from being used by the enemy await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.phaseInterceptor.to("MoveEndPhase"); expect(darmanitan.hp).toBeLessThan(darmanitan.getMaxHp()); }); + + it("should activate when the opponenet protects", async () => { + await game.classicMode.startBattle([SpeciesId.GALAR_DARMANITAN]); + + const darmanitan = game.field.getPlayerPokemon(); + + game.move.select(MoveId.TACKLE); + await game.move.selectEnemyMove(MoveId.PROTECT); + + await game.toEndOfTurn(); + expect(darmanitan.isMoveRestricted(MoveId.SPLASH)).toBe(true); + expect(darmanitan.isMoveRestricted(MoveId.TACKLE)).toBe(false); + }); + + it("should activate when a move is succesfully executed but misses", async () => { + await game.classicMode.startBattle([SpeciesId.GALAR_DARMANITAN]); + + const darmanitan = game.field.getPlayerPokemon(); + + game.move.select(MoveId.TACKLE); + await game.move.selectEnemyMove(MoveId.SPLASH); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.move.forceMiss(); + await game.toEndOfTurn(); + + expect(darmanitan.isMoveRestricted(MoveId.SPLASH)).toBe(true); + expect(darmanitan.isMoveRestricted(MoveId.TACKLE)).toBe(false); + }); }); From 5ef88a6d4de250679c9117668d312762e7957eb8 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 9 Jun 2025 00:13:26 +0200 Subject: [PATCH 24/51] [Refactor] Refactor select-modifier-phase.ts (#5886) * Refactored select-modifier-phase.ts * Added some missing type signatures * Changes from suggestions * Added ModifierSelectCallback type --- src/phases/select-modifier-phase.ts | 548 +++++++++++++++------------- 1 file changed, 295 insertions(+), 253 deletions(-) diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 4d790e70e1b..f99c921412f 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -31,6 +31,8 @@ import Overrides from "#app/overrides"; import type { CustomModifierSettings } from "#app/modifier/modifier-type"; import { isNullOrUndefined, NumberHolder } from "#app/utils/common"; +export type ModifierSelectCallback = (rowCursor: number, cursor: number) => boolean; + export class SelectModifierPhase extends BattlePhase { public readonly phaseName = "SelectModifierPhase"; private rerollCount: number; @@ -57,6 +59,10 @@ export class SelectModifierPhase extends BattlePhase { start() { super.start(); + if (!this.isPlayer()) { + return false; + } + if (!this.rerollCount && !this.isCopy) { this.updateSeed(); } else if (this.rerollCount) { @@ -67,27 +73,9 @@ export class SelectModifierPhase extends BattlePhase { if (!this.isCopy) { regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount); } - const modifierCount = new NumberHolder(3); - if (this.isPlayer()) { - globalScene.applyModifiers(ExtraModifierModifier, true, modifierCount); - globalScene.applyModifiers(TempExtraModifierModifier, true, modifierCount); - } + const modifierCount = this.getModifierCount(); - // If custom modifiers are specified, overrides default item count - if (this.customModifierSettings) { - const newItemCount = - (this.customModifierSettings.guaranteedModifierTiers?.length || 0) + - (this.customModifierSettings.guaranteedModifierTypeOptions?.length || 0) + - (this.customModifierSettings.guaranteedModifierTypeFuncs?.length || 0); - if (this.customModifierSettings.fillRemaining) { - const originalCount = modifierCount.value; - modifierCount.value = originalCount > newItemCount ? originalCount : newItemCount; - } else { - modifierCount.value = newItemCount; - } - } - - this.typeOptions = this.getModifierTypeOptions(modifierCount.value); + this.typeOptions = this.getModifierTypeOptions(modifierCount); const modifierSelectCallback = (rowCursor: number, cursor: number) => { if (rowCursor < 0 || cursor < 0) { @@ -99,258 +87,312 @@ export class SelectModifierPhase extends BattlePhase { globalScene.ui.setMode(UiMode.MESSAGE); super.end(); }, - () => - globalScene.ui.setMode( - UiMode.MODIFIER_SELECT, - this.isPlayer(), - this.typeOptions, - modifierSelectCallback, - this.getRerollCost(globalScene.lockModifierTiers), - ), + () => this.resetModifierSelect(modifierSelectCallback), ); }); return false; } - let modifierType: ModifierType; - let cost: number; - const rerollCost = this.getRerollCost(globalScene.lockModifierTiers); + switch (rowCursor) { + // Execute one of the options from the bottom row case 0: switch (cursor) { case 0: - if (rerollCost < 0 || globalScene.money < rerollCost) { - globalScene.ui.playError(); - return false; - } - globalScene.reroll = true; - globalScene.phaseManager.unshiftNew( - "SelectModifierPhase", - this.rerollCount + 1, - this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[], - ); - globalScene.ui.clearText(); - globalScene.ui.setMode(UiMode.MESSAGE).then(() => super.end()); - if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - globalScene.money -= rerollCost; - globalScene.updateMoneyText(); - globalScene.animateMoneyChanged(false); - } - globalScene.playSound("se/buy"); - break; + return this.rerollModifiers(); case 1: - globalScene.ui.setModeWithoutClear( - UiMode.PARTY, - PartyUiMode.MODIFIER_TRANSFER, - -1, - (fromSlotIndex: number, itemIndex: number, itemQuantity: number, toSlotIndex: number) => { - if ( - toSlotIndex !== undefined && - fromSlotIndex < 6 && - toSlotIndex < 6 && - fromSlotIndex !== toSlotIndex && - itemIndex > -1 - ) { - const itemModifiers = globalScene.findModifiers( - m => - m instanceof PokemonHeldItemModifier && - m.isTransferable && - m.pokemonId === party[fromSlotIndex].id, - ) as PokemonHeldItemModifier[]; - const itemModifier = itemModifiers[itemIndex]; - globalScene.tryTransferHeldItemModifier( - itemModifier, - party[toSlotIndex], - true, - itemQuantity, - undefined, - undefined, - false, - ); - } else { - globalScene.ui.setMode( - UiMode.MODIFIER_SELECT, - this.isPlayer(), - this.typeOptions, - modifierSelectCallback, - this.getRerollCost(globalScene.lockModifierTiers), - ); - } - }, - PartyUiHandler.FilterItemMaxStacks, - ); - break; + return this.openModifierTransferScreen(modifierSelectCallback); + // Check the party, pass a callback to restore the modifier select screen. case 2: globalScene.ui.setModeWithoutClear(UiMode.PARTY, PartyUiMode.CHECK, -1, () => { - globalScene.ui.setMode( - UiMode.MODIFIER_SELECT, - this.isPlayer(), - this.typeOptions, - modifierSelectCallback, - this.getRerollCost(globalScene.lockModifierTiers), - ); + this.resetModifierSelect(modifierSelectCallback); }); - break; + return true; case 3: - if (rerollCost < 0) { - // Reroll lock button is also disabled when reroll is disabled - globalScene.ui.playError(); - return false; - } - globalScene.lockModifierTiers = !globalScene.lockModifierTiers; - const uiHandler = globalScene.ui.getHandler() as ModifierSelectUiHandler; - uiHandler.setRerollCost(this.getRerollCost(globalScene.lockModifierTiers)); - uiHandler.updateLockRaritiesText(); - uiHandler.updateRerollCostText(); + return this.toggleRerollLock(); + default: return false; } - return true; + // Pick an option from the rewards case 1: - if (this.typeOptions.length === 0) { - globalScene.ui.clearText(); - globalScene.ui.setMode(UiMode.MESSAGE); - super.end(); - return true; - } - if (this.typeOptions[cursor].type) { - modifierType = this.typeOptions[cursor].type; - } - break; - default: - const shopOptions = getPlayerShopModifierTypeOptionsForWave( - globalScene.currentBattle.waveIndex, - globalScene.getWaveMoneyAmount(1), - ); - const shopOption = - shopOptions[ - rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT - ]; - if (shopOption.type) { - modifierType = shopOption.type; - } - // Apply Black Sludge to healing item cost - const healingItemCost = new NumberHolder(shopOption.cost); - globalScene.applyModifier(HealShopCostModifier, true, healingItemCost); - cost = healingItemCost.value; - break; - } - - if (cost! && globalScene.money < cost && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - // TODO: is the bang on cost correct? - globalScene.ui.playError(); - return false; - } - - const applyModifier = (modifier: Modifier, playSound = false) => { - const result = globalScene.addModifier(modifier, false, playSound, undefined, undefined, cost); - // Queue a copy of this phase when applying a TM or Memory Mushroom. - // If the player selects either of these, then escapes out of consuming them, - // they are returned to a shop in the same state. - if (modifier.type instanceof RememberMoveModifierType || modifier.type instanceof TmModifierType) { - globalScene.phaseManager.unshiftPhase(this.copy()); + return this.selectRewardModifierOption(cursor, modifierSelectCallback); + // Pick an option from the shop + default: { + return this.selectShopModifierOption(rowCursor, cursor, modifierSelectCallback); } - - if (cost && !(modifier.type instanceof RememberMoveModifierType)) { - if (result) { - if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - globalScene.money -= cost; - globalScene.updateMoneyText(); - globalScene.animateMoneyChanged(false); - } - globalScene.playSound("se/buy"); - (globalScene.ui.getHandler() as ModifierSelectUiHandler).updateCostText(); - } else { - globalScene.ui.playError(); - } - } else { - globalScene.ui.clearText(); - globalScene.ui.setMode(UiMode.MESSAGE); - super.end(); - } - }; - - if (modifierType! instanceof PokemonModifierType) { - //TODO: is the bang correct? - if (modifierType instanceof FusePokemonModifierType) { - globalScene.ui.setModeWithoutClear( - UiMode.PARTY, - PartyUiMode.SPLICE, - -1, - (fromSlotIndex: number, spliceSlotIndex: number) => { - if ( - spliceSlotIndex !== undefined && - fromSlotIndex < 6 && - spliceSlotIndex < 6 && - fromSlotIndex !== spliceSlotIndex - ) { - globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => { - const modifier = modifierType.newModifier(party[fromSlotIndex], party[spliceSlotIndex])!; //TODO: is the bang correct? - applyModifier(modifier, true); - }); - } else { - globalScene.ui.setMode( - UiMode.MODIFIER_SELECT, - this.isPlayer(), - this.typeOptions, - modifierSelectCallback, - this.getRerollCost(globalScene.lockModifierTiers), - ); - } - }, - modifierType.selectFilter, - ); - } else { - const pokemonModifierType = modifierType as PokemonModifierType; - const isMoveModifier = modifierType instanceof PokemonMoveModifierType; - const isTmModifier = modifierType instanceof TmModifierType; - const isRememberMoveModifier = modifierType instanceof RememberMoveModifierType; - const isPpRestoreModifier = - modifierType instanceof PokemonPpRestoreModifierType || modifierType instanceof PokemonPpUpModifierType; - const partyUiMode = isMoveModifier - ? PartyUiMode.MOVE_MODIFIER - : isTmModifier - ? PartyUiMode.TM_MODIFIER - : isRememberMoveModifier - ? PartyUiMode.REMEMBER_MOVE_MODIFIER - : PartyUiMode.MODIFIER; - const tmMoveId = isTmModifier ? (modifierType as TmModifierType).moveId : undefined; - globalScene.ui.setModeWithoutClear( - UiMode.PARTY, - partyUiMode, - -1, - (slotIndex: number, option: PartyOption) => { - if (slotIndex < 6) { - globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => { - const modifier = !isMoveModifier - ? !isRememberMoveModifier - ? modifierType.newModifier(party[slotIndex]) - : modifierType.newModifier(party[slotIndex], option as number) - : modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1); - applyModifier(modifier!, true); // TODO: is the bang correct? - }); - } else { - globalScene.ui.setMode( - UiMode.MODIFIER_SELECT, - this.isPlayer(), - this.typeOptions, - modifierSelectCallback, - this.getRerollCost(globalScene.lockModifierTiers), - ); - } - }, - pokemonModifierType.selectFilter, - modifierType instanceof PokemonMoveModifierType - ? (modifierType as PokemonMoveModifierType).moveSelectFilter - : undefined, - tmMoveId, - isPpRestoreModifier, - ); - } - } else { - applyModifier(modifierType!.newModifier()!); // TODO: is the bang correct? } - - return !cost!; // TODO: is the bang correct? }; + + this.resetModifierSelect(modifierSelectCallback); + } + + // Pick a modifier from among the rewards and apply it + private selectRewardModifierOption(cursor: number, modifierSelectCallback: ModifierSelectCallback): boolean { + if (this.typeOptions.length === 0) { + globalScene.ui.clearText(); + globalScene.ui.setMode(UiMode.MESSAGE); + super.end(); + return true; + } + const modifierType = this.typeOptions[cursor].type; + return this.applyChosenModifier(modifierType, 0, modifierSelectCallback); + } + + // Pick a modifier from the shop and apply it + private selectShopModifierOption( + rowCursor: number, + cursor: number, + modifierSelectCallback: ModifierSelectCallback, + ): boolean { + const shopOptions = getPlayerShopModifierTypeOptionsForWave( + globalScene.currentBattle.waveIndex, + globalScene.getWaveMoneyAmount(1), + ); + const shopOption = + shopOptions[ + rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT + ]; + const modifierType = shopOption.type; + // Apply Black Sludge to healing item cost + const healingItemCost = new NumberHolder(shopOption.cost); + globalScene.applyModifier(HealShopCostModifier, true, healingItemCost); + const cost = healingItemCost.value; + + if (globalScene.money < cost && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) { + globalScene.ui.playError(); + return false; + } + + return this.applyChosenModifier(modifierType, cost, modifierSelectCallback); + } + + // Apply a chosen modifier: do an effect or open the party menu + private applyChosenModifier( + modifierType: ModifierType, + cost: number, + modifierSelectCallback: ModifierSelectCallback, + ): boolean { + if (modifierType instanceof PokemonModifierType) { + if (modifierType instanceof FusePokemonModifierType) { + this.openFusionMenu(modifierType, cost, modifierSelectCallback); + } else { + this.openModifierMenu(modifierType, cost, modifierSelectCallback); + } + } else { + this.applyModifier(modifierType.newModifier()!); + } + return !cost; + } + + // Reroll rewards + private rerollModifiers() { + const rerollCost = this.getRerollCost(globalScene.lockModifierTiers); + if (rerollCost < 0 || globalScene.money < rerollCost) { + globalScene.ui.playError(); + return false; + } + globalScene.reroll = true; + globalScene.phaseManager.unshiftNew( + "SelectModifierPhase", + this.rerollCount + 1, + this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[], + ); + globalScene.ui.clearText(); + globalScene.ui.setMode(UiMode.MESSAGE).then(() => super.end()); + if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { + globalScene.money -= rerollCost; + globalScene.updateMoneyText(); + globalScene.animateMoneyChanged(false); + } + globalScene.playSound("se/buy"); + return true; + } + + // Transfer modifiers among party pokemon + private openModifierTransferScreen(modifierSelectCallback: ModifierSelectCallback) { + const party = globalScene.getPlayerParty(); + globalScene.ui.setModeWithoutClear( + UiMode.PARTY, + PartyUiMode.MODIFIER_TRANSFER, + -1, + (fromSlotIndex: number, itemIndex: number, itemQuantity: number, toSlotIndex: number) => { + if ( + toSlotIndex !== undefined && + fromSlotIndex < 6 && + toSlotIndex < 6 && + fromSlotIndex !== toSlotIndex && + itemIndex > -1 + ) { + const itemModifiers = globalScene.findModifiers( + m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === party[fromSlotIndex].id, + ) as PokemonHeldItemModifier[]; + const itemModifier = itemModifiers[itemIndex]; + globalScene.tryTransferHeldItemModifier( + itemModifier, + party[toSlotIndex], + true, + itemQuantity, + undefined, + undefined, + false, + ); + } else { + this.resetModifierSelect(modifierSelectCallback); + } + }, + PartyUiHandler.FilterItemMaxStacks, + ); + return true; + } + + // Toggle reroll lock + private toggleRerollLock() { + const rerollCost = this.getRerollCost(globalScene.lockModifierTiers); + if (rerollCost < 0) { + // Reroll lock button is also disabled when reroll is disabled + globalScene.ui.playError(); + return false; + } + globalScene.lockModifierTiers = !globalScene.lockModifierTiers; + const uiHandler = globalScene.ui.getHandler() as ModifierSelectUiHandler; + uiHandler.setRerollCost(this.getRerollCost(globalScene.lockModifierTiers)); + uiHandler.updateLockRaritiesText(); + uiHandler.updateRerollCostText(); + return false; + } + + // Applies the effects of the chosen modifier + private applyModifier(modifier: Modifier, cost = 0, playSound = false): void { + const result = globalScene.addModifier(modifier, false, playSound, undefined, undefined, cost); + // Queue a copy of this phase when applying a TM or Memory Mushroom. + // If the player selects either of these, then escapes out of consuming them, + // they are returned to a shop in the same state. + if (modifier.type instanceof RememberMoveModifierType || modifier.type instanceof TmModifierType) { + globalScene.phaseManager.unshiftPhase(this.copy()); + } + + if (cost && !(modifier.type instanceof RememberMoveModifierType)) { + if (result) { + if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { + globalScene.money -= cost; + globalScene.updateMoneyText(); + globalScene.animateMoneyChanged(false); + } + globalScene.playSound("se/buy"); + (globalScene.ui.getHandler() as ModifierSelectUiHandler).updateCostText(); + } else { + globalScene.ui.playError(); + } + } else { + globalScene.ui.clearText(); + globalScene.ui.setMode(UiMode.MESSAGE); + super.end(); + } + } + + // Opens the party menu specifically for fusions + private openFusionMenu( + modifierType: PokemonModifierType, + cost: number, + modifierSelectCallback: ModifierSelectCallback, + ): void { + const party = globalScene.getPlayerParty(); + globalScene.ui.setModeWithoutClear( + UiMode.PARTY, + PartyUiMode.SPLICE, + -1, + (fromSlotIndex: number, spliceSlotIndex: number) => { + if ( + spliceSlotIndex !== undefined && + fromSlotIndex < 6 && + spliceSlotIndex < 6 && + fromSlotIndex !== spliceSlotIndex + ) { + globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => { + const modifier = modifierType.newModifier(party[fromSlotIndex], party[spliceSlotIndex])!; //TODO: is the bang correct? + this.applyModifier(modifier, cost, true); + }); + } else { + this.resetModifierSelect(modifierSelectCallback); + } + }, + modifierType.selectFilter, + ); + } + + // Opens the party menu to apply one of various modifiers + private openModifierMenu( + modifierType: PokemonModifierType, + cost: number, + modifierSelectCallback: ModifierSelectCallback, + ): void { + const party = globalScene.getPlayerParty(); + const pokemonModifierType = modifierType as PokemonModifierType; + const isMoveModifier = modifierType instanceof PokemonMoveModifierType; + const isTmModifier = modifierType instanceof TmModifierType; + const isRememberMoveModifier = modifierType instanceof RememberMoveModifierType; + const isPpRestoreModifier = + modifierType instanceof PokemonPpRestoreModifierType || modifierType instanceof PokemonPpUpModifierType; + const partyUiMode = isMoveModifier + ? PartyUiMode.MOVE_MODIFIER + : isTmModifier + ? PartyUiMode.TM_MODIFIER + : isRememberMoveModifier + ? PartyUiMode.REMEMBER_MOVE_MODIFIER + : PartyUiMode.MODIFIER; + const tmMoveId = isTmModifier ? (modifierType as TmModifierType).moveId : undefined; + globalScene.ui.setModeWithoutClear( + UiMode.PARTY, + partyUiMode, + -1, + (slotIndex: number, option: PartyOption) => { + if (slotIndex < 6) { + globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => { + const modifier = !isMoveModifier + ? !isRememberMoveModifier + ? modifierType.newModifier(party[slotIndex]) + : modifierType.newModifier(party[slotIndex], option as number) + : modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1); + this.applyModifier(modifier!, cost, true); // TODO: is the bang correct? + }); + } else { + this.resetModifierSelect(modifierSelectCallback); + } + }, + pokemonModifierType.selectFilter, + modifierType instanceof PokemonMoveModifierType + ? (modifierType as PokemonMoveModifierType).moveSelectFilter + : undefined, + tmMoveId, + isPpRestoreModifier, + ); + } + + // Function that determines how many reward slots are available + private getModifierCount(): number { + const modifierCountHolder = new NumberHolder(3); + globalScene.applyModifiers(ExtraModifierModifier, true, modifierCountHolder); + globalScene.applyModifiers(TempExtraModifierModifier, true, modifierCountHolder); + + // If custom modifiers are specified, overrides default item count + if (this.customModifierSettings) { + const newItemCount = + (this.customModifierSettings.guaranteedModifierTiers?.length ?? 0) + + (this.customModifierSettings.guaranteedModifierTypeOptions?.length ?? 0) + + (this.customModifierSettings.guaranteedModifierTypeFuncs?.length ?? 0); + if (this.customModifierSettings.fillRemaining) { + const originalCount = modifierCountHolder.value; + modifierCountHolder.value = originalCount > newItemCount ? originalCount : newItemCount; + } else { + modifierCountHolder.value = newItemCount; + } + } + + return modifierCountHolder.value; + } + + // Function that resets the reward selection screen, + // e.g. after pressing cancel in the party ui or while learning a move + private resetModifierSelect(modifierSelectCallback: ModifierSelectCallback) { globalScene.ui.setMode( UiMode.MODIFIER_SELECT, this.isPlayer(), From d2ace47e1e6ece668c4827d73244bef7f053625e Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 8 Jun 2025 15:36:01 -0700 Subject: [PATCH 25/51] [Test] Add missing ability override to Dancer tests --- test/abilities/dancer.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/abilities/dancer.test.ts b/test/abilities/dancer.test.ts index ae702e4b1e7..518c96ea124 100644 --- a/test/abilities/dancer.test.ts +++ b/test/abilities/dancer.test.ts @@ -25,7 +25,7 @@ describe("Abilities - Dancer", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("double"); + game.override.battleStyle("double").enemyAbility(AbilityId.BALL_FETCH); }); // Reference Link: https://bulbapedia.bulbagarden.net/wiki/Dancer_(Ability) From b1c50dd69fa78f3b0e63ade7aeadb79828f5b2d7 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Sun, 8 Jun 2025 19:49:50 -0400 Subject: [PATCH 26/51] [Bug] Fix TM compatibility for Terapagos and Knock Off (#5958) Fix TM compatibility for Terapagos and Knock Off --- src/data/balance/tms.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/data/balance/tms.ts b/src/data/balance/tms.ts index e95fa12151d..2b0e8f5142d 100644 --- a/src/data/balance/tms.ts +++ b/src/data/balance/tms.ts @@ -5988,6 +5988,7 @@ export const tmSpecies: TmSpecies = { SpeciesId.FEZANDIPITI, SpeciesId.ARCHALUDON, SpeciesId.IRON_CROWN, + SpeciesId.TERAPAGOS, SpeciesId.ALOLA_RATICATE, SpeciesId.ALOLA_RAICHU, SpeciesId.ALOLA_SANDSLASH, @@ -16248,6 +16249,7 @@ export const tmSpecies: TmSpecies = { SpeciesId.CALYREX, SpeciesId.SANDY_SHOCKS, SpeciesId.IRON_JUGULIS, + SpeciesId.TERAPAGOS, SpeciesId.ALOLA_DUGTRIO, SpeciesId.GALAR_SLOWPOKE, SpeciesId.GALAR_SLOWBRO, @@ -39466,6 +39468,8 @@ export const tmSpecies: TmSpecies = { SpeciesId.FARFETCHD, SpeciesId.DODUO, SpeciesId.DODRIO, + SpeciesId.DEWGONG, + SpeciesId.GRIMER, SpeciesId.MUK, SpeciesId.GASTLY, SpeciesId.HAUNTER, @@ -39477,6 +39481,7 @@ export const tmSpecies: TmSpecies = { SpeciesId.CUBONE, SpeciesId.MAROWAK, SpeciesId.HITMONLEE, + SpeciesId.HITMONCHAN, SpeciesId.LICKITUNG, SpeciesId.TANGELA, SpeciesId.GOLDEEN, @@ -48806,6 +48811,7 @@ export const tmSpecies: TmSpecies = { SpeciesId.GARGANACL, SpeciesId.GLIMMET, SpeciesId.GLIMMORA, + SpeciesId.TERAPAGOS, SpeciesId.ALOLA_GEODUDE, SpeciesId.ALOLA_GRAVELER, SpeciesId.ALOLA_GOLEM, @@ -53077,6 +53083,7 @@ export const tmSpecies: TmSpecies = { SpeciesId.MIRAIDON, SpeciesId.ARCHALUDON, SpeciesId.IRON_CROWN, + SpeciesId.TERAPAGOS, [ SpeciesId.WORMADAM, "trash", From 48e911e03cc71e6bec3883acb9fc8ca045364a1c Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Mon, 9 Jun 2025 18:24:13 -0500 Subject: [PATCH 27/51] [Refactor] Remove circular deps 3 (#5959) * Move game-mode to its own file Reduces circular imports to 325 * Move battler-index to own file Reduces circular deps to 314 * Move trainer-variant to own file Reduces circ deps to 313 * Move enums in pokemon to their own file * Move arena-tag-type to its own file * Move pokemon-moves to its own file * Move command to own file * Move learnMoveType to own file * Move form change item to own file * Move battlerTagLapseType to own file * Move anim enums to own shared file * Move enums out of challenges * Move species form change triggers to own file Reduces circ imports to 291 * Update test importing pokemon move * Replace move attribute imports with string names * Untangle circular deps from game data * Fix missing string call in switch summon phase * Apply kev's suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Ensure ChargeMove's is method calls super * Use InstanceType for proper narrowing * Apply kev's suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/@types/move-types.ts | 56 ++ src/battle-scene.ts | 19 +- src/battle.ts | 14 +- src/constants.ts | 37 ++ src/data/abilities/ability.ts | 86 ++- src/data/arena-tag.ts | 14 +- src/data/battle-anims.ts | 117 +--- src/data/battler-tags.ts | 35 +- src/data/berry.ts | 2 +- src/data/challenge.ts | 92 +-- src/data/moves/apply-attrs.ts | 58 ++ src/data/moves/move-utils.ts | 96 ++- src/data/moves/move.ts | 546 ++++++++++++------ src/data/moves/pokemon-move.ts | 93 +++ .../encounters/absolute-avarice-encounter.ts | 5 +- .../encounters/bug-type-superfan-encounter.ts | 2 +- .../encounters/clowning-around-encounter.ts | 4 +- .../encounters/dancing-lessons-encounter.ts | 5 +- .../encounters/field-trip-encounter.ts | 3 +- .../encounters/fiery-fallout-encounter.ts | 4 +- .../encounters/fun-and-games-encounter.ts | 4 +- .../global-trade-system-encounter.ts | 3 +- .../encounters/lost-at-sea-encounter.ts | 2 +- .../slumbering-snorlax-encounter.ts | 5 +- .../encounters/the-strong-stuff-encounter.ts | 4 +- .../the-winstrate-challenge-encounter.ts | 2 +- .../encounters/training-session-encounter.ts | 2 +- .../encounters/trash-to-treasure-encounter.ts | 4 +- .../encounters/uncommon-breed-encounter.ts | 7 +- .../encounters/weird-dream-encounter.ts | 2 +- .../mystery-encounter-requirements.ts | 4 +- .../mystery-encounters/mystery-encounter.ts | 7 +- .../can-learn-move-requirement.ts | 2 +- .../utils/encounter-phase-utils.ts | 12 +- src/data/pokemon-forms.ts | 484 +--------------- .../pokemon-forms/form-change-triggers.ts | 348 +++++++++++ src/data/pokemon-species.ts | 3 +- src/data/terrain.ts | 5 +- src/data/trainers/TrainerPartyTemplate.ts | 2 +- src/data/trainers/trainer-config.ts | 4 +- src/data/weather.ts | 5 +- src/enums/ability-attr.ts | 11 + src/enums/ai-type.ts | 5 + src/enums/arena-tag-side.ts | 5 + src/enums/battler-index.ts | 7 + src/enums/battler-tag-lapse-type.ts | 12 + src/enums/challenge-type.ts | 69 +++ src/enums/command.ts | 7 + src/enums/dex-attr.ts | 11 + src/enums/field-position.ts | 5 + src/enums/form-change-item.ts | 119 ++++ src/enums/game-modes.ts | 7 + src/enums/hit-result.ts | 15 + src/enums/learn-move-situation.ts | 8 + src/enums/learn-move-type.ts | 8 + src/enums/move-anims-common.ts | 95 +++ src/enums/move-result.ts | 7 + src/enums/move-source-type.ts | 12 + src/enums/trainer-variant.ts | 5 + src/events/arena.ts | 2 +- src/field/arena.ts | 12 +- src/field/damage-number-handler.ts | 4 +- src/field/pokemon.ts | 257 ++------- src/field/trainer.ts | 10 +- src/game-mode.ts | 12 +- src/modifier/modifier-type.ts | 15 +- src/modifier/modifier.ts | 7 +- src/overrides.ts | 2 +- src/phases/attempt-capture-phase.ts | 2 +- src/phases/berry-phase.ts | 2 +- src/phases/check-status-effect-phase.ts | 2 +- src/phases/command-phase.ts | 8 +- src/phases/common-anim-phase.ts | 4 +- src/phases/damage-anim-phase.ts | 5 +- src/phases/encounter-phase.ts | 4 +- src/phases/enemy-command-phase.ts | 4 +- src/phases/evolution-phase.ts | 2 +- src/phases/faint-phase.ts | 12 +- src/phases/form-change-phase.ts | 2 +- src/phases/learn-move-phase.ts | 12 +- src/phases/move-charge-phase.ts | 12 +- src/phases/move-effect-phase.ts | 62 +- src/phases/move-end-phase.ts | 4 +- src/phases/move-header-phase.ts | 6 +- src/phases/move-phase.ts | 47 +- src/phases/mystery-encounter-phases.ts | 2 +- src/phases/obtain-status-effect-phase.ts | 7 +- src/phases/pokemon-heal-phase.ts | 6 +- src/phases/pokemon-phase.ts | 2 +- src/phases/pokemon-transform-phase.ts | 4 +- src/phases/post-turn-status-effect-phase.ts | 5 +- src/phases/quiet-form-change-phase.ts | 5 +- src/phases/return-phase.ts | 2 +- src/phases/scan-ivs-phase.ts | 2 +- src/phases/select-starter-phase.ts | 5 +- src/phases/select-target-phase.ts | 4 +- src/phases/shiny-sparkle-phase.ts | 2 +- src/phases/show-ability-phase.ts | 2 +- src/phases/stat-stage-change-phase.ts | 5 +- src/phases/summon-phase.ts | 4 +- src/phases/switch-summon-phase.ts | 7 +- src/phases/tera-phase.ts | 5 +- src/phases/title-phase.ts | 3 +- src/phases/toggle-double-position-phase.ts | 2 +- src/phases/turn-end-phase.ts | 2 +- src/phases/turn-init-phase.ts | 2 +- src/phases/turn-start-phase.ts | 9 +- src/phases/victory-phase.ts | 2 +- src/phases/weather-effect-phase.ts | 4 +- src/system/game-data.ts | 111 +--- src/system/pokemon-data.ts | 3 +- src/system/trainer-data.ts | 3 +- src/system/unlockables.ts | 3 +- .../version_migration/versions/v1_0_4.ts | 4 +- .../version_migration/versions/v1_7_0.ts | 3 +- .../version_migration/versions/v1_8_3.ts | 3 +- .../version_migration/versions/v1_9_0.ts | 2 +- src/ui/arena-flyout.ts | 3 +- src/ui/ball-ui-handler.ts | 2 +- src/ui/command-ui-handler.ts | 9 +- src/ui/fight-ui-handler.ts | 5 +- src/ui/game-stats-ui-handler.ts | 2 +- src/ui/hatched-pokemon-container.ts | 2 +- src/ui/party-ui-handler.ts | 16 +- src/ui/pokedex-page-ui-handler.ts | 3 +- src/ui/pokedex-ui-handler.ts | 7 +- src/ui/pokemon-info-container.ts | 2 +- src/ui/run-history-ui-handler.ts | 4 +- src/ui/run-info-ui-handler.ts | 4 +- src/ui/starter-select-ui-handler.ts | 12 +- src/ui/summary-ui-handler.ts | 3 +- src/ui/target-select-ui-handler.ts | 4 +- src/utils/data.ts | 47 ++ test/abilities/analytic.test.ts | 2 +- test/abilities/aroma_veil.test.ts | 2 +- test/abilities/battle_bond.test.ts | 3 +- test/abilities/beast_boost.test.ts | 2 +- test/abilities/commander.test.ts | 4 +- test/abilities/dancer.test.ts | 4 +- test/abilities/desolate-land.test.ts | 2 +- test/abilities/disguise.test.ts | 2 +- test/abilities/early_bird.test.ts | 2 +- test/abilities/flash_fire.test.ts | 2 +- test/abilities/flower_gift.test.ts | 2 +- test/abilities/flower_veil.test.ts | 2 +- test/abilities/forecast.test.ts | 2 +- test/abilities/friend_guard.test.ts | 2 +- test/abilities/good_as_gold.test.ts | 4 +- test/abilities/gorilla_tactics.test.ts | 2 +- test/abilities/gulp_missile.test.ts | 2 +- test/abilities/harvest.test.ts | 2 +- test/abilities/honey_gather.test.ts | 2 +- test/abilities/ice_face.test.ts | 2 +- test/abilities/infiltrator.test.ts | 2 +- test/abilities/lightningrod.test.ts | 2 +- test/abilities/magic_bounce.test.ts | 4 +- test/abilities/magic_guard.test.ts | 3 +- test/abilities/mirror_armor.test.ts | 2 +- test/abilities/mold_breaker.test.ts | 2 +- test/abilities/moxie.test.ts | 2 +- test/abilities/neutralizing_gas.test.ts | 4 +- test/abilities/no_guard.test.ts | 2 +- .../abilities/normal-move-type-change.test.ts | 2 +- test/abilities/pastel_veil.test.ts | 2 +- test/abilities/protosynthesis.test.ts | 2 +- test/abilities/serene_grace.test.ts | 5 +- test/abilities/sheer_force.test.ts | 7 +- test/abilities/shield_dust.test.ts | 2 +- test/abilities/speed_boost.test.ts | 2 +- test/abilities/stakeout.test.ts | 2 +- test/abilities/storm_drain.test.ts | 2 +- test/abilities/supreme_overlord.test.ts | 2 +- test/abilities/sweet_veil.test.ts | 2 +- test/abilities/tera_shell.test.ts | 2 +- test/abilities/unburden.test.ts | 2 +- test/abilities/victory_star.test.ts | 2 +- test/abilities/volt_absorb.test.ts | 2 +- test/abilities/wimp_out.test.ts | 4 +- test/arena/arena_gravity.test.ts | 2 +- test/arena/weather_hail.test.ts | 2 +- test/battle/battle.test.ts | 3 +- test/battle/double_battle.test.ts | 3 +- test/battle/inverse_battle.test.ts | 2 +- test/battlerTags/octolock.test.ts | 3 +- test/battlerTags/substitute.test.ts | 5 +- test/data/status_effect.test.ts | 2 +- test/endless_boss.test.ts | 2 +- test/enemy_command.test.ts | 2 +- test/escape-calculations.test.ts | 2 +- test/final_boss.test.ts | 2 +- test/game-mode.test.ts | 3 +- test/imports.test.ts | 2 +- test/items/grip_claw.test.ts | 2 +- test/items/multi_lens.test.ts | 2 +- test/items/reviver_seed.test.ts | 2 +- test/moves/after_you.test.ts | 4 +- test/moves/alluring_voice.test.ts | 2 +- test/moves/assist.test.ts | 4 +- test/moves/aurora_veil.test.ts | 5 +- test/moves/baneful_bunker.test.ts | 2 +- test/moves/baton_pass.test.ts | 2 +- test/moves/burning_jealousy.test.ts | 2 +- test/moves/camouflage.test.ts | 2 +- test/moves/ceaseless_edge.test.ts | 3 +- test/moves/chloroblast.test.ts | 2 +- test/moves/copycat.test.ts | 8 +- test/moves/destiny_bond.test.ts | 4 +- test/moves/dig.test.ts | 4 +- test/moves/disable.test.ts | 4 +- test/moves/dive.test.ts | 2 +- test/moves/doodle.test.ts | 2 +- test/moves/dragon_cheer.test.ts | 2 +- test/moves/dragon_tail.test.ts | 2 +- test/moves/dynamax_cannon.test.ts | 2 +- test/moves/electrify.test.ts | 2 +- test/moves/electro_shot.test.ts | 2 +- test/moves/encore.test.ts | 4 +- test/moves/fairy_lock.test.ts | 2 +- test/moves/false_swipe.test.ts | 2 +- test/moves/fly.test.ts | 4 +- test/moves/follow_me.test.ts | 2 +- test/moves/freeze_dry.test.ts | 2 +- test/moves/fusion_flare_bolt.test.ts | 2 +- test/moves/gastro_acid.test.ts | 4 +- test/moves/geomancy.test.ts | 2 +- test/moves/gigaton_hammer.test.ts | 2 +- test/moves/grudge.test.ts | 2 +- test/moves/heal_block.test.ts | 4 +- test/moves/instruct.test.ts | 4 +- test/moves/jaw_lock.test.ts | 2 +- test/moves/lash_out.test.ts | 2 +- test/moves/last-resort.test.ts | 4 +- test/moves/last_respects.test.ts | 2 +- test/moves/light_screen.test.ts | 5 +- test/moves/magic_coat.test.ts | 6 +- test/moves/metal_burst.test.ts | 4 +- test/moves/metronome.test.ts | 4 +- test/moves/miracle_eye.test.ts | 2 +- test/moves/mirror_move.test.ts | 4 +- test/moves/moongeist_beam.test.ts | 3 +- test/moves/multi_target.test.ts | 2 +- test/moves/order_up.test.ts | 2 +- test/moves/plasma_fists.test.ts | 2 +- test/moves/pledge_moves.test.ts | 7 +- test/moves/pollen_puff.test.ts | 2 +- test/moves/powder.test.ts | 5 +- test/moves/protect.test.ts | 7 +- test/moves/purify.test.ts | 2 +- test/moves/quash.test.ts | 4 +- test/moves/quick_guard.test.ts | 4 +- test/moves/rage_fist.test.ts | 2 +- test/moves/rage_powder.test.ts | 2 +- test/moves/reflect.test.ts | 5 +- test/moves/revival_blessing.test.ts | 4 +- test/moves/roost.test.ts | 2 +- test/moves/round.test.ts | 2 +- test/moves/safeguard.test.ts | 2 +- test/moves/scale_shot.test.ts | 2 +- test/moves/secret_power.test.ts | 4 +- test/moves/shed_tail.test.ts | 2 +- test/moves/shell_side_arm.test.ts | 6 +- test/moves/shell_trap.test.ts | 4 +- test/moves/sketch.test.ts | 5 +- test/moves/sleep_talk.test.ts | 2 +- test/moves/solar_beam.test.ts | 2 +- test/moves/spectral_thief.test.ts | 2 +- test/moves/spikes.test.ts | 3 +- test/moves/spit_up.test.ts | 2 +- test/moves/spotlight.test.ts | 2 +- test/moves/steamroller.test.ts | 2 +- test/moves/stockpile.test.ts | 2 +- test/moves/substitute.test.ts | 8 +- test/moves/swallow.test.ts | 2 +- test/moves/syrup_bomb.test.ts | 2 +- test/moves/tailwind.test.ts | 2 +- test/moves/tar_shot.test.ts | 2 +- test/moves/taunt.test.ts | 2 +- test/moves/telekinesis.test.ts | 4 +- test/moves/tera_blast.test.ts | 6 +- test/moves/tera_starstorm.test.ts | 2 +- test/moves/throat_chop.test.ts | 2 +- test/moves/torment.test.ts | 2 +- test/moves/toxic.test.ts | 2 +- test/moves/toxic_spikes.test.ts | 5 +- test/moves/transform.test.ts | 2 +- test/moves/triple_arrows.test.ts | 6 +- test/moves/upper_hand.test.ts | 4 +- test/moves/whirlwind.test.ts | 4 +- test/moves/will_o_wisp.test.ts | 2 +- ...an-offer-you-cant-refuse-encounter.test.ts | 2 +- .../bug-type-superfan-encounter.test.ts | 2 +- .../clowning-around-encounter.test.ts | 2 +- .../dancing-lessons-encounter.test.ts | 2 +- .../fight-or-flight-encounter.test.ts | 2 +- .../fun-and-games-encounter.test.ts | 2 +- .../encounters/part-timer-encounter.test.ts | 2 +- .../the-strong-stuff-encounter.test.ts | 2 +- .../trash-to-treasure-encounter.test.ts | 2 +- .../uncommon-breed-encounter.test.ts | 2 +- test/phases/frenzy-move-reset.test.ts | 2 +- test/reload.test.ts | 2 +- test/testUtils/gameManager.ts | 5 +- test/testUtils/gameManagerUtils.ts | 3 +- test/testUtils/helpers/classicModeHelper.ts | 3 +- test/testUtils/helpers/field-helper.ts | 2 +- test/testUtils/helpers/moveHelper.ts | 8 +- test/ui/starter-select.test.ts | 2 +- 307 files changed, 2185 insertions(+), 1774 deletions(-) create mode 100644 src/@types/move-types.ts create mode 100644 src/data/moves/apply-attrs.ts create mode 100644 src/data/moves/pokemon-move.ts create mode 100644 src/data/pokemon-forms/form-change-triggers.ts create mode 100644 src/enums/ability-attr.ts create mode 100644 src/enums/ai-type.ts create mode 100644 src/enums/arena-tag-side.ts create mode 100644 src/enums/battler-index.ts create mode 100644 src/enums/battler-tag-lapse-type.ts create mode 100644 src/enums/challenge-type.ts create mode 100644 src/enums/command.ts create mode 100644 src/enums/dex-attr.ts create mode 100644 src/enums/field-position.ts create mode 100644 src/enums/form-change-item.ts create mode 100644 src/enums/game-modes.ts create mode 100644 src/enums/hit-result.ts create mode 100644 src/enums/learn-move-situation.ts create mode 100644 src/enums/learn-move-type.ts create mode 100644 src/enums/move-anims-common.ts create mode 100644 src/enums/move-result.ts create mode 100644 src/enums/move-source-type.ts create mode 100644 src/enums/trainer-variant.ts diff --git a/src/@types/move-types.ts b/src/@types/move-types.ts new file mode 100644 index 00000000000..d9a06fd20ee --- /dev/null +++ b/src/@types/move-types.ts @@ -0,0 +1,56 @@ +import type { + AttackMove, + StatusMove, + SelfStatusMove, + ChargingAttackMove, + ChargingSelfStatusMove, + MoveAttrConstructorMap, + MoveAttr, +} from "#app/data/moves/move"; + +export type MoveAttrFilter = (attr: MoveAttr) => boolean; + +export type * from "#app/data/moves/move"; + +/** + * Map of move subclass names to their respective classes. + * Does not include the ChargeMove subclasses. For that, use `ChargingMoveClassMap`. + * + * @privateremarks + * The `never` field (`declare private _: never`) in some classes is necessary + * to ensure typescript does not improperly narrow a failed `is` guard to `never`. + * + * For example, if we did not have the never, and wrote + * ``` + * function Foo(move: Move) { + * if (move.is("AttackMove")) { + * + * } else if (move.is("StatusMove")) { // typescript errors on the `is`, saying that `move` is `never` + * + * } + * ``` + */ +export type MoveClassMap = { + AttackMove: AttackMove; + StatusMove: StatusMove; + SelfStatusMove: SelfStatusMove; +}; + +/** + * Union type of all move subclass names + */ +export type MoveKindString = "AttackMove" | "StatusMove" | "SelfStatusMove"; + +/** + * Map of move attribute names to attribute instances. + */ +export type MoveAttrMap = { + [K in keyof MoveAttrConstructorMap]: InstanceType; +}; + +/** + * Union type of all move attribute names as strings. + */ +export type MoveAttrString = keyof MoveAttrMap; + +export type ChargingMove = ChargingAttackMove | ChargingSelfStatusMove; diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 7743302cf94..5586691a48d 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -80,13 +80,15 @@ import type { FixedBattleConfig } from "#app/battle"; import Battle from "#app/battle"; import { BattleType } from "#enums/battle-type"; import type { GameMode } from "#app/game-mode"; -import { GameModes, getGameMode } from "#app/game-mode"; +import { getGameMode } from "#app/game-mode"; +import { GameModes } from "#enums/game-modes"; import FieldSpritePipeline from "#app/pipelines/field-sprite"; import SpritePipeline from "#app/pipelines/sprite"; import PartyExpBar from "#app/ui/party-exp-bar"; import type { TrainerSlot } from "./enums/trainer-slot"; import { trainerConfigs } from "#app/data/trainers/trainer-config"; -import Trainer, { TrainerVariant } from "#app/field/trainer"; +import Trainer from "#app/field/trainer"; +import { TrainerVariant } from "#enums/trainer-variant"; import type TrainerData from "#app/system/trainer-data"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; @@ -101,13 +103,12 @@ import type UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin"; import { addUiThemeOverrides } from "#app/ui/ui-theme"; import type PokemonData from "#app/system/pokemon-data"; import { Nature } from "#enums/nature"; -import type { SpeciesFormChange, SpeciesFormChangeTrigger } from "#app/data/pokemon-forms"; -import { - FormChangeItem, - pokemonFormChanges, - SpeciesFormChangeManualTrigger, - SpeciesFormChangeTimeOfDayTrigger, -} from "#app/data/pokemon-forms"; +import type { SpeciesFormChange } from "#app/data/pokemon-forms"; +import type { SpeciesFormChangeTrigger } from "./data/pokemon-forms/form-change-triggers"; +import { pokemonFormChanges } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeTimeOfDayTrigger } from "./data/pokemon-forms/form-change-triggers"; +import { SpeciesFormChangeManualTrigger } from "./data/pokemon-forms/form-change-triggers"; +import { FormChangeItem } from "#enums/form-change-item"; import { getTypeRgb } from "#app/data/type"; import { PokemonType } from "#enums/pokemon-type"; import PokemonSpriteSparkleHandler from "#app/field/pokemon-sprite-sparkle-handler"; diff --git a/src/battle.ts b/src/battle.ts index 2ebfb634751..0cf01a0873d 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import type { Command } from "./ui/command-ui-handler"; +import type { Command } from "#enums/command"; import { randomString, getEnumValues, @@ -10,7 +10,8 @@ import { randInt, randSeedFloat, } from "#app/utils/common"; -import Trainer, { TrainerVariant } from "./field/trainer"; +import Trainer from "./field/trainer"; +import { TrainerVariant } from "#enums/trainer-variant"; import type { GameMode } from "./game-mode"; import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier"; import type { PokeballType } from "#enums/pokeball"; @@ -33,14 +34,7 @@ import { ModifierTier } from "#app/modifier/modifier-tier"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { BattleType } from "#enums/battle-type"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; - -export enum BattlerIndex { - ATTACKER = -1, - PLAYER, - PLAYER_2, - ENEMY, - ENEMY_2, -} +import { BattlerIndex } from "#enums/battler-index"; export interface TurnCommand { command: Command; diff --git a/src/constants.ts b/src/constants.ts index d3594c389b6..62056ecc0d4 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,3 +1,5 @@ +import { SpeciesId } from "#enums/species-id"; + /** The maximum size of the player's party */ export const PLAYER_PARTY_MAX_SIZE: number = 6; @@ -17,3 +19,38 @@ export const CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES: [number, number] = [10, 180 /** The raw percentage power boost for type boost items*/ export const TYPE_BOOST_ITEM_BOOST_PERCENT = 20; + +/** + * The default species that a new player can choose from + */ +export const defaultStarterSpecies: SpeciesId[] = [ + SpeciesId.BULBASAUR, + SpeciesId.CHARMANDER, + SpeciesId.SQUIRTLE, + SpeciesId.CHIKORITA, + SpeciesId.CYNDAQUIL, + SpeciesId.TOTODILE, + SpeciesId.TREECKO, + SpeciesId.TORCHIC, + SpeciesId.MUDKIP, + SpeciesId.TURTWIG, + SpeciesId.CHIMCHAR, + SpeciesId.PIPLUP, + SpeciesId.SNIVY, + SpeciesId.TEPIG, + SpeciesId.OSHAWOTT, + SpeciesId.CHESPIN, + SpeciesId.FENNEKIN, + SpeciesId.FROAKIE, + SpeciesId.ROWLET, + SpeciesId.LITTEN, + SpeciesId.POPPLIO, + SpeciesId.GROOKEY, + SpeciesId.SCORBUNNY, + SpeciesId.SOBBLE, + SpeciesId.SPRIGATITO, + SpeciesId.FUECOCO, + SpeciesId.QUAXLY, +]; + +export const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 34ae8be78b6..128e772217f 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -1,4 +1,5 @@ -import { HitResult, MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; +import { HitResult } from "#enums/hit-result"; import { BooleanHolder, NumberHolder, @@ -10,40 +11,26 @@ import { randSeedFloat, } from "#app/utils/common"; import { getPokemonNameWithAffix } from "#app/messages"; -import { BattlerTagLapseType, GroundedTag } from "#app/data/battler-tags"; +import { GroundedTag } from "#app/data/battler-tags"; +import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText, } from "#app/data/status-effect"; import { Gender } from "#app/data/gender"; -import { - AttackMove, - FlinchAttr, - OneHitKOAttr, - HitHealAttr, - StatusMove, - SelfStatusMove, - VariablePowerAttr, - applyMoveAttrs, - RandomMovesetMoveAttr, - RandomMoveAttr, - NaturePowerAttr, - CopyMoveAttr, - NeutralDamageAgainstFlyingTypeMultiplierAttr, - FixedDamageAttr, -} from "#app/data/moves/move"; +import { applyMoveAttrs } from "../moves/apply-attrs"; import { allMoves } from "../data-lists"; -import { ArenaTagSide } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { TerrainType } from "#app/data/terrain"; import { - SpeciesFormChangeAbilityTrigger, SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger, -} from "#app/data/pokemon-forms"; +} from "../pokemon-forms/form-change-triggers"; +import { SpeciesFormChangeAbilityTrigger } from "../pokemon-forms/form-change-triggers"; import i18next from "i18next"; -import { Command } from "#app/ui/command-ui-handler"; +import { Command } from "#enums/command"; import { BerryModifierType } from "#app/modifier/modifier-type"; import { getPokeballName } from "#app/data/pokeball"; import { BattleType } from "#enums/battle-type"; @@ -69,12 +56,13 @@ import { MoveFlags } from "#enums/MoveFlags"; import { MoveTarget } from "#enums/MoveTarget"; import { MoveCategory } from "#enums/MoveCategory"; import type { BerryType } from "#enums/berry-type"; -import { CommonAnim } from "../battle-anims"; +import { CommonAnim } from "#enums/move-anims-common"; import { getBerryEffectFunc } from "../berry"; import { BerryUsedEvent } from "#app/events/battle-scene"; // Type imports -import type { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; +import type { EnemyPokemon } from "#app/field/pokemon"; +import type { PokemonMove } from "../moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; import type { Weather } from "#app/data/weather"; import type { BattlerTag } from "#app/data/battler-tags"; @@ -86,7 +74,7 @@ import type { AbAttrApplyFunc, AbAttrSuccessFunc, } from "#app/@types/ability-types"; -import type { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#enums/battler-index"; import type Move from "#app/data/moves/move"; import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag"; import { noAbilityTypeOverrideMoves } from "../moves/invalid-moves"; @@ -520,7 +508,7 @@ export class AttackTypeImmunityAbAttr extends TypeImmunityAbAttr { ): boolean { return ( move.category !== MoveCategory.STATUS && - !move.hasAttr(NeutralDamageAgainstFlyingTypeMultiplierAttr) && + !move.hasAttr("NeutralDamageAgainstFlyingTypeMultiplierAttr") && super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args) ); } @@ -693,7 +681,7 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr { args.length > 0 ? (args[0] as NumberHolder).value : pokemon.getAttackTypeEffectiveness(attacker.getMoveType(move), attacker, undefined, undefined, move); - return move instanceof AttackMove && modifierValue < 2; + return move.is("AttackMove") && modifierValue < 2; } override applyPreDefend( @@ -735,7 +723,7 @@ export class FullHpResistTypeAbAttr extends PreDefendAbAttr { const typeMultiplier = args[0]; return ( typeMultiplier instanceof NumberHolder && - !move?.hasAttr(FixedDamageAttr) && + !move?.hasAttr("FixedDamageAttr") && pokemon.isFullHp() && typeMultiplier.value > 0.5 ); @@ -980,7 +968,7 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr { _hitResult: HitResult | null, _args: any[], ): boolean { - return move.hasAttr(HitHealAttr); + return move.hasAttr("HitHealAttr"); } /** @@ -2061,10 +2049,10 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr { */ !move.findAttr( attr => - attr instanceof RandomMovesetMoveAttr || - attr instanceof RandomMoveAttr || - attr instanceof NaturePowerAttr || - attr instanceof CopyMoveAttr, + attr.is("RandomMovesetMoveAttr") || + attr.is("RandomMoveAttr") || + attr.is("NaturePowerAttr") || + attr.is("CopyMoveAttr"), ) ) { const moveType = pokemon.getMoveType(move); @@ -2539,17 +2527,11 @@ export class AllyStatMultiplierAbAttr extends AbAttr { * @extends AbAttr */ export class ExecutedMoveAbAttr extends AbAttr { - canApplyExecutedMove( - _pokemon: Pokemon, - _simulated: boolean, - ): boolean { + canApplyExecutedMove(_pokemon: Pokemon, _simulated: boolean): boolean { return true; } - applyExecutedMove( - _pokemon: Pokemon, - _simulated: boolean, - ): void {} + applyExecutedMove(_pokemon: Pokemon, _simulated: boolean): void {} } /** @@ -2557,7 +2539,7 @@ export class ExecutedMoveAbAttr extends AbAttr { * @extends ExecutedMoveAbAttr */ export class GorillaTacticsAbAttr extends ExecutedMoveAbAttr { - constructor(showAbility: boolean = false) { + constructor(showAbility = false) { super(showAbility); } @@ -4926,13 +4908,13 @@ function getAnticipationCondition(): AbAttrCondition { } // the move's base type (not accounting for variable type changes) is super effective if ( - move.getMove() instanceof AttackMove && + move.getMove().is("AttackMove") && pokemon.getAttackTypeEffectiveness(move.getMove().type, opponent, true, undefined, move.getMove()) >= 2 ) { return true; } // move is a OHKO - if (move.getMove().hasAttr(OneHitKOAttr)) { + if (move.getMove().hasAttr("OneHitKOAttr")) { return true; } // edge case for hidden power, type is computed @@ -5001,9 +4983,9 @@ export class ForewarnAbAttr extends PostSummonAbAttr { let movePower = 0; for (const opponent of pokemon.getOpponents()) { for (const move of opponent.moveset) { - if (move?.getMove() instanceof StatusMove) { + if (move?.getMove().is("StatusMove")) { movePower = 1; - } else if (move?.getMove().hasAttr(OneHitKOAttr)) { + } else if (move?.getMove().hasAttr("OneHitKOAttr")) { movePower = 150; } else if ( move?.getMove().id === MoveId.COUNTER || @@ -5874,10 +5856,10 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { dancer.turnData.extraTurns++; const phaseManager = globalScene.phaseManager; // If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance - if (move.getMove() instanceof AttackMove || move.getMove() instanceof StatusMove) { + if (move.getMove().is("AttackMove") || move.getMove().is("StatusMove")) { const target = this.getTarget(dancer, source, targets); phaseManager.unshiftNew("MovePhase", dancer, target, move, true, true); - } else if (move.getMove() instanceof SelfStatusMove) { + } else if (move.getMove().is("SelfStatusMove")) { // If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself phaseManager.unshiftNew("MovePhase", dancer, [dancer.getBattlerIndex()], move, true, true); } @@ -7785,7 +7767,7 @@ export function applyPreAttackAbAttrs( export function applyExecutedMoveAbAttrs( attrType: Constructor, pokemon: Pokemon, - simulated: boolean = false, + simulated = false, ...args: any[] ): void { applyAbAttrsInternal( @@ -8207,7 +8189,7 @@ export function initAbilities() { allAbilities.push( new Ability(AbilityId.NONE, 3), new Ability(AbilityId.STENCH, 3) - .attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => !move.hasAttr(FlinchAttr) && !move.hitsSubstitute(user, target) ? 10 : 0, BattlerTagType.FLINCHED), + .attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => !move.hasAttr("FlinchAttr") && !move.hitsSubstitute(user, target) ? 10 : 0, BattlerTagType.FLINCHED), new Ability(AbilityId.DRIZZLE, 3) .attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN) .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.RAIN), @@ -8514,7 +8496,7 @@ export function initAbilities() { new Ability(AbilityId.TECHNICIAN, 4) .attr(MovePowerBoostAbAttr, (user, target, move) => { const power = new NumberHolder(move.power); - applyMoveAttrs(VariablePowerAttr, user, target, move, power); + applyMoveAttrs("VariablePowerAttr", user, target, move, power); return power.value <= 60; }, 1.5), new Ability(AbilityId.LEAF_GUARD, 4) @@ -8635,7 +8617,7 @@ export function initAbilities() { ) .edgeCase(), // Cannot recover berries used up by fling or natural gift (unimplemented) new Ability(AbilityId.TELEPATHY, 5) - .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move instanceof AttackMove) + .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move.is("AttackMove")) .ignorable(), new Ability(AbilityId.MOODY, 5) .attr(MoodyAbAttr), diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 28b8c6acd41..e18ee5ac556 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -7,9 +7,9 @@ import { MoveTarget } from "#enums/MoveTarget"; import { MoveCategory } from "#enums/MoveCategory"; import { getPokemonNameWithAffix } from "#app/messages"; import type Pokemon from "#app/field/pokemon"; -import { HitResult } from "#app/field/pokemon"; +import { HitResult } from "#enums/hit-result"; import { StatusEffect } from "#enums/status-effect"; -import type { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#enums/battler-index"; import { BlockNonDirectDamageAbAttr, InfiltratorAbAttr, @@ -20,18 +20,14 @@ import { applyOnLoseAbAttrs, } from "#app/data/abilities/ability"; import { Stat } from "#enums/stat"; -import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims"; +import { CommonBattleAnim } from "#app/data/battle-anims"; +import { CommonAnim } from "#enums/move-anims-common"; import i18next from "i18next"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; import { MoveId } from "#enums/move-id"; - -export enum ArenaTagSide { - BOTH, - PLAYER, - ENEMY, -} +import { ArenaTagSide } from "#enums/arena-tag-side"; export abstract class ArenaTag { constructor( diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index 321d9938b2f..0b40469b255 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -1,111 +1,15 @@ import { globalScene } from "#app/global-scene"; -import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, SelfStatusMove } from "./moves/move"; import { allMoves } from "./data-lists"; import { MoveFlags } from "#enums/MoveFlags"; import type Pokemon from "../field/pokemon"; import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils/common"; -import type { BattlerIndex } from "../battle"; +import type { BattlerIndex } from "#enums/battler-index"; import { MoveId } from "#enums/move-id"; import { SubstituteTag } from "./battler-tags"; import { isNullOrUndefined } from "../utils/common"; import Phaser from "phaser"; import { EncounterAnim } from "#enums/encounter-anims"; - -export enum AnimFrameTarget { - USER, - TARGET, - GRAPHIC, -} - -enum AnimFocus { - TARGET = 1, - USER, - USER_TARGET, - SCREEN, -} - -enum AnimBlendType { - NORMAL, - ADD, - SUBTRACT, -} - -export enum ChargeAnim { - FLY_CHARGING = 1000, - BOUNCE_CHARGING, - DIG_CHARGING, - FUTURE_SIGHT_CHARGING, - DIVE_CHARGING, - SOLAR_BEAM_CHARGING, - SHADOW_FORCE_CHARGING, - SKULL_BASH_CHARGING, - FREEZE_SHOCK_CHARGING, - SKY_DROP_CHARGING, - SKY_ATTACK_CHARGING, - ICE_BURN_CHARGING, - DOOM_DESIRE_CHARGING, - RAZOR_WIND_CHARGING, - PHANTOM_FORCE_CHARGING, - GEOMANCY_CHARGING, - SHADOW_BLADE_CHARGING, - SOLAR_BLADE_CHARGING, - BEAK_BLAST_CHARGING, - METEOR_BEAM_CHARGING, - ELECTRO_SHOT_CHARGING, -} - -export enum CommonAnim { - USE_ITEM = 2000, - HEALTH_UP, - TERASTALLIZE, - POISON = 2010, - TOXIC, - PARALYSIS, - SLEEP, - FROZEN, - BURN, - CONFUSION, - ATTRACT, - BIND, - WRAP, - CURSE_NO_GHOST, - LEECH_SEED, - FIRE_SPIN, - PROTECT, - COVET, - WHIRLPOOL, - BIDE, - SAND_TOMB, - QUICK_GUARD, - WIDE_GUARD, - CURSE, - MAGMA_STORM, - CLAMP, - SNAP_TRAP, - THUNDER_CAGE, - INFESTATION, - ORDER_UP_CURLY, - ORDER_UP_DROOPY, - ORDER_UP_STRETCHY, - RAGING_BULL_FIRE, - RAGING_BULL_WATER, - SALT_CURE, - POWDER, - SUNNY = 2100, - RAIN, - SANDSTORM, - HAIL, - SNOW, - WIND, - HEAVY_RAIN, - HARSH_SUN, - STRONG_WINDS, - MISTY_TERRAIN = 2110, - ELECTRIC_TERRAIN, - GRASSY_TERRAIN, - PSYCHIC_TERRAIN, - LOCK_ON = 2120, -} +import { AnimBlendType, AnimFrameTarget, AnimFocus, ChargeAnim, CommonAnim } from "#enums/move-anims-common"; export class AnimConfig { public id: number; @@ -531,7 +435,7 @@ export function initMoveAnim(move: MoveId): Promise { if (moveAnims.get(move) !== null) { const chargeAnimSource = allMoves[move].isChargingMove() ? allMoves[move] - : (allMoves[move].getAttrs(DelayedAttackAttr)[0] ?? allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]); + : (allMoves[move].getAttrs("DelayedAttackAttr")[0] ?? allMoves[move].getAttrs("BeakBlastHeaderAttr")[0]); if (chargeAnimSource && chargeAnims.get(chargeAnimSource.chargeAnim) === null) { return; } @@ -542,12 +446,11 @@ export function initMoveAnim(move: MoveId): Promise { } } else { moveAnims.set(move, null); - const defaultMoveAnim = - allMoves[move] instanceof AttackMove - ? MoveId.TACKLE - : allMoves[move] instanceof SelfStatusMove - ? MoveId.FOCUS_ENERGY - : MoveId.TAIL_WHIP; + const defaultMoveAnim = allMoves[move].is("AttackMove") + ? MoveId.TACKLE + : allMoves[move].is("SelfStatusMove") + ? MoveId.FOCUS_ENERGY + : MoveId.TAIL_WHIP; const fetchAnimAndResolve = (move: MoveId) => { globalScene @@ -570,7 +473,7 @@ export function initMoveAnim(move: MoveId): Promise { } const chargeAnimSource = allMoves[move].isChargingMove() ? allMoves[move] - : (allMoves[move].getAttrs(DelayedAttackAttr)[0] ?? allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]); + : (allMoves[move].getAttrs("DelayedAttackAttr")[0] ?? allMoves[move].getAttrs("BeakBlastHeaderAttr")[0]); if (chargeAnimSource) { initMoveChargeAnim(chargeAnimSource.chargeAnim).then(() => resolve()); } else { @@ -703,7 +606,7 @@ export function loadMoveAnimAssets(moveIds: MoveId[], startLoad?: boolean): Prom for (const moveId of moveIds) { const chargeAnimSource = allMoves[moveId].isChargingMove() ? allMoves[moveId] - : (allMoves[moveId].getAttrs(DelayedAttackAttr)[0] ?? allMoves[moveId].getAttrs(BeakBlastHeaderAttr)[0]); + : (allMoves[moveId].getAttrs("DelayedAttackAttr")[0] ?? allMoves[moveId].getAttrs("BeakBlastHeaderAttr")[0]); if (chargeAnimSource) { const moveChargeAnims = chargeAnims.get(chargeAnimSource.chargeAnim); moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims![0]); // TODO: is the bang correct? diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index d4f62237446..ffa179c6aab 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -9,23 +9,20 @@ import { ReverseDrainAbAttr, } from "#app/data/abilities/ability"; import { allAbilities } from "./data-lists"; -import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims"; +import { CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims"; +import { ChargeAnim, CommonAnim } from "#enums/move-anims-common"; import type Move from "#app/data/moves/move"; -import { - applyMoveAttrs, - ConsecutiveUseDoublePowerAttr, - HealOnAllyAttr, - StatusCategoryOnAllyAttr, -} from "#app/data/moves/move"; +import { applyMoveAttrs } from "./moves/apply-attrs"; import { allMoves } from "./data-lists"; import { MoveFlags } from "#enums/MoveFlags"; import { MoveCategory } from "#enums/MoveCategory"; -import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeAbilityTrigger } from "./pokemon-forms/form-change-triggers"; import { getStatusEffectHealText } from "#app/data/status-effect"; import { TerrainType } from "#app/data/terrain"; import { PokemonType } from "#enums/pokemon-type"; import type Pokemon from "#app/field/pokemon"; -import { HitResult, MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; +import { HitResult } from "#enums/hit-result"; import { getPokemonNameWithAffix } from "#app/messages"; import type { MoveEffectPhase } from "#app/phases/move-effect-phase"; import type { MovePhase } from "#app/phases/move-phase"; @@ -41,19 +38,7 @@ import { EFFECTIVE_STATS, getStatKey, Stat, type BattleStat, type EffectiveStat import { StatusEffect } from "#enums/status-effect"; import { WeatherType } from "#enums/weather-type"; import { isNullOrUndefined } from "#app/utils/common"; - -export enum BattlerTagLapseType { - FAINT, - MOVE, - PRE_MOVE, - AFTER_MOVE, - MOVE_EFFECT, - TURN_END, - HIT, - /** Tag lapses AFTER_HIT, applying its effects even if the user faints */ - AFTER_HIT, - CUSTOM, -} +import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; export class BattlerTag { public tagType: BattlerTagType; @@ -2804,8 +2789,8 @@ export class HealBlockTag extends MoveRestrictionBattlerTag { */ override isMoveTargetRestricted(move: MoveId, user: Pokemon, target: Pokemon) { const moveCategory = new NumberHolder(allMoves[move].category); - applyMoveAttrs(StatusCategoryOnAllyAttr, user, target, allMoves[move], moveCategory); - return allMoves[move].hasAttr(HealOnAllyAttr) && moveCategory.value === MoveCategory.STATUS; + applyMoveAttrs("StatusCategoryOnAllyAttr", user, target, allMoves[move], moveCategory); + return allMoves[move].hasAttr("HealOnAllyAttr") && moveCategory.value === MoveCategory.STATUS; } /** @@ -3141,7 +3126,7 @@ export class TormentTag extends MoveRestrictionBattlerTag { // This checks for locking / momentum moves like Rollout and Hydro Cannon + if the user is under the influence of BattlerTagType.FRENZY // Because Uproar's unique behavior is not implemented, it does not check for Uproar. Torment has been marked as partial in moves.ts const moveObj = allMoves[lastMove.move]; - const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || user.getTag(BattlerTagType.FRENZY); + const isUnaffected = moveObj.hasAttr("ConsecutiveUseDoublePowerAttr") || user.getTag(BattlerTagType.FRENZY); const validLastMoveResult = lastMove.result === MoveResult.SUCCESS || lastMove.result === MoveResult.MISS; return lastMove.move === move && validLastMoveResult && lastMove.move !== MoveId.STRUGGLE && !isUnaffected; } diff --git a/src/data/berry.ts b/src/data/berry.ts index 52ea1c44391..df500fa0609 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -1,6 +1,6 @@ import { getPokemonNameWithAffix } from "../messages"; import type Pokemon from "../field/pokemon"; -import { HitResult } from "../field/pokemon"; +import { HitResult } from "#enums/hit-result"; import { getStatusEffectHealText } from "./status-effect"; import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils/common"; import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./abilities/ability"; diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 8dd303c34fd..8bdccb6d5fd 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -2,17 +2,18 @@ import { BooleanHolder, type NumberHolder, randSeedItem } from "#app/utils/commo import { deepCopy } from "#app/utils/data"; import i18next from "i18next"; import type { DexAttrProps, GameData } from "#app/system/game-data"; -import { defaultStarterSpecies } from "#app/system/game-data"; +import { defaultStarterSpecies } from "#app/constants"; import type PokemonSpecies from "#app/data/pokemon-species"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "./moves/pokemon-move"; import type { FixedBattleConfig } from "#app/battle"; import { getRandomTrainerFunc } from "#app/battle"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; import { BattleType } from "#enums/battle-type"; -import Trainer, { TrainerVariant } from "#app/field/trainer"; +import Trainer from "#app/field/trainer"; +import { TrainerVariant } from "#enums/trainer-variant"; import { PokemonType } from "#enums/pokemon-type"; import { Challenges } from "#enums/challenges"; import { SpeciesId } from "#enums/species-id"; @@ -24,93 +25,12 @@ import { ModifierTier } from "#app/modifier/modifier-tier"; import { globalScene } from "#app/global-scene"; import { pokemonFormChanges } from "./pokemon-forms"; import { pokemonEvolutions } from "./balance/pokemon-evolutions"; +import { ChallengeType } from "#enums/challenge-type"; +import type { MoveSourceType } from "#enums/move-source-type"; /** A constant for the default max cost of the starting party before a run */ const DEFAULT_PARTY_MAX_COST = 10; -/** - * An enum for all the challenge types. The parameter entries on these describe the - * parameters to use when calling the applyChallenges function. - */ -export enum ChallengeType { - /** - * Challenges which modify what starters you can choose - * @see {@link Challenge.applyStarterChoice} - */ - STARTER_CHOICE, - /** - * Challenges which modify how many starter points you have - * @see {@link Challenge.applyStarterPoints} - */ - STARTER_POINTS, - /** - * Challenges which modify how many starter points you have - * @see {@link Challenge.applyStarterPointCost} - */ - STARTER_COST, - /** - * Challenges which modify your starters in some way - * @see {@link Challenge.applyStarterModify} - */ - STARTER_MODIFY, - /** - * Challenges which limit which pokemon you can have in battle. - * @see {@link Challenge.applyPokemonInBattle} - */ - POKEMON_IN_BATTLE, - /** - * Adds or modifies the fixed battles in a run - * @see {@link Challenge.applyFixedBattle} - */ - FIXED_BATTLES, - /** - * Modifies the effectiveness of Type matchups in battle - * @see {@linkcode Challenge.applyTypeEffectiveness} - */ - TYPE_EFFECTIVENESS, - /** - * Modifies what level the AI pokemon are. UNIMPLEMENTED. - */ - AI_LEVEL, - /** - * Modifies how many move slots the AI has. UNIMPLEMENTED. - */ - AI_MOVE_SLOTS, - /** - * Modifies if a pokemon has its passive. UNIMPLEMENTED. - */ - PASSIVE_ACCESS, - /** - * Modifies the game mode settings in some way. UNIMPLEMENTED. - */ - GAME_MODE_MODIFY, - /** - * Modifies what level AI pokemon can access a move. UNIMPLEMENTED. - */ - MOVE_ACCESS, - /** - * Modifies what weight AI pokemon have when generating movesets. UNIMPLEMENTED. - */ - MOVE_WEIGHT, - /** - * Modifies what the pokemon stats for Flip Stat Mode. - */ - FLIP_STAT, -} - -/** - * Used for challenge types that modify movesets, these denote the various sources of moves for pokemon. - */ -export enum MoveSourceType { - LEVEL_UP, // Currently unimplemented for move access - RELEARNER, // Relearner moves currently unimplemented - COMMON_TM, - GREAT_TM, - ULTRA_TM, - COMMON_EGG, - RARE_EGG, -} - /** * A challenge object. Exists only to serve as a base class. */ diff --git a/src/data/moves/apply-attrs.ts b/src/data/moves/apply-attrs.ts new file mode 100644 index 00000000000..98e7b6d791f --- /dev/null +++ b/src/data/moves/apply-attrs.ts @@ -0,0 +1,58 @@ +/** + * Module holding functions to apply move attributes. + * Must not import anything that is not a type. + */ +import type Pokemon from "#app/field/pokemon"; +import type { default as Move, MoveAttr } from "./move"; +import type { ChargingMove } from "#app/@types/move-types"; +import type { MoveAttrFilter, MoveAttrString } from "#app/@types/move-types"; + +function applyMoveAttrsInternal( + attrFilter: MoveAttrFilter, + user: Pokemon | null, + target: Pokemon | null, + move: Move, + args: any[], +): void { + move.attrs.filter(attr => attrFilter(attr)).forEach(attr => attr.apply(user, target, move, args)); +} + +function applyMoveChargeAttrsInternal( + attrFilter: MoveAttrFilter, + user: Pokemon | null, + target: Pokemon | null, + move: ChargingMove, + args: any[], +): void { + move.chargeAttrs.filter(attr => attrFilter(attr)).forEach(attr => attr.apply(user, target, move, args)); +} + +export function applyMoveAttrs( + attrType: MoveAttrString, + user: Pokemon | null, + target: Pokemon | null, + move: Move, + ...args: any[] +): void { + applyMoveAttrsInternal((attr: MoveAttr) => attr.is(attrType), user, target, move, args); +} + +export function applyFilteredMoveAttrs( + attrFilter: MoveAttrFilter, + user: Pokemon, + target: Pokemon | null, + move: Move, + ...args: any[] +): void { + applyMoveAttrsInternal(attrFilter, user, target, move, args); +} + +export function applyMoveChargeAttrs( + attrType: MoveAttrString, + user: Pokemon | null, + target: Pokemon | null, + move: ChargingMove, + ...args: any[] +): void { + applyMoveChargeAttrsInternal((attr: MoveAttr) => attr.is(attrType), user, target, move, args); +} diff --git a/src/data/moves/move-utils.ts b/src/data/moves/move-utils.ts index 3323d6f4a0c..20723ab0347 100644 --- a/src/data/moves/move-utils.ts +++ b/src/data/moves/move-utils.ts @@ -1,5 +1,14 @@ -import { MoveTarget } from "#enums/MoveTarget"; +import type Pokemon from "#app/field/pokemon"; +import type { BattlerIndex } from "#enums/battler-index"; +import type { MoveId } from "#enums/move-id"; +import type { MoveTargetSet, UserMoveConditionFunc } from "./move"; import type Move from "./move"; +import { NumberHolder, isNullOrUndefined } from "#app/utils/common"; +import { MoveTarget } from "#enums/MoveTarget"; +import { PokemonType } from "#enums/pokemon-type"; +import { allMoves } from "#app/data/data-lists"; +import { applyMoveAttrs } from "./apply-attrs"; +import { BattlerTagType } from "#enums/battler-tag-type"; /** * Return whether the move targets the field @@ -18,3 +27,88 @@ export function isFieldTargeted(move: Move): boolean { } return false; } + +export function getMoveTargets(user: Pokemon, move: MoveId, replaceTarget?: MoveTarget): MoveTargetSet { + const variableTarget = new NumberHolder(0); + user.getOpponents(false).forEach(p => applyMoveAttrs("VariableTargetAttr", user, p, allMoves[move], variableTarget)); + + let moveTarget: MoveTarget | undefined; + if (allMoves[move].hasAttr("VariableTargetAttr")) { + moveTarget = variableTarget.value; + } else if (replaceTarget !== undefined) { + moveTarget = replaceTarget; + } else if (move) { + moveTarget = allMoves[move].moveTarget; + } else if (move === undefined) { + moveTarget = MoveTarget.NEAR_ENEMY; + } + const opponents = user.getOpponents(false); + + let set: Pokemon[] = []; + let multiple = false; + const ally: Pokemon | undefined = user.getAlly(); + + switch (moveTarget) { + case MoveTarget.USER: + case MoveTarget.PARTY: + set = [user]; + break; + case MoveTarget.NEAR_OTHER: + case MoveTarget.OTHER: + case MoveTarget.ALL_NEAR_OTHERS: + case MoveTarget.ALL_OTHERS: + set = !isNullOrUndefined(ally) ? opponents.concat([ally]) : opponents; + multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS; + break; + case MoveTarget.NEAR_ENEMY: + case MoveTarget.ALL_NEAR_ENEMIES: + case MoveTarget.ALL_ENEMIES: + case MoveTarget.ENEMY_SIDE: + set = opponents; + multiple = moveTarget !== MoveTarget.NEAR_ENEMY; + break; + case MoveTarget.RANDOM_NEAR_ENEMY: + set = [opponents[user.randBattleSeedInt(opponents.length)]]; + break; + case MoveTarget.ATTACKER: + return { targets: [-1 as BattlerIndex], multiple: false }; + case MoveTarget.NEAR_ALLY: + case MoveTarget.ALLY: + set = !isNullOrUndefined(ally) ? [ally] : []; + break; + case MoveTarget.USER_OR_NEAR_ALLY: + case MoveTarget.USER_AND_ALLIES: + case MoveTarget.USER_SIDE: + set = !isNullOrUndefined(ally) ? [user, ally] : [user]; + multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY; + break; + case MoveTarget.ALL: + case MoveTarget.BOTH_SIDES: + set = (!isNullOrUndefined(ally) ? [user, ally] : [user]).concat(opponents); + multiple = true; + break; + case MoveTarget.CURSE: + { + const extraTargets = !isNullOrUndefined(ally) ? [ally] : []; + set = user.getTypes(true).includes(PokemonType.GHOST) ? opponents.concat(extraTargets) : [user]; + } + break; + } + + return { + targets: set + .filter(p => p?.isActive(true)) + .map(p => p.getBattlerIndex()) + .filter(t => t !== undefined), + multiple, + }; +} + +export const frenzyMissFunc: UserMoveConditionFunc = (user: Pokemon, move: Move) => { + while (user.getMoveQueue().length && user.getMoveQueue()[0].move === move.id) { + user.getMoveQueue().shift(); + } + user.removeTag(BattlerTagType.FRENZY); // FRENZY tag should be disrupted on miss/no effect + + return true; +}; diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 976c0cd7d97..57660b51391 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -1,4 +1,5 @@ -import { ChargeAnim, MoveChargeAnim } from "../battle-anims"; +import { MoveChargeAnim } from "../battle-anims"; +import { ChargeAnim } from "#enums/move-anims-common"; import { CommandedTag, EncoreTag, @@ -14,14 +15,11 @@ import { import { getPokemonNameWithAffix } from "../../messages"; import type { AttackMoveResult, TurnMove } from "../../field/pokemon"; import type Pokemon from "../../field/pokemon"; -import { - EnemyPokemon, - FieldPosition, - HitResult, - MoveResult, - PlayerPokemon, - PokemonMove, -} from "../../field/pokemon"; +import type { EnemyPokemon } from "#app/field/pokemon"; +import { PokemonMove } from "./pokemon-move"; +import { MoveResult } from "#enums/move-result"; +import { HitResult } from "#enums/hit-result"; +import { FieldPosition } from "#enums/field-position"; import { getNonVolatileStatusEffects, getStatusEffectHealText, @@ -32,7 +30,8 @@ import { PokemonType } from "#enums/pokemon-type"; import { BooleanHolder, NumberHolder, isNullOrUndefined, toDmgValue, randSeedItem, randSeedInt, getEnumValues, toReadableString, type Constructor, randSeedFloat } from "#app/utils/common"; import { WeatherType } from "#enums/weather-type"; import type { ArenaTrapTag } from "../arena-tag"; -import { ArenaTagSide, WeakenMoveTypeTag } from "../arena-tag"; +import { WeakenMoveTypeTag } from "../arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import { AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, @@ -75,11 +74,11 @@ import { PokemonMultiHitModifier, PreserveBerryModifier, } from "../../modifier/modifier"; -import type { BattlerIndex } from "../../battle"; +import type { BattlerIndex } from "#enums/battler-index"; import { BattleType } from "#enums/battle-type"; import { TerrainType } from "../terrain"; import { ModifierPoolType } from "#app/modifier/modifier-type"; -import { Command } from "../../ui/command-ui-handler"; +import { Command } from "#enums/command"; import i18next from "i18next"; import type { Localizable } from "#app/@types/locales"; import { getBerryEffectFunc } from "../berry"; @@ -105,9 +104,10 @@ import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { SwitchPhase } from "#app/phases/switch-phase"; import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; -import { SpeciesFormChangeRevertWeatherFormTrigger } from "../pokemon-forms"; +import { SpeciesFormChangeRevertWeatherFormTrigger } from "../pokemon-forms/form-change-triggers"; import type { GameMode } from "#app/game-mode"; -import { applyChallenges, ChallengeType } from "../challenge"; +import { applyChallenges } from "../challenge"; +import { ChallengeType } from "#enums/challenge-type"; import { SwitchType } from "#enums/switch-type"; import { StatusEffect } from "#enums/status-effect"; import { globalScene } from "#app/global-scene"; @@ -123,11 +123,14 @@ import { MoveEffectTrigger } from "#enums/MoveEffectTrigger"; import { MultiHitType } from "#enums/MultiHitType"; import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves } from "./invalid-moves"; import { SelectBiomePhase } from "#app/phases/select-biome-phase"; +import { ChargingMove, MoveAttrMap, MoveAttrString, MoveKindString, MoveClassMap } from "#app/@types/move-types"; +import { applyMoveAttrs } from "./apply-attrs"; +import { frenzyMissFunc, getMoveTargets } from "./move-utils"; type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; -type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean; +export type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean; -export default class Move implements Localizable { +export default abstract class Move implements Localizable { public id: MoveId; public name: string; private _type: PokemonType; @@ -147,6 +150,17 @@ export default class Move implements Localizable { private flags: number = 0; private nameAppend: string = ""; + /** + * Check if the move is of the given subclass without requiring `instanceof`. + * + * ⚠️ Does _not_ work for {@linkcode ChargingAttackMove} and {@linkcode ChargingSelfStatusMove} subclasses. For those, + * use {@linkcode isChargingMove} instead. + * + * @param moveKind - The string name of the move to check against + * @returns Whether this move is of the provided type. + */ + public abstract is(moveKind: K): this is MoveClassMap[K]; + constructor(id: MoveId, type: PokemonType, category: MoveCategory, defaultMoveTarget: MoveTarget, power: number, accuracy: number, pp: number, chance: number, priority: number, generation: number) { this.id = id; this._type = type; @@ -188,8 +202,12 @@ export default class Move implements Localizable { * @param attrType any attribute that extends {@linkcode MoveAttr} * @returns Array of attributes that match `attrType`, Empty Array if none match. */ - getAttrs(attrType: Constructor): T[] { - return this.attrs.filter((a): a is T => a instanceof attrType); + getAttrs(attrType: T): (MoveAttrMap[T])[] { + const targetAttr = MoveAttrs[attrType]; + if (!targetAttr) { + return []; + } + return this.attrs.filter((a): a is MoveAttrMap[T] => a instanceof targetAttr); } /** @@ -197,8 +215,13 @@ export default class Move implements Localizable { * @param attrType any attribute that extends {@linkcode MoveAttr} * @returns true if the move has attribute `attrType` */ - hasAttr(attrType: Constructor): boolean { - return this.attrs.some((attr) => attr instanceof attrType); + hasAttr(attrType: MoveAttrString): boolean { + const targetAttr = MoveAttrs[attrType]; + // Guard against invalid attrType + if (!targetAttr) { + return false; + } + return this.attrs.some((attr) => attr instanceof targetAttr); } /** @@ -768,14 +791,14 @@ export default class Move implements Localizable { calculateBattleAccuracy(user: Pokemon, target: Pokemon, simulated: boolean = false) { const moveAccuracy = new NumberHolder(this.accuracy); - applyMoveAttrs(VariableAccuracyAttr, user, target, this, moveAccuracy); + applyMoveAttrs("VariableAccuracyAttr", user, target, this, moveAccuracy); applyPreDefendAbAttrs(WonderSkinAbAttr, target, user, this, { value: false }, simulated, moveAccuracy); if (moveAccuracy.value === -1) { return moveAccuracy.value; } - const isOhko = this.hasAttr(OneHitKOAccuracyAttr); + const isOhko = this.hasAttr("OneHitKOAccuracyAttr"); if (!isOhko) { globalScene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy); @@ -815,7 +838,7 @@ export default class Move implements Localizable { applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, typeChangeHolder, typeChangeMovePowerMultiplier); const sourceTeraType = source.getTeraType(); - if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { + if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { power.value = 60; } @@ -847,9 +870,9 @@ export default class Move implements Localizable { power.value *= typeBoost.boostValue; } - applyMoveAttrs(VariablePowerAttr, source, target, this, power); + applyMoveAttrs("VariablePowerAttr", source, target, this, power); - if (!this.hasAttr(TypelessAttr)) { + if (!this.hasAttr("TypelessAttr")) { globalScene.arena.applyTags(WeakenMoveTypeTag, simulated, typeChangeHolder.value, power); globalScene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, typeChangeHolder.value, power); } @@ -864,7 +887,7 @@ export default class Move implements Localizable { getPriority(user: Pokemon, simulated: boolean = true) { const priority = new NumberHolder(this.priority); - applyMoveAttrs(IncrementMovePriorityAttr, user, null, this, priority); + applyMoveAttrs("IncrementMovePriorityAttr", user, null, this, priority); applyAbAttrs(ChangeMovePriorityAbAttr, user, null, simulated, this, priority); return priority.value; @@ -885,7 +908,7 @@ export default class Move implements Localizable { } else if (this.id === MoveId.TRIPLE_KICK) { effectivePower = 47.07; } else { - const multiHitAttr = this.getAttrs(MultiHitAttr)[0]; + const multiHitAttr = this.getAttrs("MultiHitAttr")[0]; if (multiHitAttr) { effectivePower = multiHitAttr.calculateExpectedHitCount(this) * this.power; } else { @@ -898,10 +921,10 @@ export default class Move implements Localizable { // These are intentionally not else-if statements even though there are no // pokemon moves that have more than one of these attributes. This allows // the function to future proof new moves / custom move behaviors. - if (this.hasAttr(DelayedAttackAttr)) { + if (this.hasAttr("DelayedAttackAttr")) { numTurns += 2; } - if (this.hasAttr(RechargeAttr)) { + if (this.hasAttr("RechargeAttr")) { numTurns += 1; } if (this.isChargingMove()) { @@ -927,10 +950,10 @@ export default class Move implements Localizable { const isMultiTarget = multiple && targets.length > 1; // ...cannot enhance multi-hit or sacrificial moves - const exceptAttrs: Constructor[] = [ - MultiHitAttr, - SacrificialAttr, - SacrificialAttrOnHit + const exceptAttrs: MoveAttrString[] = [ + "MultiHitAttr", + "SacrificialAttr", + "SacrificialAttrOnHit" ]; // ...and cannot enhance these specific moves @@ -956,6 +979,13 @@ export default class Move implements Localizable { } export class AttackMove extends Move { + /** This field does not exist at runtime and must not be used. + * Its sole purpose is to ensure that typescript is able to properly narrow when the `is` method is called. + */ + declare private _: never; + override is(moveKind: K): this is MoveClassMap[K] { + return moveKind === "AttackMove"; + } constructor(id: MoveId, type: PokemonType, category: MoveCategory, power: number, accuracy: number, pp: number, chance: number, priority: number, generation: number) { super(id, type, category, MoveTarget.NEAR_OTHER, power, accuracy, pp, chance, priority, generation); @@ -985,7 +1015,7 @@ export class AttackMove extends Move { const [ thisStat, offStat ]: EffectiveStat[] = this.category === MoveCategory.PHYSICAL ? [ Stat.ATK, Stat.SPATK ] : [ Stat.SPATK, Stat.ATK ]; const statHolder = new NumberHolder(user.getEffectiveStat(thisStat, target)); const offStatValue = user.getEffectiveStat(offStat, target); - applyMoveAttrs(VariableAtkAttr, user, target, move, statHolder); + applyMoveAttrs("VariableAtkAttr", user, target, move, statHolder); const statRatio = offStatValue / statHolder.value; if (statRatio <= 0.75) { attackScore *= 2; @@ -994,7 +1024,7 @@ export class AttackMove extends Move { } const power = new NumberHolder(this.calculateEffectivePower()); - applyMoveAttrs(VariablePowerAttr, user, target, move, power); + applyMoveAttrs("VariablePowerAttr", user, target, move, power); attackScore += Math.floor(power.value / 5); @@ -1003,20 +1033,42 @@ export class AttackMove extends Move { } export class StatusMove extends Move { + /** This field does not exist at runtime and must not be used. + * Its sole purpose is to ensure that typescript is able to properly narrow when the `is` method is called. + */ + declare private _: never; constructor(id: MoveId, type: PokemonType, accuracy: number, pp: number, chance: number, priority: number, generation: number) { super(id, type, MoveCategory.STATUS, MoveTarget.NEAR_OTHER, -1, accuracy, pp, chance, priority, generation); } + + override is(moveKind: K): this is MoveClassMap[K] { + return moveKind === "StatusMove"; + } } export class SelfStatusMove extends Move { + /** This field does not exist at runtime and must not be used. + * Its sole purpose is to ensure that typescript is able to properly narrow when the `is` method is called. + */ + declare private _: never; constructor(id: MoveId, type: PokemonType, accuracy: number, pp: number, chance: number, priority: number, generation: number) { super(id, type, MoveCategory.STATUS, MoveTarget.USER, -1, accuracy, pp, chance, priority, generation); } + + override is(moveKind: K): this is MoveClassMap[K] { + return moveKind === "SelfStatusMove"; + } } -type SubMove = new (...args: any[]) => Move; +// TODO: Figure out how to improve the signature of this so that +// the `ChargeMove` function knows that the argument `Base` is a specific subclass of move that cannot +// be abstract. +// Right now, I only know how to do this by using the type conjunction (the & operators) +type SubMove = new (...args: any[]) => Move & { + is(moveKind: K): this is MoveClassMap[K]; +}; -function ChargeMove(Base: TBase) { +function ChargeMove(Base: TBase, nameAppend: string) { return class extends Base { /** The animation to play during the move's charging phase */ public readonly chargeAnim: ChargeAnim = ChargeAnim[`${MoveId[this.id]}_CHARGING`]; @@ -1060,8 +1112,12 @@ function ChargeMove(Base: TBase) { * @returns Array of attributes that match `attrType`, or an empty array if * no matches are found. */ - getChargeAttrs(attrType: Constructor): T[] { - return this.chargeAttrs.filter((attr): attr is T => attr instanceof attrType); + getChargeAttrs(attrType: T): (MoveAttrMap[T])[] { + const targetAttr = MoveAttrs[attrType]; + if (!targetAttr) { + return []; + } + return this.chargeAttrs.filter((attr): attr is MoveAttrMap[T] => attr instanceof targetAttr); } /** @@ -1069,8 +1125,12 @@ function ChargeMove(Base: TBase) { * @param attrType any attribute that extends {@linkcode MoveAttr} * @returns `true` if a matching attribute is found; `false` otherwise */ - hasChargeAttr(attrType: Constructor): boolean { - return this.chargeAttrs.some((attr) => attr instanceof attrType); + hasChargeAttr(attrType: T): boolean { + const targetAttr = MoveAttrs[attrType]; + if (!targetAttr) { + return false; + } + return this.chargeAttrs.some((attr) => attr instanceof targetAttr); } /** @@ -1088,10 +1148,8 @@ function ChargeMove(Base: TBase) { }; } -export class ChargingAttackMove extends ChargeMove(AttackMove) {} -export class ChargingSelfStatusMove extends ChargeMove(SelfStatusMove) {} - -export type ChargingMove = ChargingAttackMove | ChargingSelfStatusMove; +export class ChargingAttackMove extends ChargeMove(AttackMove, "ChargingAttackMove") {} +export class ChargingSelfStatusMove extends ChargeMove(SelfStatusMove, "ChargingSelfStatusMove") {} /** * Base class defining all {@linkcode Move} Attributes @@ -1102,6 +1160,22 @@ export abstract class MoveAttr { /** Should this {@linkcode Move} target the user? */ public selfTarget: boolean; + /** + * Return whether this attribute is of the given type. + * + * @remarks + * Used to avoid requring the caller to have imported the specific attribute type, avoiding circular dependencies. + * @param attr - The attribute to check against + * @returns Whether the attribute is an instance of the given type. + */ + public is(attr: T): this is MoveAttrMap[T] { + const targetAttr = MoveAttrs[attr]; + if (!targetAttr) { + return false; + } + return this instanceof targetAttr; + } + constructor(selfTarget: boolean = false) { this.selfTarget = selfTarget; } @@ -1268,7 +1342,7 @@ export class MoveEffectAttr extends MoveAttr { applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, !showAbility, moveChance, move); - if ((!move.hasAttr(FlinchAttr) || moveChance.value <= move.chance) && !move.hasAttr(SecretPowerAttr)) { + if ((!move.hasAttr("FlinchAttr") || moveChance.value <= move.chance) && !move.hasAttr("SecretPowerAttr")) { const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; globalScene.arena.applyTagsForSide(ArenaTagType.WATER_FIRE_PLEDGE, userSide, false, moveChance); } @@ -2331,7 +2405,7 @@ export class MultiHitAttr extends MoveAttr { */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const hitType = new NumberHolder(this.intrinsicMultiHitType); - applyMoveAttrs(ChangeMultiHitTypeAttr, user, target, move, hitType); + applyMoveAttrs("ChangeMultiHitTypeAttr", user, target, move, hitType); this.multiHitType = hitType.value; (args[0] as NumberHolder).value = this.getHitCount(user, target); @@ -3032,7 +3106,11 @@ export class WeatherInstantChargeAttr extends InstantChargeAttr { } export class OverrideMoveEffectAttr extends MoveAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + /** This field does not exist at runtime and must not be used. + * Its sole purpose is to ensure that typescript is able to properly narrow when the `is` method is called. + */ + declare private _: never; + override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { return true; } } @@ -3114,7 +3192,7 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr { const allyMovePhase = globalScene.phaseManager.findPhase((phase) => phase.is("MovePhase") && phase.pokemon.isPlayer() === user.isPlayer()); if (allyMovePhase) { 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); // "{userPokemonName} is waiting for {allyPokemonName}'s move..." @@ -3235,22 +3313,22 @@ export class StatStageChangeAttr extends MoveEffectAttr { switch (stat) { case Stat.ATK: if (this.selfTarget) { - noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL); + noEffect = !user.getMoveset().find(m => {const mv = m.getMove(); return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL;} ); } break; case Stat.DEF: if (!this.selfTarget) { - noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL); + noEffect = !user.getMoveset().find(m => {const mv = m.getMove(); return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL;} ); } break; case Stat.SPATK: if (this.selfTarget) { - noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL); + noEffect = !user.getMoveset().find(m => {const mv = m.getMove(); return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL;} ); } break; case Stat.SPDEF: if (!this.selfTarget) { - noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL); + noEffect = !user.getMoveset().find(m => {const mv = m.getMove(); return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL;} ); } break; } @@ -5410,7 +5488,7 @@ export class FrenzyAttr extends MoveEffectAttr { new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true })); user.addTag(BattlerTagType.FRENZY, turnCount, move.id, user.id); } else { - applyMoveAttrs(AddBattlerTagAttr, user, target, move, args); + applyMoveAttrs("AddBattlerTagAttr", user, target, move, args); user.lapseTag(BattlerTagType.FRENZY); // if FRENZY is already in effect (moveQueue.length > 0), lapse the tag } @@ -5418,15 +5496,6 @@ export class FrenzyAttr extends MoveEffectAttr { } } -export const frenzyMissFunc: UserMoveConditionFunc = (user: Pokemon, move: Move) => { - while (user.getMoveQueue().length && user.getMoveQueue()[0].move === move.id) { - user.getMoveQueue().shift(); - } - user.removeTag(BattlerTagType.FRENZY); // FRENZY tag should be disrupted on miss/no effect - - return true; -}; - /** * Attribute that grants {@link https://bulbapedia.bulbagarden.net/wiki/Semi-invulnerable_turn | semi-invulnerability} to the user during * the associated move's charging phase. Should only be used for {@linkcode ChargingMove | ChargingMoves} as a `chargeAttr`. @@ -5781,7 +5850,7 @@ export class ProtectAttr extends AddBattlerTagAttr { while (moveHistory.length) { turnMove = moveHistory.shift(); - if (!allMoves[turnMove?.move ?? MoveId.NONE].hasAttr(ProtectAttr) || turnMove?.result !== MoveResult.SUCCESS) { + if (!allMoves[turnMove?.move ?? MoveId.NONE].hasAttr("ProtectAttr") || turnMove?.result !== MoveResult.SUCCESS) { break; } timesUsed++; @@ -5912,7 +5981,7 @@ export class AddArenaTagAttr extends MoveEffectAttr { } if ((move.chance < 0 || move.chance === 100 || user.randBattleSeedInt(100) < move.chance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) { - const side = ((this.selfSideTarget ? user : target).isPlayer() !== (move.hasAttr(AddArenaTrapTagAttr) && target === user)) ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + const side = ((this.selfSideTarget ? user : target).isPlayer() !== (move.hasAttr("AddArenaTrapTagAttr") && target === user)) ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; globalScene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, side); return true; } @@ -6376,7 +6445,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { return (user, target, move) => { const switchOutTarget = (this.selfSwitch ? user : target); 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 (move.hitsSubstitute(user, target)) { @@ -7945,58 +8014,6 @@ const attackedByItemMessageFunc = (user: Pokemon, target: Pokemon, move: Move) = return message; }; -export type MoveAttrFilter = (attr: MoveAttr) => boolean; - -function applyMoveAttrsInternal( - attrFilter: MoveAttrFilter, - user: Pokemon | null, - target: Pokemon | null, - move: Move, - args: any[], -): void { - move.attrs.filter((attr) => attrFilter(attr)).forEach((attr) => attr.apply(user, target, move, args)); -} - -function applyMoveChargeAttrsInternal( - attrFilter: MoveAttrFilter, - user: Pokemon | null, - target: Pokemon | null, - move: ChargingMove, - args: any[], -): void { - move.chargeAttrs.filter((attr) => attrFilter(attr)).forEach((attr) => attr.apply(user, target, move, args)); -} - -export function applyMoveAttrs( - attrType: Constructor, - user: Pokemon | null, - target: Pokemon | null, - move: Move, - ...args: any[] -): void { - applyMoveAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args); -} - -export function applyFilteredMoveAttrs( - attrFilter: MoveAttrFilter, - user: Pokemon, - target: Pokemon | null, - move: Move, - ...args: any[] -): void { - applyMoveAttrsInternal(attrFilter, user, target, move, args); -} - -export function applyMoveChargeAttrs( - attrType: Constructor, - user: Pokemon | null, - target: Pokemon | null, - move: ChargingMove, - ...args: any[] -): void { - applyMoveChargeAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args); -} - export class MoveCondition { protected func: MoveConditionFunc; @@ -8175,73 +8192,232 @@ export type MoveTargetSet = { multiple: boolean; }; -export function getMoveTargets(user: Pokemon, move: MoveId, replaceTarget?: MoveTarget): MoveTargetSet { - const variableTarget = new NumberHolder(0); - user.getOpponents(false).forEach(p => applyMoveAttrs(VariableTargetAttr, user, p, allMoves[move], variableTarget)); +/** + * Map of Move attributes to their respective classes. Used for instanceof checks. + */ +const MoveAttrs = Object.freeze({ + MoveEffectAttr, + MoveHeaderAttr, + MessageHeaderAttr, + AddBattlerTagAttr, + AddBattlerTagHeaderAttr, + BeakBlastHeaderAttr, + PreMoveMessageAttr, + PreUseInterruptAttr, + RespectAttackTypeImmunityAttr, + IgnoreOpponentStatStagesAttr, + HighCritAttr, + CritOnlyAttr, + FixedDamageAttr, + UserHpDamageAttr, + TargetHalfHpDamageAttr, + MatchHpAttr, + CounterDamageAttr, + LevelDamageAttr, + RandomLevelDamageAttr, + ModifiedDamageAttr, + SurviveDamageAttr, + SplashAttr, + CelebrateAttr, + RecoilAttr, + SacrificialAttr, + SacrificialAttrOnHit, + HalfSacrificialAttr, + AddSubstituteAttr, + HealAttr, + PartyStatusCureAttr, + FlameBurstAttr, + SacrificialFullRestoreAttr, + IgnoreWeatherTypeDebuffAttr, + WeatherHealAttr, + PlantHealAttr, + SandHealAttr, + BoostHealAttr, + HealOnAllyAttr, + HitHealAttr, + IncrementMovePriorityAttr, + MultiHitAttr, + ChangeMultiHitTypeAttr, + WaterShurikenMultiHitTypeAttr, + StatusEffectAttr, + MultiStatusEffectAttr, + PsychoShiftEffectAttr, + StealHeldItemChanceAttr, + RemoveHeldItemAttr, + EatBerryAttr, + StealEatBerryAttr, + HealStatusEffectAttr, + BypassSleepAttr, + BypassBurnDamageReductionAttr, + WeatherChangeAttr, + ClearWeatherAttr, + TerrainChangeAttr, + ClearTerrainAttr, + OneHitKOAttr, + InstantChargeAttr, + WeatherInstantChargeAttr, + OverrideMoveEffectAttr, + DelayedAttackAttr, + AwaitCombinedPledgeAttr, + StatStageChangeAttr, + SecretPowerAttr, + PostVictoryStatStageChangeAttr, + AcupressureStatStageChangeAttr, + GrowthStatStageChangeAttr, + CutHpStatStageBoostAttr, + OrderUpStatBoostAttr, + CopyStatsAttr, + InvertStatsAttr, + ResetStatsAttr, + SwapStatStagesAttr, + HpSplitAttr, + VariablePowerAttr, + LessPPMorePowerAttr, + MovePowerMultiplierAttr, + BeatUpAttr, + DoublePowerChanceAttr, + ConsecutiveUsePowerMultiplierAttr, + ConsecutiveUseDoublePowerAttr, + ConsecutiveUseMultiBasePowerAttr, + WeightPowerAttr, + ElectroBallPowerAttr, + GyroBallPowerAttr, + LowHpPowerAttr, + CompareWeightPowerAttr, + HpPowerAttr, + OpponentHighHpPowerAttr, + FirstAttackDoublePowerAttr, + TurnDamagedDoublePowerAttr, + MagnitudePowerAttr, + AntiSunlightPowerDecreaseAttr, + FriendshipPowerAttr, + RageFistPowerAttr, + PositiveStatStagePowerAttr, + PunishmentPowerAttr, + PresentPowerAttr, + WaterShurikenPowerAttr, + SpitUpPowerAttr, + SwallowHealAttr, + MultiHitPowerIncrementAttr, + LastMoveDoublePowerAttr, + CombinedPledgePowerAttr, + CombinedPledgeStabBoostAttr, + RoundPowerAttr, + CueNextRoundAttr, + StatChangeBeforeDmgCalcAttr, + SpectralThiefAttr, + VariableAtkAttr, + TargetAtkUserAtkAttr, + DefAtkAttr, + VariableDefAttr, + DefDefAttr, + VariableAccuracyAttr, + ThunderAccuracyAttr, + StormAccuracyAttr, + AlwaysHitMinimizeAttr, + ToxicAccuracyAttr, + BlizzardAccuracyAttr, + VariableMoveCategoryAttr, + PhotonGeyserCategoryAttr, + TeraMoveCategoryAttr, + TeraBlastPowerAttr, + StatusCategoryOnAllyAttr, + ShellSideArmCategoryAttr, + VariableMoveTypeAttr, + FormChangeItemTypeAttr, + TechnoBlastTypeAttr, + AuraWheelTypeAttr, + RagingBullTypeAttr, + IvyCudgelTypeAttr, + WeatherBallTypeAttr, + TerrainPulseTypeAttr, + HiddenPowerTypeAttr, + TeraBlastTypeAttr, + TeraStarstormTypeAttr, + MatchUserTypeAttr, + CombinedPledgeTypeAttr, + VariableMoveTypeMultiplierAttr, + NeutralDamageAgainstFlyingTypeMultiplierAttr, + IceNoEffectTypeAttr, + FlyingTypeMultiplierAttr, + VariableMoveTypeChartAttr, + FreezeDryAttr, + OneHitKOAccuracyAttr, + SheerColdAccuracyAttr, + MissEffectAttr, + NoEffectAttr, + TypelessAttr, + BypassRedirectAttr, + FrenzyAttr, + SemiInvulnerableAttr, + LeechSeedAttr, + FallDownAttr, + GulpMissileTagAttr, + JawLockAttr, + CurseAttr, + LapseBattlerTagAttr, + RemoveBattlerTagAttr, + FlinchAttr, + ConfuseAttr, + RechargeAttr, + TrapAttr, + ProtectAttr, + IgnoreAccuracyAttr, + FaintCountdownAttr, + RemoveAllSubstitutesAttr, + HitsTagAttr, + HitsTagForDoubleDamageAttr, + AddArenaTagAttr, + RemoveArenaTagsAttr, + AddArenaTrapTagAttr, + AddArenaTrapTagHitAttr, + RemoveArenaTrapAttr, + RemoveScreensAttr, + SwapArenaTagsAttr, + AddPledgeEffectAttr, + RevivalBlessingAttr, + ForceSwitchOutAttr, + ChillyReceptionAttr, + RemoveTypeAttr, + CopyTypeAttr, + CopyBiomeTypeAttr, + ChangeTypeAttr, + AddTypeAttr, + FirstMoveTypeAttr, + CallMoveAttr, + RandomMoveAttr, + RandomMovesetMoveAttr, + NaturePowerAttr, + CopyMoveAttr, + RepeatMoveAttr, + ReducePpMoveAttr, + AttackReducePpMoveAttr, + MovesetCopyMoveAttr, + SketchAttr, + AbilityChangeAttr, + AbilityCopyAttr, + AbilityGiveAttr, + SwitchAbilitiesAttr, + SuppressAbilitiesAttr, + TransformAttr, + SwapStatAttr, + ShiftStatAttr, + AverageStatsAttr, + MoneyAttr, + DestinyBondAttr, + AddBattlerTagIfBoostedAttr, + StatusIfBoostedAttr, + LastResortAttr, + VariableTargetAttr, + AfterYouAttr, + ForceLastAttr, + HitsSameTypeAttr, + ResistLastMoveTypeAttr, + ExposedMoveAttr, +}); - let moveTarget: MoveTarget | undefined; - if (allMoves[move].hasAttr(VariableTargetAttr)) { - moveTarget = variableTarget.value; - } else if (replaceTarget !== undefined) { - moveTarget = replaceTarget; - } else if (move) { - moveTarget = allMoves[move].moveTarget; - } else if (move === undefined) { - moveTarget = MoveTarget.NEAR_ENEMY; - } - const opponents = user.getOpponents(false); - - let set: Pokemon[] = []; - let multiple = false; - const ally: Pokemon | undefined = user.getAlly(); - - switch (moveTarget) { - case MoveTarget.USER: - case MoveTarget.PARTY: - set = [ user ]; - break; - case MoveTarget.NEAR_OTHER: - case MoveTarget.OTHER: - case MoveTarget.ALL_NEAR_OTHERS: - case MoveTarget.ALL_OTHERS: - set = !isNullOrUndefined(ally) ? (opponents.concat([ ally ])) : opponents; - multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS; - break; - case MoveTarget.NEAR_ENEMY: - case MoveTarget.ALL_NEAR_ENEMIES: - case MoveTarget.ALL_ENEMIES: - case MoveTarget.ENEMY_SIDE: - set = opponents; - multiple = moveTarget !== MoveTarget.NEAR_ENEMY; - break; - case MoveTarget.RANDOM_NEAR_ENEMY: - set = [ opponents[user.randBattleSeedInt(opponents.length)] ]; - break; - case MoveTarget.ATTACKER: - return { targets: [ -1 as BattlerIndex ], multiple: false }; - case MoveTarget.NEAR_ALLY: - case MoveTarget.ALLY: - set = !isNullOrUndefined(ally) ? [ ally ] : []; - break; - case MoveTarget.USER_OR_NEAR_ALLY: - case MoveTarget.USER_AND_ALLIES: - case MoveTarget.USER_SIDE: - set = !isNullOrUndefined(ally) ? [ user, ally ] : [ user ]; - multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY; - break; - case MoveTarget.ALL: - case MoveTarget.BOTH_SIDES: - set = (!isNullOrUndefined(ally) ? [ user, ally ] : [ user ]).concat(opponents); - multiple = true; - break; - case MoveTarget.CURSE: - const extraTargets = !isNullOrUndefined(ally) ? [ ally ] : []; - set = user.getTypes(true).includes(PokemonType.GHOST) ? (opponents.concat(extraTargets)) : [ user ]; - break; - } - - return { targets: set.filter(p => p?.isActive(true)).map(p => p.getBattlerIndex()).filter(t => t !== undefined), multiple }; -} +/** Map of of move attribute names to their constructors */ +export type MoveAttrConstructorMap = typeof MoveAttrs; export const selfStatLowerMoves: MoveId[] = []; @@ -11255,7 +11431,7 @@ export function initMoves() { .attr(StatusEffectAttr, StatusEffect.TOXIC) ); allMoves.map(m => { - if (m.getAttrs(StatStageChangeAttr).some(a => a.selfTarget && a.stages < 0)) { + if (m.getAttrs("StatStageChangeAttr").some(a => a.selfTarget && a.stages < 0)) { selfStatLowerMoves.push(m.id); } }); diff --git a/src/data/moves/pokemon-move.ts b/src/data/moves/pokemon-move.ts new file mode 100644 index 00000000000..da46caa819f --- /dev/null +++ b/src/data/moves/pokemon-move.ts @@ -0,0 +1,93 @@ +import type Pokemon from "#app/field/pokemon"; +import { toDmgValue } from "#app/utils/common"; +import type { MoveId } from "#enums/move-id"; +import { allMoves } from "../data-lists"; +import type Move from "./move"; + +/** + * Wrapper class for the {@linkcode Move} class for Pokemon to interact with. + * These are the moves assigned to a {@linkcode Pokemon} object. + * It links to {@linkcode Move} class via the move ID. + * Compared to {@linkcode Move}, this class also tracks things like + * PP Ups recieved, PP used, etc. + * @see {@linkcode isUsable} - checks if move is restricted, out of PP, or not implemented. + * @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID. + * @see {@linkcode usePp} - removes a point of PP from the move. + * @see {@linkcode getMovePp} - returns amount of PP a move currently has. + * @see {@linkcode getPpRatio} - returns the current PP amount / max PP amount. + * @see {@linkcode getName} - returns name of {@linkcode Move}. + **/ +export class PokemonMove { + public moveId: MoveId; + public ppUsed: number; + public ppUp: number; + public virtual: boolean; + + /** + * If defined and nonzero, overrides the maximum PP of the move (e.g., due to move being copied by Transform). + * This also nullifies all effects of `ppUp`. + */ + public maxPpOverride?: number; + + constructor(moveId: MoveId, ppUsed = 0, ppUp = 0, virtual = false, maxPpOverride?: number) { + this.moveId = moveId; + this.ppUsed = ppUsed; + this.ppUp = ppUp; + this.virtual = virtual; + this.maxPpOverride = maxPpOverride; + } + + /** + * Checks whether the move can be selected or performed by a Pokemon, without consideration for the move's targets. + * The move is unusable if it is out of PP, restricted by an effect, or unimplemented. + * + * @param pokemon - {@linkcode Pokemon} that would be using this move + * @param ignorePp - If `true`, skips the PP check + * @param ignoreRestrictionTags - If `true`, skips the check for move restriction tags (see {@link MoveRestrictionBattlerTag}) + * @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`. + */ + isUsable(pokemon: Pokemon, ignorePp = false, ignoreRestrictionTags = false): boolean { + if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon)) { + return false; + } + + if (this.getMove().name.endsWith(" (N)")) { + return false; + } + + return ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1; + } + + getMove(): Move { + return allMoves[this.moveId]; + } + + /** + * Sets {@link ppUsed} for this move and ensures the value does not exceed {@link getMovePp} + * @param count Amount of PP to use + */ + usePp(count = 1) { + this.ppUsed = Math.min(this.ppUsed + count, this.getMovePp()); + } + + getMovePp(): number { + return this.maxPpOverride || this.getMove().pp + this.ppUp * toDmgValue(this.getMove().pp / 5); + } + + getPpRatio(): number { + return 1 - this.ppUsed / this.getMovePp(); + } + + getName(): string { + return this.getMove().name; + } + + /** + * Copies an existing move or creates a valid {@linkcode PokemonMove} object from json representing one + * @param source The data for the move to copy; can be a {@linkcode PokemonMove} or JSON object representing one + * @returns A valid {@linkcode PokemonMove} object + */ + static loadMove(source: PokemonMove | any): PokemonMove { + return new PokemonMove(source.moveId, source.ppUsed, source.ppUp, source.virtual, source.maxPpOverride); + } +} diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index 82fbc0efd69..2213dc4afaa 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -7,7 +7,8 @@ import { transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type Pokemon from "#app/field/pokemon"; -import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; +import { EnemyPokemon } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; @@ -25,7 +26,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { MoveId } from "#enums/move-id"; import { BattlerTagType } from "#enums/battler-tag-type"; import { randInt } from "#app/utils/common"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { applyModifierTypeToPlayerPokemon, catchPokemon, diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index 938a136bced..c318efc0cb3 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -24,7 +24,7 @@ import { TrainerType } from "#enums/trainer-type"; import { SpeciesId } from "#enums/species-id"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { getEncounterText, showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MoveId } from "#enums/move-id"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index 19c4948aeab..a81dcc841b7 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -37,11 +37,11 @@ import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler"; import type { PlayerPokemon } from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { Ability } from "#app/data/abilities/ability-class"; import { BerryModifier } from "#app/modifier/modifier"; import { BerryType } from "#enums/berry-type"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { MoveId } from "#enums/move-id"; import { EncounterBattleAnim } from "#app/data/battle-anims"; import { MoveCategory } from "#enums/MoveCategory"; diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index 80465e1d20c..a74980919d6 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { globalScene } from "#app/global-scene"; import { EncounterBattleAnim } from "#app/data/battle-anims"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; @@ -23,7 +23,8 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { TrainerSlot } from "#enums/trainer-slot"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; +import { EnemyPokemon } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { modifierTypes } from "#app/modifier/modifier-type"; import PokemonData from "#app/system/pokemon-data"; diff --git a/src/data/mystery-encounters/encounters/field-trip-encounter.ts b/src/data/mystery-encounters/encounters/field-trip-encounter.ts index 2cd6123838b..82fbfd51eec 100644 --- a/src/data/mystery-encounters/encounters/field-trip-encounter.ts +++ b/src/data/mystery-encounters/encounters/field-trip-encounter.ts @@ -7,7 +7,8 @@ import { setEncounterExp, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; import { modifierTypes } from "#app/modifier/modifier-type"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 8b0e5a08020..d42778cb17c 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -24,9 +24,9 @@ import { SpeciesId } from "#enums/species-id"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Gender } from "#app/data/gender"; import { PokemonType } from "#enums/pokemon-type"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { MoveId } from "#enums/move-id"; import { EncounterBattleAnim } from "#app/data/battle-anims"; import { WeatherType } from "#enums/weather-type"; diff --git a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts index a52641f857d..456562ca70e 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -13,7 +13,7 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst import { TrainerSlot } from "#enums/trainer-slot"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { FieldPosition } from "#app/field/pokemon"; +import { FieldPosition } from "#enums/field-position"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; @@ -25,7 +25,7 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { PlayerGender } from "#enums/player-gender"; import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball"; import { addPokeballOpenParticles } from "#app/field/anims"; -import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { modifierTypes } from "#app/modifier/modifier-type"; import { Nature } from "#enums/nature"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 6ecce46ae24..a84b0af30ed 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -33,7 +33,8 @@ import { } from "#app/utils/common"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; +import { EnemyPokemon } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { HiddenAbilityRateBoosterModifier, diff --git a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts index 009639291de..8eea0623cd4 100644 --- a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts +++ b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts @@ -11,7 +11,7 @@ import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encount import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; const OPTION_1_REQUIRED_MOVE = MoveId.SURF; const OPTION_2_REQUIRED_MOVE = MoveId.FLY; diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index bb1529e3695..1ecd73143fc 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -21,8 +21,9 @@ import { import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { Nature } from "#enums/nature"; import { MoveId } from "#enums/move-id"; -import { BattlerIndex } from "#app/battle"; -import { AiType, PokemonMove } from "#app/field/pokemon"; +import { BattlerIndex } from "#enums/battler-index"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { AiType } from "#enums/ai-type"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index be347fb0035..a3eb78f479e 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -17,11 +17,11 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { SpeciesId } from "#enums/species-id"; import { Nature } from "#enums/nature"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { MoveId } from "#enums/move-id"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index 4b17260098f..91754629821 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -23,7 +23,7 @@ import { Nature } from "#enums/nature"; import { PokemonType } from "#enums/pokemon-type"; import { BerryType } from "#enums/berry-type"; import { Stat } from "#enums/stat"; -import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/abilities/ability"; import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index b17a39ae694..9ab91f439bf 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -12,7 +12,7 @@ import { speciesStarterCosts } from "#app/data/balance/starters"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; -import { AbilityAttr } from "#app/system/game-data"; +import { AbilityAttr } from "#enums/ability-attr"; import PokemonData from "#app/system/pokemon-data"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { isNullOrUndefined, randSeedShuffle } from "#app/utils/common"; diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index a0051058d02..8bcd024de5c 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -24,8 +24,8 @@ import i18next from "#app/plugins/i18n"; import { ModifierTier } from "#app/modifier/modifier-tier"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { MoveId } from "#enums/move-id"; -import { BattlerIndex } from "#app/battle"; -import { PokemonMove } from "#app/field/pokemon"; +import { BattlerIndex } from "#enums/battler-index"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { randSeedInt } from "#app/utils/common"; diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index 411ecdec080..c48f93a9a9d 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -10,7 +10,7 @@ import { import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import type Pokemon from "#app/field/pokemon"; import type { EnemyPokemon } from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; @@ -29,8 +29,7 @@ import { import PokemonData from "#app/system/pokemon-data"; import { isNullOrUndefined, randSeedInt } from "#app/utils/common"; import type { MoveId } from "#enums/move-id"; -import { BattlerIndex } from "#app/battle"; -import { SelfStatusMove } from "#app/data/moves/move"; +import { BattlerIndex } from "#enums/battler-index"; import { PokeballType } from "#enums/pokeball"; import { BattlerTagType } from "#enums/battler-tag-type"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; @@ -175,7 +174,7 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder. // Check what type of move the egg move is to determine target const pokemonMove = new PokemonMove(eggMove); const move = pokemonMove.getMove(); - const target = move instanceof SelfStatusMove ? BattlerIndex.ENEMY : BattlerIndex.PLAYER; + const target = move.is("SelfStatusMove") ? BattlerIndex.ENEMY : BattlerIndex.PLAYER; encounter.startOfBattleEffects.push({ sourceBattlerIndex: BattlerIndex.ENEMY, diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 2b1f775b78e..fe1911c9007 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -16,7 +16,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { NumberHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils/common"; import type PokemonSpecies from "#app/data/pokemon-species"; import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index 88d9ba402a9..bca34be723b 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -2,7 +2,9 @@ import { globalScene } from "#app/global-scene"; import { allAbilities } from "../data-lists"; import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { Nature } from "#enums/nature"; -import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; +import { pokemonFormChanges } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeItemTrigger } from "../pokemon-forms/form-change-triggers"; +import { FormChangeItem } from "#enums/form-change-item"; import { StatusEffect } from "#enums/status-effect"; import { PokemonType } from "#enums/pokemon-type"; import { WeatherType } from "#enums/weather-type"; diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index e305252ed0f..6510e32fe76 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -1,5 +1,6 @@ import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type { PokemonMove } from "../moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils/common"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; @@ -20,11 +21,11 @@ import { StatusEffectRequirement, WaveRangeRequirement, } from "./mystery-encounter-requirements"; -import type { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#enums/battler-index"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import type { GameModes } from "#app/game-mode"; +import type { GameModes } from "#enums/game-modes"; import type { EncounterAnim } from "#enums/encounter-anims"; import type { Challenges } from "#enums/challenges"; import { globalScene } from "#app/global-scene"; diff --git a/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts b/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts index 7698be7b15d..c0e45d5401c 100644 --- a/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts +++ b/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts @@ -1,6 +1,6 @@ import type { MoveId } from "#enums/move-id"; import type { PlayerPokemon } from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { isNullOrUndefined } from "#app/utils/common"; import { EncounterPokemonRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { globalScene } from "#app/global-scene"; diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index b86cbaa18c9..69984229681 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -1,5 +1,5 @@ import type Battle from "#app/battle"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { BattleType } from "#enums/battle-type"; import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes"; import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; @@ -8,9 +8,12 @@ import { WEIGHT_INCREMENT_ON_SPAWN_MISS, } from "#app/data/mystery-encounters/mystery-encounters"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import type { AiType, PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type { AiType } from "#enums/ai-type"; import type Pokemon from "#app/field/pokemon"; -import { EnemyPokemon, FieldPosition, PokemonMove } from "#app/field/pokemon"; +import { EnemyPokemon } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { FieldPosition } from "#enums/field-position"; import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type"; import { getPartyLuckValue, @@ -30,7 +33,8 @@ import type { BattlerTagType } from "#enums/battler-tag-type"; import { BiomeId } from "#enums/biome-id"; import type { TrainerType } from "#enums/trainer-type"; import i18next from "i18next"; -import Trainer, { TrainerVariant } from "#app/field/trainer"; +import Trainer from "#app/field/trainer"; +import { TrainerVariant } from "#enums/trainer-variant"; import type { Gender } from "#app/data/gender"; import type { Nature } from "#enums/nature"; import type { MoveId } from "#enums/move-id"; diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index 47eb355c8b6..ea129454034 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -1,139 +1,30 @@ -import { PokemonFormChangeItemModifier } from "../modifier/modifier"; import type Pokemon from "../field/pokemon"; -import { StatusEffect } from "#enums/status-effect"; import { allMoves } from "./data-lists"; import { MoveCategory } from "#enums/MoveCategory"; import type { Constructor, nil } from "#app/utils/common"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; -import type { TimeOfDay } from "#enums/time-of-day"; -import { getPokemonNameWithAffix } from "#app/messages"; -import i18next from "i18next"; import { WeatherType } from "#enums/weather-type"; -import { Challenges } from "#app/enums/challenges"; import { SpeciesFormKey } from "#enums/species-form-key"; import { globalScene } from "#app/global-scene"; - -export enum FormChangeItem { - NONE, - - ABOMASITE, - ABSOLITE, - AERODACTYLITE, - AGGRONITE, - ALAKAZITE, - ALTARIANITE, - AMPHAROSITE, - AUDINITE, - BANETTITE, - BEEDRILLITE, - BLASTOISINITE, - BLAZIKENITE, - CAMERUPTITE, - CHARIZARDITE_X, - CHARIZARDITE_Y, - DIANCITE, - GALLADITE, - GARCHOMPITE, - GARDEVOIRITE, - GENGARITE, - GLALITITE, - GYARADOSITE, - HERACRONITE, - HOUNDOOMINITE, - KANGASKHANITE, - LATIASITE, - LATIOSITE, - LOPUNNITE, - LUCARIONITE, - MANECTITE, - MAWILITE, - MEDICHAMITE, - METAGROSSITE, - MEWTWONITE_X, - MEWTWONITE_Y, - PIDGEOTITE, - PINSIRITE, - RAYQUAZITE, - SABLENITE, - SALAMENCITE, - SCEPTILITE, - SCIZORITE, - SHARPEDONITE, - SLOWBRONITE, - STEELIXITE, - SWAMPERTITE, - TYRANITARITE, - VENUSAURITE, - - BLUE_ORB = 50, - RED_ORB, - ADAMANT_CRYSTAL, - LUSTROUS_GLOBE, - GRISEOUS_CORE, - REVEAL_GLASS, - MAX_MUSHROOMS, - DARK_STONE, - LIGHT_STONE, - PRISON_BOTTLE, - RUSTED_SWORD, - RUSTED_SHIELD, - ICY_REINS_OF_UNITY, - SHADOW_REINS_OF_UNITY, - ULTRANECROZIUM_Z, - - SHARP_METEORITE = 100, - HARD_METEORITE, - SMOOTH_METEORITE, - GRACIDEA, - SHOCK_DRIVE, - BURN_DRIVE, - CHILL_DRIVE, - DOUSE_DRIVE, - N_SOLARIZER, - N_LUNARIZER, - WELLSPRING_MASK, - HEARTHFLAME_MASK, - CORNERSTONE_MASK, - FIST_PLATE, - SKY_PLATE, - TOXIC_PLATE, - EARTH_PLATE, - STONE_PLATE, - INSECT_PLATE, - SPOOKY_PLATE, - IRON_PLATE, - FLAME_PLATE, - SPLASH_PLATE, - MEADOW_PLATE, - ZAP_PLATE, - MIND_PLATE, - ICICLE_PLATE, - DRACO_PLATE, - DREAD_PLATE, - PIXIE_PLATE, - BLANK_PLATE, // TODO: Find a potential use for this - LEGEND_PLATE, // TODO: Find a potential use for this - FIGHTING_MEMORY, - FLYING_MEMORY, - POISON_MEMORY, - GROUND_MEMORY, - ROCK_MEMORY, - BUG_MEMORY, - GHOST_MEMORY, - STEEL_MEMORY, - FIRE_MEMORY, - WATER_MEMORY, - GRASS_MEMORY, - ELECTRIC_MEMORY, - PSYCHIC_MEMORY, - ICE_MEMORY, - DRAGON_MEMORY, - DARK_MEMORY, - FAIRY_MEMORY, - NORMAL_MEMORY, // TODO: Find a potential use for this -} +import { FormChangeItem } from "#enums/form-change-item"; +import { + MeloettaFormChangePostMoveTrigger, + SpeciesDefaultFormMatchTrigger, + SpeciesFormChangeAbilityTrigger, + SpeciesFormChangeActiveTrigger, + SpeciesFormChangeCompoundTrigger, + SpeciesFormChangeItemTrigger, + SpeciesFormChangeLapseTeraTrigger, + SpeciesFormChangeManualTrigger, + SpeciesFormChangeMoveLearnedTrigger, + SpeciesFormChangePreMoveTrigger, + SpeciesFormChangeRevertWeatherFormTrigger, + SpeciesFormChangeTeraTrigger, + type SpeciesFormChangeTrigger, + SpeciesFormChangeWeatherTrigger, +} from "./pokemon-forms/form-change-triggers"; export type SpeciesFormChangeConditionPredicate = (p: Pokemon) => boolean; export type SpeciesFormChangeConditionEnforceFunc = (p: Pokemon) => void; @@ -214,347 +105,6 @@ export class SpeciesFormChangeCondition { } } -export abstract class SpeciesFormChangeTrigger { - public description = ""; - - canChange(_pokemon: Pokemon): boolean { - return true; - } - - hasTriggerType(triggerType: Constructor): boolean { - return this instanceof triggerType; - } -} - -export class SpeciesFormChangeManualTrigger extends SpeciesFormChangeTrigger {} - -export class SpeciesFormChangeAbilityTrigger extends SpeciesFormChangeTrigger { - public description: string = i18next.t("pokemonEvolutions:Forms.ability"); -} - -export class SpeciesFormChangeCompoundTrigger { - public description = ""; - public triggers: SpeciesFormChangeTrigger[]; - - constructor(...triggers: SpeciesFormChangeTrigger[]) { - this.triggers = triggers; - this.description = this.triggers - .filter(trigger => trigger?.description?.length > 0) - .map(trigger => trigger.description) - .join(", "); - } - - canChange(pokemon: Pokemon): boolean { - for (const trigger of this.triggers) { - if (!trigger.canChange(pokemon)) { - return false; - } - } - - return true; - } - - hasTriggerType(triggerType: Constructor): boolean { - return !!this.triggers.find(t => t.hasTriggerType(triggerType)); - } -} - -export class SpeciesFormChangeItemTrigger extends SpeciesFormChangeTrigger { - public item: FormChangeItem; - public active: boolean; - - constructor(item: FormChangeItem, active = true) { - super(); - this.item = item; - this.active = active; - this.description = this.active - ? i18next.t("pokemonEvolutions:Forms.item", { - item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`), - }) - : i18next.t("pokemonEvolutions:Forms.deactivateItem", { - item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`), - }); - } - - canChange(pokemon: Pokemon): boolean { - return !!globalScene.findModifier( - m => - m instanceof PokemonFormChangeItemModifier && - m.pokemonId === pokemon.id && - m.formChangeItem === this.item && - m.active === this.active, - ); - } -} - -export class SpeciesFormChangeTimeOfDayTrigger extends SpeciesFormChangeTrigger { - public timesOfDay: TimeOfDay[]; - - constructor(...timesOfDay: TimeOfDay[]) { - super(); - this.timesOfDay = timesOfDay; - this.description = i18next.t("pokemonEvolutions:Forms.timeOfDay"); - } - - canChange(_pokemon: Pokemon): boolean { - return this.timesOfDay.indexOf(globalScene.arena.getTimeOfDay()) > -1; - } -} - -export class SpeciesFormChangeActiveTrigger extends SpeciesFormChangeTrigger { - public active: boolean; - - constructor(active = false) { - super(); - this.active = active; - this.description = this.active - ? i18next.t("pokemonEvolutions:Forms.enter") - : i18next.t("pokemonEvolutions:Forms.leave"); - } - - canChange(pokemon: Pokemon): boolean { - return pokemon.isActive(true) === this.active; - } -} - -export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigger { - public statusEffects: StatusEffect[]; - public invert: boolean; - - constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) { - super(); - if (!Array.isArray(statusEffects)) { - statusEffects = [statusEffects]; - } - this.statusEffects = statusEffects; - this.invert = invert; - this.description = i18next.t("pokemonEvolutions:Forms.statusEffect"); - } - - canChange(pokemon: Pokemon): boolean { - return this.statusEffects.indexOf(pokemon.status?.effect || StatusEffect.NONE) > -1 !== this.invert; - } -} - -export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigger { - public move: MoveId; - public known: boolean; - - constructor(move: MoveId, known = true) { - super(); - this.move = move; - this.known = known; - const moveKey = MoveId[this.move] - .split("_") - .filter(f => f) - .map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase())) - .join("") as unknown as string; - this.description = known - ? i18next.t("pokemonEvolutions:Forms.moveLearned", { - move: i18next.t(`move:${moveKey}.name`), - }) - : i18next.t("pokemonEvolutions:Forms.moveForgotten", { - move: i18next.t(`move:${moveKey}.name`), - }); - } - - canChange(pokemon: Pokemon): boolean { - return !!pokemon.moveset.filter(m => m.moveId === this.move).length === this.known; - } -} - -export abstract class SpeciesFormChangeMoveTrigger extends SpeciesFormChangeTrigger { - public movePredicate: (m: MoveId) => boolean; - public used: boolean; - - constructor(move: MoveId | ((m: MoveId) => boolean), used = true) { - super(); - this.movePredicate = typeof move === "function" ? move : (m: MoveId) => m === move; - this.used = used; - } -} - -export class SpeciesFormChangePreMoveTrigger extends SpeciesFormChangeMoveTrigger { - description = i18next.t("pokemonEvolutions:Forms.preMove"); - - canChange(pokemon: Pokemon): boolean { - const command = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; - return !!command?.move && this.movePredicate(command.move.move) === this.used; - } -} - -export class SpeciesFormChangePostMoveTrigger extends SpeciesFormChangeMoveTrigger { - description = i18next.t("pokemonEvolutions:Forms.postMove"); - - canChange(pokemon: Pokemon): boolean { - return ( - pokemon.summonData && !!pokemon.getLastXMoves(1).filter(m => this.movePredicate(m.move)).length === this.used - ); - } -} - -export class MeloettaFormChangePostMoveTrigger extends SpeciesFormChangePostMoveTrigger { - override canChange(pokemon: Pokemon): boolean { - if (globalScene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) { - return false; - } - // Meloetta will not transform if it has the ability Sheer Force when using Relic Song - if (pokemon.hasAbility(AbilityId.SHEER_FORCE)) { - return false; - } - return super.canChange(pokemon); - } -} - -export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger { - private formKey: string; - - constructor(formKey: string) { - super(); - this.formKey = formKey; - this.description = ""; - } - - canChange(pokemon: Pokemon): boolean { - return ( - this.formKey === - pokemon.species.forms[globalScene.getSpeciesFormIndex(pokemon.species, pokemon.gender, pokemon.getNature(), true)] - .formKey - ); - } -} - -/** - * Class used for triggering form changes based on the user's Tera type. - * Used by Ogerpon and Terapagos. - * @extends SpeciesFormChangeTrigger - */ -export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger { - description = i18next.t("pokemonEvolutions:Forms.tera"); -} - -/** - * Class used for triggering form changes based on the user's lapsed Tera type. - * Used by Ogerpon and Terapagos. - * @extends SpeciesFormChangeTrigger - */ -export class SpeciesFormChangeLapseTeraTrigger extends SpeciesFormChangeTrigger { - description = i18next.t("pokemonEvolutions:Forms.teraLapse"); -} - -/** - * Class used for triggering form changes based on weather. - * Used by Castform and Cherrim. - * @extends SpeciesFormChangeTrigger - */ -export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger { - /** The ability that triggers the form change */ - public ability: AbilityId; - /** The list of weathers that trigger the form change */ - public weathers: WeatherType[]; - - constructor(ability: AbilityId, weathers: WeatherType[]) { - super(); - this.ability = ability; - this.weathers = weathers; - this.description = i18next.t("pokemonEvolutions:Forms.weather"); - } - - /** - * Checks if the Pokemon has the required ability and is in the correct weather while - * the weather or ability is also not suppressed. - * @param {Pokemon} pokemon the pokemon that is trying to do the form change - * @returns `true` if the Pokemon can change forms, `false` otherwise - */ - canChange(pokemon: Pokemon): boolean { - const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE; - const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed(); - const isAbilitySuppressed = pokemon.summonData.abilitySuppressed; - - return ( - !isAbilitySuppressed && - !isWeatherSuppressed && - pokemon.hasAbility(this.ability) && - this.weathers.includes(currentWeather) - ); - } -} - -/** - * Class used for reverting to the original form when the weather runs out - * or when the user loses the ability/is suppressed. - * Used by Castform and Cherrim. - * @extends SpeciesFormChangeTrigger - */ -export class SpeciesFormChangeRevertWeatherFormTrigger extends SpeciesFormChangeTrigger { - /** The ability that triggers the form change*/ - public ability: AbilityId; - /** The list of weathers that will also trigger a form change to original form */ - public weathers: WeatherType[]; - - constructor(ability: AbilityId, weathers: WeatherType[]) { - super(); - this.ability = ability; - this.weathers = weathers; - this.description = i18next.t("pokemonEvolutions:Forms.weatherRevert"); - } - - /** - * Checks if the Pokemon has the required ability and the weather is one that will revert - * the Pokemon to its original form or the weather or ability is suppressed - * @param {Pokemon} pokemon the pokemon that is trying to do the form change - * @returns `true` if the Pokemon will revert to its original form, `false` otherwise - */ - canChange(pokemon: Pokemon): boolean { - if (pokemon.hasAbility(this.ability, false, true)) { - const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE; - const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed(); - const isAbilitySuppressed = pokemon.summonData.abilitySuppressed; - const summonDataAbility = pokemon.summonData.ability; - const isAbilityChanged = summonDataAbility !== this.ability && summonDataAbility !== AbilityId.NONE; - - if (this.weathers.includes(currentWeather) || isWeatherSuppressed || isAbilitySuppressed || isAbilityChanged) { - return true; - } - } - return false; - } -} - -export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: SpeciesFormChange, preName: string): string { - const isMega = formChange.formKey.indexOf(SpeciesFormKey.MEGA) > -1; - const isGmax = formChange.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1; - const isEmax = formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1; - const isRevert = !isMega && formChange.formKey === pokemon.species.forms[0].formKey; - if (isMega) { - return i18next.t("battlePokemonForm:megaChange", { - preName, - pokemonName: pokemon.name, - }); - } - if (isGmax) { - return i18next.t("battlePokemonForm:gigantamaxChange", { - preName, - pokemonName: pokemon.name, - }); - } - if (isEmax) { - return i18next.t("battlePokemonForm:eternamaxChange", { - preName, - pokemonName: pokemon.name, - }); - } - if (isRevert) { - return i18next.t("battlePokemonForm:revertChange", { - pokemonName: getPokemonNameWithAffix(pokemon), - }); - } - if (pokemon.getAbility().id === AbilityId.DISGUISE) { - return i18next.t("battlePokemonForm:disguiseChange"); - } - return i18next.t("battlePokemonForm:formChange", { preName }); -} - /** * Gives a condition for form changing checking if a species is registered as caught in the player's dex data. * Used for fusion forms such as Kyurem and Necrozma. diff --git a/src/data/pokemon-forms/form-change-triggers.ts b/src/data/pokemon-forms/form-change-triggers.ts new file mode 100644 index 00000000000..eb2c0a557c2 --- /dev/null +++ b/src/data/pokemon-forms/form-change-triggers.ts @@ -0,0 +1,348 @@ +import i18next from "i18next"; +import type { Constructor } from "#app/utils/common"; +import type { TimeOfDay } from "#enums/time-of-day"; +import type Pokemon from "#app/field/pokemon"; +import type { SpeciesFormChange } from "#app/data/pokemon-forms"; +import type { PokemonFormChangeItemModifier } from "#app/modifier/modifier"; +import { getPokemonNameWithAffix } from "#app/messages"; +import { globalScene } from "#app/global-scene"; +import { FormChangeItem } from "#enums/form-change-item"; +import { AbilityId } from "#enums/ability-id"; +import { Challenges } from "#enums/challenges"; +import { MoveId } from "#enums/move-id"; +import { SpeciesFormKey } from "#enums/species-form-key"; +import { StatusEffect } from "#enums/status-effect"; +import { WeatherType } from "#enums/weather-type"; + +export abstract class SpeciesFormChangeTrigger { + public description = ""; + + canChange(_pokemon: Pokemon): boolean { + return true; + } + + hasTriggerType(triggerType: Constructor): boolean { + return this instanceof triggerType; + } +} + +export class SpeciesFormChangeManualTrigger extends SpeciesFormChangeTrigger {} + +export class SpeciesFormChangeAbilityTrigger extends SpeciesFormChangeTrigger { + public description: string = i18next.t("pokemonEvolutions:Forms.ability"); +} + +export class SpeciesFormChangeCompoundTrigger { + public description = ""; + public triggers: SpeciesFormChangeTrigger[]; + + constructor(...triggers: SpeciesFormChangeTrigger[]) { + this.triggers = triggers; + this.description = this.triggers + .filter(trigger => trigger?.description?.length > 0) + .map(trigger => trigger.description) + .join(", "); + } + + canChange(pokemon: Pokemon): boolean { + for (const trigger of this.triggers) { + if (!trigger.canChange(pokemon)) { + return false; + } + } + + return true; + } + + hasTriggerType(triggerType: Constructor): boolean { + return !!this.triggers.find(t => t.hasTriggerType(triggerType)); + } +} + +export class SpeciesFormChangeItemTrigger extends SpeciesFormChangeTrigger { + public item: FormChangeItem; + public active: boolean; + + constructor(item: FormChangeItem, active = true) { + super(); + this.item = item; + this.active = active; + this.description = this.active + ? i18next.t("pokemonEvolutions:Forms.item", { + item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`), + }) + : i18next.t("pokemonEvolutions:Forms.deactivateItem", { + item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`), + }); + } + + canChange(pokemon: Pokemon): boolean { + return !!globalScene.findModifier(r => { + // Assume that if m has the `formChangeItem` property, then it is a PokemonFormChangeItemModifier + const m = r as PokemonFormChangeItemModifier; + return ( + "formChangeItem" in m && + m.pokemonId === pokemon.id && + m.formChangeItem === this.item && + m.active === this.active + ); + }); + } +} + +export class SpeciesFormChangeTimeOfDayTrigger extends SpeciesFormChangeTrigger { + public timesOfDay: TimeOfDay[]; + + constructor(...timesOfDay: TimeOfDay[]) { + super(); + this.timesOfDay = timesOfDay; + this.description = i18next.t("pokemonEvolutions:Forms.timeOfDay"); + } + + canChange(_pokemon: Pokemon): boolean { + return this.timesOfDay.indexOf(globalScene.arena.getTimeOfDay()) > -1; + } +} +export class SpeciesFormChangeActiveTrigger extends SpeciesFormChangeTrigger { + public active: boolean; + + constructor(active = false) { + super(); + this.active = active; + this.description = this.active + ? i18next.t("pokemonEvolutions:Forms.enter") + : i18next.t("pokemonEvolutions:Forms.leave"); + } + + canChange(pokemon: Pokemon): boolean { + return pokemon.isActive(true) === this.active; + } +} + +export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigger { + public statusEffects: StatusEffect[]; + public invert: boolean; + + constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) { + super(); + if (!Array.isArray(statusEffects)) { + statusEffects = [statusEffects]; + } + this.statusEffects = statusEffects; + this.invert = invert; + // this.description = i18next.t("pokemonEvolutions:Forms.statusEffect"); + } + + canChange(pokemon: Pokemon): boolean { + return this.statusEffects.indexOf(pokemon.status?.effect || StatusEffect.NONE) > -1 !== this.invert; + } +} + +export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigger { + public move: MoveId; + public known: boolean; + + constructor(move: MoveId, known = true) { + super(); + this.move = move; + this.known = known; + const moveKey = MoveId[this.move] + .split("_") + .filter(f => f) + .map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase())) + .join("") as unknown as string; + this.description = known + ? i18next.t("pokemonEvolutions:Forms.moveLearned", { + move: i18next.t(`move:${moveKey}.name`), + }) + : i18next.t("pokemonEvolutions:Forms.moveForgotten", { + move: i18next.t(`move:${moveKey}.name`), + }); + } + + canChange(pokemon: Pokemon): boolean { + return !!pokemon.moveset.filter(m => m.moveId === this.move).length === this.known; + } +} + +export abstract class SpeciesFormChangeMoveTrigger extends SpeciesFormChangeTrigger { + public movePredicate: (m: MoveId) => boolean; + public used: boolean; + + constructor(move: MoveId | ((m: MoveId) => boolean), used = true) { + super(); + this.movePredicate = typeof move === "function" ? move : (m: MoveId) => m === move; + this.used = used; + } +} + +export class SpeciesFormChangePreMoveTrigger extends SpeciesFormChangeMoveTrigger { + description = i18next.t("pokemonEvolutions:Forms.preMove"); + canChange(pokemon: Pokemon): boolean { + const command = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; + return !!command?.move && this.movePredicate(command.move.move) === this.used; + } +} + +export class SpeciesFormChangePostMoveTrigger extends SpeciesFormChangeMoveTrigger { + description = i18next.t("pokemonEvolutions:Forms.postMove"); + canChange(pokemon: Pokemon): boolean { + return ( + pokemon.summonData && !!pokemon.getLastXMoves(1).filter(m => this.movePredicate(m.move)).length === this.used + ); + } +} + +export class MeloettaFormChangePostMoveTrigger extends SpeciesFormChangePostMoveTrigger { + override canChange(pokemon: Pokemon): boolean { + if (globalScene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) { + return false; + } + // Meloetta will not transform if it has the ability Sheer Force when using Relic Song + if (pokemon.hasAbility(AbilityId.SHEER_FORCE)) { + return false; + } + return super.canChange(pokemon); + } +} + +export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger { + private formKey: string; + + constructor(formKey: string) { + super(); + this.formKey = formKey; + this.description = ""; + } + + canChange(pokemon: Pokemon): boolean { + return ( + this.formKey === + pokemon.species.forms[globalScene.getSpeciesFormIndex(pokemon.species, pokemon.gender, pokemon.getNature(), true)] + .formKey + ); + } +} + +/** + * Class used for triggering form changes based on the user's Tera type. + * Used by Ogerpon and Terapagos. + */ +export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger {} + +/** + * Class used for triggering form changes based on the user's lapsed Tera type. + * Used by Ogerpon and Terapagos. + */ +export class SpeciesFormChangeLapseTeraTrigger extends SpeciesFormChangeTrigger {} + +/** + * Class used for triggering form changes based on weather. + * Used by Castform and Cherrim. + */ +export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger { + /** The ability that triggers the form change */ + public ability: AbilityId; + /** The list of weathers that trigger the form change */ + public weathers: WeatherType[]; + + constructor(ability: AbilityId, weathers: WeatherType[]) { + super(); + this.ability = ability; + this.weathers = weathers; + this.description = i18next.t("pokemonEvolutions:Forms.weather"); + } + + /** + * Checks if the Pokemon has the required ability and is in the correct weather while + * the weather or ability is also not suppressed. + * @param pokemon - The pokemon that is trying to do the form change + * @returns `true` if the Pokemon can change forms, `false` otherwise + */ + canChange(pokemon: Pokemon): boolean { + const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE; + const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed(); + const isAbilitySuppressed = pokemon.summonData.abilitySuppressed; + + return ( + !isAbilitySuppressed && + !isWeatherSuppressed && + pokemon.hasAbility(this.ability) && + this.weathers.includes(currentWeather) + ); + } +} + +/** + * Class used for reverting to the original form when the weather runs out + * or when the user loses the ability/is suppressed. + * Used by Castform and Cherrim. + */ +export class SpeciesFormChangeRevertWeatherFormTrigger extends SpeciesFormChangeTrigger { + /** The ability that triggers the form change*/ + public ability: AbilityId; + /** The list of weathers that will also trigger a form change to original form */ + public weathers: WeatherType[]; + + constructor(ability: AbilityId, weathers: WeatherType[]) { + super(); + this.ability = ability; + this.weathers = weathers; + this.description = i18next.t("pokemonEvolutions:Forms.weatherRevert"); + } + + /** + * Checks if the Pokemon has the required ability and the weather is one that will revert + * the Pokemon to its original form or the weather or ability is suppressed + * @param {Pokemon} pokemon the pokemon that is trying to do the form change + * @returns `true` if the Pokemon will revert to its original form, `false` otherwise + */ + canChange(pokemon: Pokemon): boolean { + if (pokemon.hasAbility(this.ability, false, true)) { + const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE; + const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed(); + const isAbilitySuppressed = pokemon.summonData.abilitySuppressed; + const summonDataAbility = pokemon.summonData.ability; + const isAbilityChanged = summonDataAbility !== this.ability && summonDataAbility !== AbilityId.NONE; + + if (this.weathers.includes(currentWeather) || isWeatherSuppressed || isAbilitySuppressed || isAbilityChanged) { + return true; + } + } + return false; + } +} + +export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: SpeciesFormChange, preName: string): string { + const isMega = formChange.formKey.indexOf(SpeciesFormKey.MEGA) > -1; + const isGmax = formChange.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1; + const isEmax = formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1; + const isRevert = !isMega && formChange.formKey === pokemon.species.forms[0].formKey; + if (isMega) { + return i18next.t("battlePokemonForm:megaChange", { + preName, + pokemonName: pokemon.name, + }); + } + if (isGmax) { + return i18next.t("battlePokemonForm:gigantamaxChange", { + preName, + pokemonName: pokemon.name, + }); + } + if (isEmax) { + return i18next.t("battlePokemonForm:eternamaxChange", { + preName, + pokemonName: pokemon.name, + }); + } + if (isRevert) { + return i18next.t("battlePokemonForm:revertChange", { + pokemonName: getPokemonNameWithAffix(pokemon), + }); + } + if (pokemon.getAbility().id === AbilityId.DISGUISE) { + return i18next.t("battlePokemonForm:disguiseChange"); + } + return i18next.t("battlePokemonForm:formChange", { preName }); +} diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 36a8bbb0520..56dc649afac 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -7,7 +7,8 @@ import i18next from "i18next"; import type { AnySound } from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; import type { GameMode } from "#app/game-mode"; -import { DexAttr, type StarterMoveset } from "#app/system/game-data"; +import type { StarterMoveset } from "#app/system/game-data"; +import { DexAttr } from "#enums/dex-attr"; import { isNullOrUndefined, capitalizeString, diff --git a/src/data/terrain.ts b/src/data/terrain.ts index 5b6063cee68..b3ee62ac2f9 100644 --- a/src/data/terrain.ts +++ b/src/data/terrain.ts @@ -1,8 +1,7 @@ import type Pokemon from "../field/pokemon"; import type Move from "./moves/move"; import { PokemonType } from "#enums/pokemon-type"; -import { ProtectAttr } from "./moves/move"; -import type { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#enums/battler-index"; import i18next from "i18next"; export enum TerrainType { @@ -55,7 +54,7 @@ export class Terrain { isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean { switch (this.terrainType) { case TerrainType.PSYCHIC: - if (!move.hasAttr(ProtectAttr)) { + if (!move.hasAttr("ProtectAttr")) { // Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain return ( move.getPriority(user) > 0 && diff --git a/src/data/trainers/TrainerPartyTemplate.ts b/src/data/trainers/TrainerPartyTemplate.ts index 86201589276..135fe669825 100644 --- a/src/data/trainers/TrainerPartyTemplate.ts +++ b/src/data/trainers/TrainerPartyTemplate.ts @@ -1,7 +1,7 @@ import { startingWave } from "#app/starting-wave"; import { globalScene } from "#app/global-scene"; import { PartyMemberStrength } from "#enums/party-member-strength"; -import { GameModes } from "#app/game-mode"; +import { GameModes } from "#enums/game-modes"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; export class TrainerPartyTemplate { diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 8e704b0b301..2f1f7ed07a8 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -1,12 +1,12 @@ import { globalScene } from "#app/global-scene"; import { modifierTypes } from "#app/modifier/modifier-type"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "../moves/pokemon-move"; import { toReadableString, isNullOrUndefined, randSeedItem, randSeedInt, randSeedIntRange } from "#app/utils/common"; import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { tmSpecies } from "#app/data/balance/tms"; import { doubleBattleDialogue } from "#app/data/dialogue"; -import { TrainerVariant } from "#app/field/trainer"; +import { TrainerVariant } from "#enums/trainer-variant"; import { getIsInitialized, initI18n } from "#app/plugins/i18n"; import i18next from "i18next"; import { Gender } from "#app/data/gender"; diff --git a/src/data/weather.ts b/src/data/weather.ts index 3bd2e38824d..822e5aa8303 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -4,7 +4,6 @@ import { getPokemonNameWithAffix } from "../messages"; import type Pokemon from "../field/pokemon"; import { PokemonType } from "#enums/pokemon-type"; import type Move from "./moves/move"; -import { AttackMove } from "./moves/move"; import { randSeedInt } from "#app/utils/common"; import { SuppressWeatherEffectAbAttr } from "./abilities/ability"; import { TerrainType, getTerrainName } from "./terrain"; @@ -95,9 +94,9 @@ export class Weather { switch (this.weatherType) { case WeatherType.HARSH_SUN: - return move instanceof AttackMove && moveType === PokemonType.WATER; + return move.is("AttackMove") && moveType === PokemonType.WATER; case WeatherType.HEAVY_RAIN: - return move instanceof AttackMove && moveType === PokemonType.FIRE; + return move.is("AttackMove") && moveType === PokemonType.FIRE; } return false; diff --git a/src/enums/ability-attr.ts b/src/enums/ability-attr.ts new file mode 100644 index 00000000000..5f7d107f2d1 --- /dev/null +++ b/src/enums/ability-attr.ts @@ -0,0 +1,11 @@ +/** + * Not to be confused with an Ability Attribute. + * This is an object literal storing the slot that an ability can occupy. + */ +export const AbilityAttr = Object.freeze({ + ABILITY_1: 1, + ABILITY_2: 2, + ABILITY_HIDDEN: 4, +}); + +export type AbilityAttr = typeof AbilityAttr[keyof typeof AbilityAttr]; \ No newline at end of file diff --git a/src/enums/ai-type.ts b/src/enums/ai-type.ts new file mode 100644 index 00000000000..13931172a4a --- /dev/null +++ b/src/enums/ai-type.ts @@ -0,0 +1,5 @@ +export enum AiType { + RANDOM, + SMART_RANDOM, + SMART +} diff --git a/src/enums/arena-tag-side.ts b/src/enums/arena-tag-side.ts new file mode 100644 index 00000000000..3e326ce158a --- /dev/null +++ b/src/enums/arena-tag-side.ts @@ -0,0 +1,5 @@ +export enum ArenaTagSide { + BOTH, + PLAYER, + ENEMY +} diff --git a/src/enums/battler-index.ts b/src/enums/battler-index.ts new file mode 100644 index 00000000000..32b1684c86c --- /dev/null +++ b/src/enums/battler-index.ts @@ -0,0 +1,7 @@ +export enum BattlerIndex { + ATTACKER = -1, + PLAYER, + PLAYER_2, + ENEMY, + ENEMY_2 +} diff --git a/src/enums/battler-tag-lapse-type.ts b/src/enums/battler-tag-lapse-type.ts new file mode 100644 index 00000000000..355a084148b --- /dev/null +++ b/src/enums/battler-tag-lapse-type.ts @@ -0,0 +1,12 @@ +export enum BattlerTagLapseType { + FAINT, + MOVE, + PRE_MOVE, + AFTER_MOVE, + MOVE_EFFECT, + TURN_END, + HIT, + /** Tag lapses AFTER_HIT, applying its effects even if the user faints */ + AFTER_HIT, + CUSTOM +} diff --git a/src/enums/challenge-type.ts b/src/enums/challenge-type.ts new file mode 100644 index 00000000000..d9b1fce3e6e --- /dev/null +++ b/src/enums/challenge-type.ts @@ -0,0 +1,69 @@ +/** + * An enum for all the challenge types. The parameter entries on these describe the + * parameters to use when calling the applyChallenges function. + */ +export enum ChallengeType { + /** + * Challenges which modify what starters you can choose + * @see {@link Challenge.applyStarterChoice} + */ + STARTER_CHOICE, + /** + * Challenges which modify how many starter points you have + * @see {@link Challenge.applyStarterPoints} + */ + STARTER_POINTS, + /** + * Challenges which modify how many starter points you have + * @see {@link Challenge.applyStarterPointCost} + */ + STARTER_COST, + /** + * Challenges which modify your starters in some way + * @see {@link Challenge.applyStarterModify} + */ + STARTER_MODIFY, + /** + * Challenges which limit which pokemon you can have in battle. + * @see {@link Challenge.applyPokemonInBattle} + */ + POKEMON_IN_BATTLE, + /** + * Adds or modifies the fixed battles in a run + * @see {@link Challenge.applyFixedBattle} + */ + FIXED_BATTLES, + /** + * Modifies the effectiveness of Type matchups in battle + * @see {@linkcode Challenge.applyTypeEffectiveness} + */ + TYPE_EFFECTIVENESS, + /** + * Modifies what level the AI pokemon are. UNIMPLEMENTED. + */ + AI_LEVEL, + /** + * Modifies how many move slots the AI has. UNIMPLEMENTED. + */ + AI_MOVE_SLOTS, + /** + * Modifies if a pokemon has its passive. UNIMPLEMENTED. + */ + PASSIVE_ACCESS, + /** + * Modifies the game mode settings in some way. UNIMPLEMENTED. + */ + GAME_MODE_MODIFY, + /** + * Modifies what level AI pokemon can access a move. UNIMPLEMENTED. + */ + MOVE_ACCESS, + /** + * Modifies what weight AI pokemon have when generating movesets. UNIMPLEMENTED. + */ + MOVE_WEIGHT, + /** + * Modifies what the pokemon stats for Flip Stat Mode. + */ + FLIP_STAT +} diff --git a/src/enums/command.ts b/src/enums/command.ts new file mode 100644 index 00000000000..4cd626bb066 --- /dev/null +++ b/src/enums/command.ts @@ -0,0 +1,7 @@ +export enum Command { + FIGHT = 0, + BALL, + POKEMON, + RUN, + TERA +} diff --git a/src/enums/dex-attr.ts b/src/enums/dex-attr.ts new file mode 100644 index 00000000000..ee5ceb43ef2 --- /dev/null +++ b/src/enums/dex-attr.ts @@ -0,0 +1,11 @@ +export const DexAttr = Object.freeze({ + NON_SHINY: 1n, + SHINY: 2n, + MALE: 4n, + FEMALE: 8n, + DEFAULT_VARIANT: 16n, + VARIANT_2: 32n, + VARIANT_3: 64n, + DEFAULT_FORM: 128n, +}); +export type DexAttr = typeof DexAttr[keyof typeof DexAttr]; diff --git a/src/enums/field-position.ts b/src/enums/field-position.ts new file mode 100644 index 00000000000..5b7f9c6c570 --- /dev/null +++ b/src/enums/field-position.ts @@ -0,0 +1,5 @@ +export enum FieldPosition { + CENTER, + LEFT, + RIGHT +} diff --git a/src/enums/form-change-item.ts b/src/enums/form-change-item.ts new file mode 100644 index 00000000000..15620eafd0a --- /dev/null +++ b/src/enums/form-change-item.ts @@ -0,0 +1,119 @@ +export enum FormChangeItem { + NONE, + + ABOMASITE, + ABSOLITE, + AERODACTYLITE, + AGGRONITE, + ALAKAZITE, + ALTARIANITE, + AMPHAROSITE, + AUDINITE, + BANETTITE, + BEEDRILLITE, + BLASTOISINITE, + BLAZIKENITE, + CAMERUPTITE, + CHARIZARDITE_X, + CHARIZARDITE_Y, + DIANCITE, + GALLADITE, + GARCHOMPITE, + GARDEVOIRITE, + GENGARITE, + GLALITITE, + GYARADOSITE, + HERACRONITE, + HOUNDOOMINITE, + KANGASKHANITE, + LATIASITE, + LATIOSITE, + LOPUNNITE, + LUCARIONITE, + MANECTITE, + MAWILITE, + MEDICHAMITE, + METAGROSSITE, + MEWTWONITE_X, + MEWTWONITE_Y, + PIDGEOTITE, + PINSIRITE, + RAYQUAZITE, + SABLENITE, + SALAMENCITE, + SCEPTILITE, + SCIZORITE, + SHARPEDONITE, + SLOWBRONITE, + STEELIXITE, + SWAMPERTITE, + TYRANITARITE, + VENUSAURITE, + + BLUE_ORB = 50, + RED_ORB, + ADAMANT_CRYSTAL, + LUSTROUS_GLOBE, + GRISEOUS_CORE, + REVEAL_GLASS, + MAX_MUSHROOMS, + DARK_STONE, + LIGHT_STONE, + PRISON_BOTTLE, + RUSTED_SWORD, + RUSTED_SHIELD, + ICY_REINS_OF_UNITY, + SHADOW_REINS_OF_UNITY, + ULTRANECROZIUM_Z, + + SHARP_METEORITE = 100, + HARD_METEORITE, + SMOOTH_METEORITE, + GRACIDEA, + SHOCK_DRIVE, + BURN_DRIVE, + CHILL_DRIVE, + DOUSE_DRIVE, + N_SOLARIZER, + N_LUNARIZER, + WELLSPRING_MASK, + HEARTHFLAME_MASK, + CORNERSTONE_MASK, + FIST_PLATE, + SKY_PLATE, + TOXIC_PLATE, + EARTH_PLATE, + STONE_PLATE, + INSECT_PLATE, + SPOOKY_PLATE, + IRON_PLATE, + FLAME_PLATE, + SPLASH_PLATE, + MEADOW_PLATE, + ZAP_PLATE, + MIND_PLATE, + ICICLE_PLATE, + DRACO_PLATE, + DREAD_PLATE, + PIXIE_PLATE, + BLANK_PLATE,// TODO: Find a potential use for this + LEGEND_PLATE,// TODO: Find a potential use for this + FIGHTING_MEMORY, + FLYING_MEMORY, + POISON_MEMORY, + GROUND_MEMORY, + ROCK_MEMORY, + BUG_MEMORY, + GHOST_MEMORY, + STEEL_MEMORY, + FIRE_MEMORY, + WATER_MEMORY, + GRASS_MEMORY, + ELECTRIC_MEMORY, + PSYCHIC_MEMORY, + ICE_MEMORY, + DRAGON_MEMORY, + DARK_MEMORY, + FAIRY_MEMORY, + NORMAL_MEMORY +} diff --git a/src/enums/game-modes.ts b/src/enums/game-modes.ts new file mode 100644 index 00000000000..837b634621c --- /dev/null +++ b/src/enums/game-modes.ts @@ -0,0 +1,7 @@ +export enum GameModes { + CLASSIC, + ENDLESS, + SPLICED_ENDLESS, + DAILY, + CHALLENGE +} diff --git a/src/enums/hit-result.ts b/src/enums/hit-result.ts new file mode 100644 index 00000000000..3e62587dd6c --- /dev/null +++ b/src/enums/hit-result.ts @@ -0,0 +1,15 @@ +export enum HitResult { + EFFECTIVE = 1, + SUPER_EFFECTIVE, + NOT_VERY_EFFECTIVE, + ONE_HIT_KO, + NO_EFFECT, + STATUS, + HEAL, + FAIL, + MISS, + INDIRECT, + IMMUNE, + CONFUSION, + INDIRECT_KO +} diff --git a/src/enums/learn-move-situation.ts b/src/enums/learn-move-situation.ts new file mode 100644 index 00000000000..9b329d0f3de --- /dev/null +++ b/src/enums/learn-move-situation.ts @@ -0,0 +1,8 @@ +export enum LearnMoveSituation { + MISC, + LEVEL_UP, + RELEARN, + EVOLUTION, + EVOLUTION_FUSED,// If fusionSpecies has Evolved + EVOLUTION_FUSED_BASE +} diff --git a/src/enums/learn-move-type.ts b/src/enums/learn-move-type.ts new file mode 100644 index 00000000000..442639c1bc7 --- /dev/null +++ b/src/enums/learn-move-type.ts @@ -0,0 +1,8 @@ +export enum LearnMoveType { + /** For learning a move via level-up, evolution, or other non-item-based event */ + LEARN_MOVE, + /** For learning a move via Memory Mushroom */ + MEMORY, + /** For learning a move via TM */ + TM +} diff --git a/src/enums/move-anims-common.ts b/src/enums/move-anims-common.ts new file mode 100644 index 00000000000..f21e4c8be4a --- /dev/null +++ b/src/enums/move-anims-common.ts @@ -0,0 +1,95 @@ +export enum AnimFrameTarget { + USER, + TARGET, + GRAPHIC +} + +export enum AnimFocus { + TARGET = 1, + USER, + USER_TARGET, + SCREEN +} + +export enum AnimBlendType { + NORMAL, + ADD, + SUBTRACT +} + +export enum ChargeAnim { + FLY_CHARGING = 1000, + BOUNCE_CHARGING, + DIG_CHARGING, + FUTURE_SIGHT_CHARGING, + DIVE_CHARGING, + SOLAR_BEAM_CHARGING, + SHADOW_FORCE_CHARGING, + SKULL_BASH_CHARGING, + FREEZE_SHOCK_CHARGING, + SKY_DROP_CHARGING, + SKY_ATTACK_CHARGING, + ICE_BURN_CHARGING, + DOOM_DESIRE_CHARGING, + RAZOR_WIND_CHARGING, + PHANTOM_FORCE_CHARGING, + GEOMANCY_CHARGING, + SHADOW_BLADE_CHARGING, + SOLAR_BLADE_CHARGING, + BEAK_BLAST_CHARGING, + METEOR_BEAM_CHARGING, + ELECTRO_SHOT_CHARGING +} + +export enum CommonAnim { + USE_ITEM = 2000, + HEALTH_UP, + TERASTALLIZE, + POISON = 2010, + TOXIC, + PARALYSIS, + SLEEP, + FROZEN, + BURN, + CONFUSION, + ATTRACT, + BIND, + WRAP, + CURSE_NO_GHOST, + LEECH_SEED, + FIRE_SPIN, + PROTECT, + COVET, + WHIRLPOOL, + BIDE, + SAND_TOMB, + QUICK_GUARD, + WIDE_GUARD, + CURSE, + MAGMA_STORM, + CLAMP, + SNAP_TRAP, + THUNDER_CAGE, + INFESTATION, + ORDER_UP_CURLY, + ORDER_UP_DROOPY, + ORDER_UP_STRETCHY, + RAGING_BULL_FIRE, + RAGING_BULL_WATER, + SALT_CURE, + POWDER, + SUNNY = 2100, + RAIN, + SANDSTORM, + HAIL, + SNOW, + WIND, + HEAVY_RAIN, + HARSH_SUN, + STRONG_WINDS, + MISTY_TERRAIN = 2110, + ELECTRIC_TERRAIN, + GRASSY_TERRAIN, + PSYCHIC_TERRAIN, + LOCK_ON = 2120 +} diff --git a/src/enums/move-result.ts b/src/enums/move-result.ts new file mode 100644 index 00000000000..d402f5b1aed --- /dev/null +++ b/src/enums/move-result.ts @@ -0,0 +1,7 @@ +export enum MoveResult { + PENDING, + SUCCESS, + FAIL, + MISS, + OTHER +} diff --git a/src/enums/move-source-type.ts b/src/enums/move-source-type.ts new file mode 100644 index 00000000000..d9afb07e7f7 --- /dev/null +++ b/src/enums/move-source-type.ts @@ -0,0 +1,12 @@ +/** + * Used for challenge types that modify movesets, these denote the various sources of moves for pokemon. + */ +export enum MoveSourceType { + LEVEL_UP,// Currently unimplemented for move access + RELEARNER,// Relearner moves currently unimplemented + COMMON_TM, + GREAT_TM, + ULTRA_TM, + COMMON_EGG, + RARE_EGG +} diff --git a/src/enums/trainer-variant.ts b/src/enums/trainer-variant.ts new file mode 100644 index 00000000000..cd8d71cc1b9 --- /dev/null +++ b/src/enums/trainer-variant.ts @@ -0,0 +1,5 @@ +export enum TrainerVariant { + DEFAULT, + FEMALE, + DOUBLE +} diff --git a/src/events/arena.ts b/src/events/arena.ts index ad77289b76b..3b65506db98 100644 --- a/src/events/arena.ts +++ b/src/events/arena.ts @@ -1,4 +1,4 @@ -import type { ArenaTagSide } from "#app/data/arena-tag"; +import type { ArenaTagSide } from "#enums/arena-tag-side"; import type { ArenaTagType } from "#enums/arena-tag-type"; import type { TerrainType } from "#app/data/terrain"; import type { WeatherType } from "#enums/weather-type"; diff --git a/src/field/arena.ts b/src/field/arena.ts index 82a8afbedad..fffbf8dfa26 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -12,12 +12,13 @@ import { getLegendaryWeatherContinuesMessage, Weather, } from "#app/data/weather"; -import { CommonAnim } from "#app/data/battle-anims"; +import { CommonAnim } from "#enums/move-anims-common"; import type { PokemonType } from "#enums/pokemon-type"; import type Move from "#app/data/moves/move"; import type { ArenaTag } from "#app/data/arena-tag"; -import { ArenaTagSide, ArenaTrapTag, getArenaTag } from "#app/data/arena-tag"; -import type { BattlerIndex } from "#app/battle"; +import { ArenaTrapTag, getArenaTag } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; +import type { BattlerIndex } from "#enums/battler-index"; import { Terrain, TerrainType } from "#app/data/terrain"; import { applyAbAttrs, @@ -37,7 +38,10 @@ import { SpeciesId } from "#enums/species-id"; import { TimeOfDay } from "#enums/time-of-day"; import { TrainerType } from "#enums/trainer-type"; import { AbilityId } from "#enums/ability-id"; -import { SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "#app/data/pokemon-forms"; +import { + SpeciesFormChangeRevertWeatherFormTrigger, + SpeciesFormChangeWeatherTrigger, +} from "#app/data/pokemon-forms/form-change-triggers"; import { WeatherType } from "#enums/weather-type"; import { FieldEffectModifier } from "#app/modifier/modifier"; diff --git a/src/field/damage-number-handler.ts b/src/field/damage-number-handler.ts index bfb85018dd6..b8b3ed76e18 100644 --- a/src/field/damage-number-handler.ts +++ b/src/field/damage-number-handler.ts @@ -1,9 +1,9 @@ import { TextStyle, addTextObject } from "../ui/text"; import type { DamageResult } from "./pokemon"; import type Pokemon from "./pokemon"; -import { HitResult } from "./pokemon"; +import { HitResult } from "#enums/hit-result"; import { formatStat, fixedInt } from "#app/utils/common"; -import type { BattlerIndex } from "../battle"; +import type { BattlerIndex } from "#enums/battler-index"; import { globalScene } from "#app/global-scene"; type TextAndShadowArr = [string | null, string | null]; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 71b21076ae6..7032cd06131 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -9,36 +9,8 @@ import BattleInfo from "#app/ui/battle-info/battle-info"; import { EnemyBattleInfo } from "#app/ui/battle-info/enemy-battle-info"; import { PlayerBattleInfo } from "#app/ui/battle-info/player-battle-info"; import type Move from "#app/data/moves/move"; -import { - HighCritAttr, - HitsTagAttr, - applyMoveAttrs, - FixedDamageAttr, - VariableAtkAttr, - TypelessAttr, - CritOnlyAttr, - getMoveTargets, - OneHitKOAttr, - VariableMoveTypeAttr, - VariableDefAttr, - AttackMove, - ModifiedDamageAttr, - VariableMoveTypeMultiplierAttr, - IgnoreOpponentStatStagesAttr, - SacrificialAttr, - VariableMoveCategoryAttr, - CounterDamageAttr, - StatStageChangeAttr, - RechargeAttr, - IgnoreWeatherTypeDebuffAttr, - BypassBurnDamageReductionAttr, - SacrificialAttrOnHit, - OneHitKOAccuracyAttr, - RespectAttackTypeImmunityAttr, - CombinedPledgeStabBoostAttr, - VariableMoveTypeChartAttr, - HpSplitAttr, -} from "#app/data/moves/move"; +import { getMoveTargets } from "#app/data/moves/move-utils"; +import { applyMoveAttrs } from "#app/data/moves/apply-attrs"; import { allMoves } from "#app/data/data-lists"; import { MoveTarget } from "#enums/MoveTarget"; import { MoveCategory } from "#enums/MoveCategory"; @@ -116,7 +88,6 @@ import { import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms"; import { BattlerTag, - BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, @@ -135,8 +106,10 @@ import { loadBattlerTag, type GrudgeTag, } from "../data/battler-tags"; +import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { WeatherType } from "#enums/weather-type"; -import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; +import { NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import type { SuppressAbilitiesTag } from "#app/data/arena-tag"; import type { Ability } from "#app/data/abilities/ability-class"; import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr"; @@ -195,7 +168,7 @@ import { } from "#app/data/abilities/ability"; import { allAbilities } from "#app/data/data-lists"; import type PokemonData from "#app/system/pokemon-data"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { UiMode } from "#enums/ui-mode"; import type { PartyOption } from "#app/ui/party-ui-handler"; import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler"; @@ -204,7 +177,7 @@ import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; import { EVOLVE_MOVE, RELEARN_MOVE } from "#app/data/balance/pokemon-level-moves"; import { achvs } from "#app/system/achv"; import type { StarterDataEntry, StarterMoveset } from "#app/system/game-data"; -import { DexAttr } from "#app/system/game-data"; +import { DexAttr } from "#enums/dex-attr"; import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities"; import { getNatureStatMultiplier } from "#app/data/nature"; import type { SpeciesFormChange } from "#app/data/pokemon-forms"; @@ -213,14 +186,15 @@ import { SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, -} from "#app/data/pokemon-forms"; +} from "#app/data/pokemon-forms/form-change-triggers"; import { TerrainType } from "#app/data/terrain"; import type { TrainerSlot } from "#enums/trainer-slot"; import Overrides from "#app/overrides"; import i18next from "i18next"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { ModifierTier } from "#app/modifier/modifier-tier"; -import { applyChallenges, ChallengeType } from "#app/data/challenge"; +import { applyChallenges } from "#app/data/challenge"; +import { ChallengeType } from "#enums/challenge-type"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattleSpec } from "#enums/battle-spec"; @@ -249,21 +223,12 @@ import { doShinySparkleAnim } from "#app/field/anims"; import { MoveFlags } from "#enums/MoveFlags"; import { timedEventManager } from "#app/global-event-manager"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; - -export enum LearnMoveSituation { - MISC, - LEVEL_UP, - RELEARN, - EVOLUTION, - EVOLUTION_FUSED, // If fusionSpecies has Evolved - EVOLUTION_FUSED_BASE, // If fusion's base species has Evolved -} - -export enum FieldPosition { - CENTER, - LEFT, - RIGHT, -} +import { FieldPosition } from "#enums/field-position"; +import { LearnMoveSituation } from "#enums/learn-move-situation"; +import { HitResult } from "#enums/hit-result"; +import { AiType } from "#enums/ai-type"; +import type { MoveResult } from "#enums/move-result"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; /** Base typeclass for damage parameter methods, used for DRY */ type damageParams = { @@ -1435,7 +1400,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ getCritStage(source: Pokemon, move: Move): number { const critStage = new NumberHolder(0); - applyMoveAttrs(HighCritAttr, source, this, move, critStage); + applyMoveAttrs("HighCritAttr", source, this, move, critStage); globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage); globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage); applyAbAttrs(BonusCritAbAttr, source, null, false, critStage); @@ -1461,7 +1426,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ getMoveCategory(target: Pokemon, move: Move): MoveCategory { const moveCategory = new NumberHolder(move.category); - applyMoveAttrs(VariableMoveCategoryAttr, this, target, move, moveCategory); + applyMoveAttrs("VariableMoveCategoryAttr", this, target, move, moveCategory); return moveCategory.value; } @@ -2356,7 +2321,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public getMoveType(move: Move, simulated = true): PokemonType { const moveTypeHolder = new NumberHolder(move.type); - applyMoveAttrs(VariableMoveTypeAttr, this, null, move, moveTypeHolder); + applyMoveAttrs("VariableMoveTypeAttr", this, null, move, moveTypeHolder); applyPreAttackAbAttrs(MoveTypeChangeAbAttr, this, null, move, simulated, moveTypeHolder); // If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type, @@ -2400,18 +2365,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.turnData?.moveEffectiveness; } - if (move.hasAttr(TypelessAttr)) { + if (move.hasAttr("TypelessAttr")) { return 1; } const moveType = source.getMoveType(move); const typeMultiplier = new NumberHolder( - move.category !== MoveCategory.STATUS || move.hasAttr(RespectAttackTypeImmunityAttr) + move.category !== MoveCategory.STATUS || move.hasAttr("RespectAttackTypeImmunityAttr") ? this.getAttackTypeEffectiveness(moveType, source, false, simulated, move, useIllusion) : 1, ); - applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); + applyMoveAttrs("VariableMoveTypeMultiplierAttr", source, this, move, typeMultiplier); if (this.getTypes(true, true).find(t => move.isTypeImmune(source, this, t))) { typeMultiplier.value = 0; } @@ -2438,7 +2403,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const immuneTags = this.findTags(tag => tag instanceof TypeImmuneTag && tag.immuneType === moveType); for (const tag of immuneTags) { - if (move && !move.getAttrs(HitsTagAttr).some(attr => attr.tagType === tag.tagType)) { + if (move && !move.getAttrs("HitsTagAttr").some(attr => attr.tagType === tag.tagType)) { typeMultiplier.value = 0; break; } @@ -2494,7 +2459,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const multiplier = new NumberHolder(getTypeDamageMultiplier(moveType, defType)); applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, multiplier); if (move) { - applyMoveAttrs(VariableMoveTypeChartAttr, null, this, move, multiplier, defType); + applyMoveAttrs("VariableMoveTypeChartAttr", null, this, move, multiplier, defType); } if (source) { const ignoreImmunity = new BooleanHolder(false); @@ -3130,24 +3095,26 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Bosses never get self ko moves or Pain Split if (this.isBoss()) { - movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(SacrificialAttr) && !allMoves[m[0]].hasAttr(HpSplitAttr)); + movePool = movePool.filter( + m => !allMoves[m[0]].hasAttr("SacrificialAttr") && !allMoves[m[0]].hasAttr("HpSplitAttr"), + ); } // No one gets Memento or Final Gambit - movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(SacrificialAttrOnHit)); + movePool = movePool.filter(m => !allMoves[m[0]].hasAttr("SacrificialAttrOnHit")); if (this.hasTrainer()) { // Trainers never get OHKO moves - movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(OneHitKOAttr)); + movePool = movePool.filter(m => !allMoves[m[0]].hasAttr("OneHitKOAttr")); // Half the weight of self KO moves - movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].hasAttr(SacrificialAttr) ? 0.5 : 1)]); + movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].hasAttr("SacrificialAttr") ? 0.5 : 1)]); // Trainers get a weight bump to stat buffing moves movePool = movePool.map(m => [ m[0], - m[1] * (allMoves[m[0]].getAttrs(StatStageChangeAttr).some(a => a.stages > 1 && a.selfTarget) ? 1.25 : 1), + m[1] * (allMoves[m[0]].getAttrs("StatStageChangeAttr").some(a => a.stages > 1 && a.selfTarget) ? 1.25 : 1), ]); // Trainers get a weight decrease to multiturn moves movePool = movePool.map(m => [ m[0], - m[1] * (!!allMoves[m[0]].isChargingMove() || !!allMoves[m[0]].hasAttr(RechargeAttr) ? 0.7 : 1), + m[1] * (!!allMoves[m[0]].isChargingMove() || !!allMoves[m[0]].hasAttr("RechargeAttr") ? 0.7 : 1), ]); } @@ -3214,7 +3181,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { !this.moveset.some( mo => m[0] === mo.moveId || - (allMoves[m[0]].hasAttr(SacrificialAttr) && mo.getMove().hasAttr(SacrificialAttr)), // Only one self-KO move allowed + (allMoves[m[0]].hasAttr("SacrificialAttr") && mo.getMove().hasAttr("SacrificialAttr")), // Only one self-KO move allowed ), ) .map(m => { @@ -3243,7 +3210,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { !this.moveset.some( mo => m[0] === mo.moveId || - (allMoves[m[0]].hasAttr(SacrificialAttr) && mo.getMove().hasAttr(SacrificialAttr)), // Only one self-KO move allowed + (allMoves[m[0]].hasAttr("SacrificialAttr") && mo.getMove().hasAttr("SacrificialAttr")), // Only one self-KO move allowed ), ); } @@ -3451,7 +3418,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyAbAttrs(IgnoreOpponentStatStagesAbAttr, opponent, null, simulated, stat, ignoreStatStage); } if (move) { - applyMoveAttrs(IgnoreOpponentStatStagesAttr, this, opponent, move, ignoreStatStage); + applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, opponent, move, ignoreStatStage); } } @@ -3476,7 +3443,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns The calculated accuracy multiplier. */ getAccuracyMultiplier(target: Pokemon, sourceMove: Move): number { - const isOhko = sourceMove.hasAttr(OneHitKOAccuracyAttr); + const isOhko = sourceMove.hasAttr("OneHitKOAccuracyAttr"); if (isOhko) { return 1; } @@ -3489,7 +3456,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyAbAttrs(IgnoreOpponentStatStagesAbAttr, target, null, false, Stat.ACC, ignoreAccStatStage); applyAbAttrs(IgnoreOpponentStatStagesAbAttr, this, null, false, Stat.EVA, ignoreEvaStatStage); - applyMoveAttrs(IgnoreOpponentStatStagesAttr, this, target, sourceMove, ignoreEvaStatStage); + applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage); globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage); @@ -3572,7 +3539,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { simulated, ), ); - applyMoveAttrs(VariableAtkAttr, source, this, move, sourceAtk); + applyMoveAttrs("VariableAtkAttr", source, this, move, sourceAtk); /** * This Pokemon's defensive stat for the given move's category. @@ -3590,7 +3557,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { simulated, ), ); - applyMoveAttrs(VariableDefAttr, source, this, move, targetDef); + applyMoveAttrs("VariableDefAttr", source, this, move, targetDef); /** * The attack's base damage, as determined by the source's level, move power @@ -3617,7 +3584,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ calculateStabMultiplier(source: Pokemon, move: Move, ignoreSourceAbility: boolean, simulated: boolean): number { // If the move has the Typeless attribute, it doesn't get STAB (e.g. struggle) - if (move.hasAttr(TypelessAttr)) { + if (move.hasAttr("TypelessAttr")) { return 1; } const sourceTypes = source.getTypes(); @@ -3629,7 +3596,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { stabMultiplier.value += 0.5; } - applyMoveAttrs(CombinedPledgeStabBoostAttr, source, this, move, stabMultiplier); + applyMoveAttrs("CombinedPledgeStabBoostAttr", source, this, move, stabMultiplier); if (!ignoreSourceAbility) { applyAbAttrs(StabBoostAbAttr, source, null, simulated, stabMultiplier); @@ -3678,7 +3645,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const variableCategory = new NumberHolder(move.category); - applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, variableCategory); + applyMoveAttrs("VariableMoveCategoryAttr", source, this, move, variableCategory); const moveCategory = variableCategory.value as MoveCategory; /** The move's type after type-changing effects are applied */ @@ -3703,7 +3670,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const arenaAttackTypeMultiplier = new NumberHolder( globalScene.arena.getAttackTypeMultiplier(moveType, source.isGrounded()), ); - applyMoveAttrs(IgnoreWeatherTypeDebuffAttr, source, this, move, arenaAttackTypeMultiplier); + applyMoveAttrs("IgnoreWeatherTypeDebuffAttr", source, this, move, arenaAttackTypeMultiplier); const isTypeImmune = typeMultiplier * arenaAttackTypeMultiplier.value === 0; @@ -3717,7 +3684,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // If the attack deals fixed damage, return a result with that much damage const fixedDamage = new NumberHolder(0); - applyMoveAttrs(FixedDamageAttr, source, this, move, fixedDamage); + applyMoveAttrs("FixedDamageAttr", source, this, move, fixedDamage); if (fixedDamage.value) { const multiLensMultiplier = new NumberHolder(1); globalScene.applyModifiers( @@ -3739,7 +3706,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // If the attack is a one-hit KO move, return a result with damage equal to this Pokemon's HP const isOneHitKo = new BooleanHolder(false); - applyMoveAttrs(OneHitKOAttr, source, this, move, isOneHitKo); + applyMoveAttrs("OneHitKOAttr", source, this, move, isOneHitKo); if (isOneHitKo.value) { return { cancelled: false, @@ -3816,7 +3783,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { isPhysical && source.status && source.status.effect === StatusEffect.BURN && - !move.hasAttr(BypassBurnDamageReductionAttr) + !move.hasAttr("BypassBurnDamageReductionAttr") ) { const burnDamageReductionCancelled = new BooleanHolder(false); if (!ignoreSourceAbility) { @@ -3850,7 +3817,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ const hitsTagMultiplier = new NumberHolder(1); move - .getAttrs(HitsTagAttr) + .getAttrs("HitsTagAttr") .filter(hta => hta.doubleDamage) .forEach(hta => { if (this.getTag(hta.tagType)) { @@ -3907,7 +3874,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } // This attribute may modify damage arbitrarily, so be careful about changing its order of application. - applyMoveAttrs(ModifiedDamageAttr, source, this, move, damage); + applyMoveAttrs("ModifiedDamageAttr", source, this, move, damage); if (this.isFullHp() && !ignoreAbility) { applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, move, cancelled, false, damage); @@ -3943,7 +3910,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getCriticalHitResult(source: Pokemon, move: Move, simulated = true): boolean { const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide); - if (noCritTag || Overrides.NEVER_CRIT_OVERRIDE || move.hasAttr(FixedDamageAttr)) { + if (noCritTag || Overrides.NEVER_CRIT_OVERRIDE || move.hasAttr("FixedDamageAttr")) { return false; } const isCritical = new BooleanHolder(false); @@ -3951,7 +3918,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (source.getTag(BattlerTagType.ALWAYS_CRIT)) { isCritical.value = true; } - applyMoveAttrs(CritOnlyAttr, source, this, move, isCritical); + applyMoveAttrs("CritOnlyAttr", source, this, move, isCritical); applyAbAttrs(ConditionalCritAbAttr, source, null, simulated, isCritical, this, move); if (!isCritical.value) { const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))]; @@ -6272,7 +6239,7 @@ export class EnemyPokemon extends Pokemon { .targets.map(ind => fieldPokemon[ind]) .filter(p => this.isPlayer() !== p.isPlayer()); // Only considers critical hits for crit-only moves or when this Pokemon is under the effect of Laser Focus - const isCritical = move.hasAttr(CritOnlyAttr) || !!this.getTag(BattlerTagType.ALWAYS_CRIT); + const isCritical = move.hasAttr("CritOnlyAttr") || !!this.getTag(BattlerTagType.ALWAYS_CRIT); return ( move.category !== MoveCategory.STATUS && @@ -6341,7 +6308,7 @@ export class EnemyPokemon extends Pokemon { ![MoveId.SUCKER_PUNCH, MoveId.UPPER_HAND, MoveId.THUNDERCLAP].includes(move.id) ) { targetScore = -20; - } else if (move instanceof AttackMove) { + } else if (move.is("AttackMove")) { /** * Attack moves are given extra multipliers to their base benefit score based on * the move's type effectiveness against the target and whether the move is a STAB move. @@ -6462,7 +6429,7 @@ export class EnemyPokemon extends Pokemon { if (!sortedBenefitScores.length) { // Set target to BattlerIndex.ATTACKER when using a counter move // This is the same as when the player does so - if (move.hasAttr(CounterDamageAttr)) { + if (move.hasAttr("CounterDamageAttr")) { return [BattlerIndex.ATTACKER]; } @@ -6945,36 +6912,6 @@ export class PokemonTurnData { public berriesEaten: BerryType[] = []; } -export enum AiType { - RANDOM, - SMART_RANDOM, - SMART, -} - -export enum MoveResult { - PENDING, - SUCCESS, - FAIL, - MISS, - OTHER, -} - -export enum HitResult { - EFFECTIVE = 1, - SUPER_EFFECTIVE, - NOT_VERY_EFFECTIVE, - ONE_HIT_KO, - NO_EFFECT, - STATUS, - HEAL, - FAIL, - MISS, - INDIRECT, - IMMUNE, - CONFUSION, - INDIRECT_KO, -} - export type DamageResult = | HitResult.EFFECTIVE | HitResult.SUPER_EFFECTIVE @@ -6993,91 +6930,3 @@ export interface DamageCalculationResult { /** The damage dealt by the move */ damage: number; } - -/** - * Wrapper class for the {@linkcode Move} class for Pokemon to interact with. - * These are the moves assigned to a {@linkcode Pokemon} object. - * It links to {@linkcode Move} class via the move ID. - * Compared to {@linkcode Move}, this class also tracks things like - * PP Ups recieved, PP used, etc. - * @see {@linkcode isUsable} - checks if move is restricted, out of PP, or not implemented. - * @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID. - * @see {@linkcode usePp} - removes a point of PP from the move. - * @see {@linkcode getMovePp} - returns amount of PP a move currently has. - * @see {@linkcode getPpRatio} - returns the current PP amount / max PP amount. - * @see {@linkcode getName} - returns name of {@linkcode Move}. - **/ -export class PokemonMove { - public moveId: MoveId; - public ppUsed: number; - public ppUp: number; - public virtual: boolean; - - /** - * If defined and nonzero, overrides the maximum PP of the move (e.g., due to move being copied by Transform). - * This also nullifies all effects of `ppUp`. - */ - public maxPpOverride?: number; - - constructor(moveId: MoveId, ppUsed = 0, ppUp = 0, virtual = false, maxPpOverride?: number) { - this.moveId = moveId; - this.ppUsed = ppUsed; - this.ppUp = ppUp; - this.virtual = virtual; - this.maxPpOverride = maxPpOverride; - } - - /** - * Checks whether the move can be selected or performed by a Pokemon, without consideration for the move's targets. - * The move is unusable if it is out of PP, restricted by an effect, or unimplemented. - * - * @param pokemon - {@linkcode Pokemon} that would be using this move - * @param ignorePp - If `true`, skips the PP check - * @param ignoreRestrictionTags - If `true`, skips the check for move restriction tags (see {@link MoveRestrictionBattlerTag}) - * @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`. - */ - isUsable(pokemon: Pokemon, ignorePp = false, ignoreRestrictionTags = false): boolean { - if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon)) { - return false; - } - - if (this.getMove().name.endsWith(" (N)")) { - return false; - } - - return ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1; - } - - getMove(): Move { - return allMoves[this.moveId]; - } - - /** - * Sets {@link ppUsed} for this move and ensures the value does not exceed {@link getMovePp} - * @param count Amount of PP to use - */ - usePp(count = 1) { - this.ppUsed = Math.min(this.ppUsed + count, this.getMovePp()); - } - - getMovePp(): number { - return this.maxPpOverride || this.getMove().pp + this.ppUp * toDmgValue(this.getMove().pp / 5); - } - - getPpRatio(): number { - return 1 - this.ppUsed / this.getMovePp(); - } - - getName(): string { - return this.getMove().name; - } - - /** - * Copies an existing move or creates a valid {@linkcode PokemonMove} object from json representing one - * @param source The data for the move to copy; can be a {@linkcode PokemonMove} or JSON object representing one - * @returns A valid {@linkcode PokemonMove} object - */ - static loadMove(source: PokemonMove | any): PokemonMove { - return new PokemonMove(source.moveId, source.ppUsed, source.ppUp, source.virtual, source.maxPpOverride); - } -} diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 244a23185da..8d950b08507 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -13,19 +13,15 @@ import { TeraAIMode } from "#enums/tera-ai-mode"; import type { EnemyPokemon } from "#app/field/pokemon"; import { randSeedWeightedItem, randSeedItem, randSeedInt } from "#app/utils/common"; import type { PersistentModifier } from "#app/modifier/modifier"; -import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; +import { ArenaTrapTag } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import { getIsInitialized, initI18n } from "#app/plugins/i18n"; import i18next from "i18next"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; import { signatureSpecies } from "#app/data/balance/signature-species"; - -export enum TrainerVariant { - DEFAULT, - FEMALE, - DOUBLE, -} +import { TrainerVariant } from "#enums/trainer-variant"; export default class Trainer extends Phaser.GameObjects.Container { public config: TrainerConfig; diff --git a/src/game-mode.ts b/src/game-mode.ts index 7ad8a6a83e9..a6fc8195175 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -2,7 +2,8 @@ import i18next from "i18next"; import type { FixedBattleConfigs } from "./battle"; import { classicFixedBattles, FixedBattleConfig } from "./battle"; import type { Challenge } from "./data/challenge"; -import { allChallenges, applyChallenges, ChallengeType, copyChallenge } from "./data/challenge"; +import { allChallenges, applyChallenges, copyChallenge } from "./data/challenge"; +import { ChallengeType } from "#enums/challenge-type"; import type PokemonSpecies from "./data/pokemon-species"; import { allSpecies } from "./data/pokemon-species"; import type { Arena } from "./field/arena"; @@ -14,14 +15,7 @@ import { Challenges } from "./enums/challenges"; import { globalScene } from "#app/global-scene"; import { getDailyStartingBiome } from "./data/daily-run"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES, CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES } from "./constants"; - -export enum GameModes { - CLASSIC, - ENDLESS, - SPLICED_ENDLESS, - DAILY, - CHALLENGE, -} +import { GameModes } from "#enums/game-modes"; interface GameModeConfig { isClassic?: boolean; diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index ccbc202407b..b37ed7dea3b 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -2,19 +2,16 @@ import { globalScene } from "#app/global-scene"; import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { tmPoolTiers, tmSpecies } from "#app/data/balance/tms"; import { getBerryEffectDescription, getBerryName } from "#app/data/berry"; -import { AttackMove } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { getNatureName, getNatureStatMultiplier } from "#app/data/nature"; import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; -import { - FormChangeItem, - pokemonFormChanges, - SpeciesFormChangeCondition, - SpeciesFormChangeItemTrigger, -} from "#app/data/pokemon-forms"; +import { pokemonFormChanges, SpeciesFormChangeCondition } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers"; +import { FormChangeItem } from "#enums/form-change-item"; import { getStatusEffectDescriptor } from "#app/data/status-effect"; import { PokemonType } from "#enums/pokemon-type"; -import type { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { @@ -1339,7 +1336,7 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator { p .getMoveset() .map(m => m.getMove()) - .filter(m => m instanceof AttackMove) + .filter(m => m.is("AttackMove")) .map(m => m.type), ); if (!attackMoveTypes.length) { diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index b6f96db751a..bfea061b1e8 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -3,14 +3,15 @@ import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry"; import { getLevelTotalExp } from "#app/data/exp"; import { allMoves } from "#app/data/data-lists"; import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; -import { type FormChangeItem, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers"; +import type { FormChangeItem } from "#enums/form-change-item"; import { getStatusEffectHealText } from "#app/data/status-effect"; import Pokemon, { type PlayerPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; -import { LearnMoveType } from "#app/phases/learn-move-phase"; +import { LearnMoveType } from "#enums/learn-move-type"; import type { VoucherType } from "#app/system/voucher"; -import { Command } from "#app/ui/command-ui-handler"; +import { Command } from "#enums/command"; import { addTextObject, TextStyle } from "#app/ui/text"; import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, randSeedFloat, toDmgValue } from "#app/utils/common"; import { BattlerTagType } from "#enums/battler-tag-type"; diff --git a/src/overrides.ts b/src/overrides.ts index 86e1708248d..1fbca8e8de1 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -1,7 +1,7 @@ import { type PokeballCounts } from "#app/battle-scene"; import { EvolutionItem } from "#app/data/balance/pokemon-evolutions"; import { Gender } from "#app/data/gender"; -import { FormChangeItem } from "#app/data/pokemon-forms"; +import { FormChangeItem } from "#enums/form-change-item"; import { type ModifierOverride } from "#app/modifier/modifier-type"; import { Variant } from "#app/sprites/variant"; import { Unlockables } from "#app/system/unlockables"; diff --git a/src/phases/attempt-capture-phase.ts b/src/phases/attempt-capture-phase.ts index 4f3f54a7e5b..f4e6725935a 100644 --- a/src/phases/attempt-capture-phase.ts +++ b/src/phases/attempt-capture-phase.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; import { SubstituteTag } from "#app/data/battler-tags"; import { diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index 6e40e299e7c..cc990d1e2ae 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -4,7 +4,7 @@ import { HealFromBerryUseAbAttr, RepeatBerryNextTurnAbAttr, } from "#app/data/abilities/ability"; -import { CommonAnim } from "#app/data/battle-anims"; +import { CommonAnim } from "#enums/move-anims-common"; import { BerryUsedEvent } from "#app/events/battle-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { BerryModifier } from "#app/modifier/modifier"; diff --git a/src/phases/check-status-effect-phase.ts b/src/phases/check-status-effect-phase.ts index e4793fae076..43495e038e9 100644 --- a/src/phases/check-status-effect-phase.ts +++ b/src/phases/check-status-effect-phase.ts @@ -1,5 +1,5 @@ import { Phase } from "#app/phase"; -import type { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#enums/battler-index"; import { globalScene } from "#app/global-scene"; export class CheckStatusEffectPhase extends Phase { diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index afd9cb3bf93..d7264b4aff2 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -4,7 +4,7 @@ import { BattleType } from "#enums/battle-type"; import type { EncoreTag } from "#app/data/battler-tags"; import { TrappedTag } from "#app/data/battler-tags"; import type { MoveTargetSet } from "#app/data/moves/move"; -import { getMoveTargets } from "#app/data/moves/move"; +import { getMoveTargets } from "#app/data/moves/move-utils"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#app/enums/battler-tag-type"; @@ -12,15 +12,15 @@ import { BiomeId } from "#enums/biome-id"; import { MoveId } from "#enums/move-id"; import { PokeballType } from "#enums/pokeball"; import type { PlayerPokemon, TurnMove } from "#app/field/pokemon"; -import { FieldPosition } from "#app/field/pokemon"; +import { FieldPosition } from "#enums/field-position"; import { getPokemonNameWithAffix } from "#app/messages"; -import { Command } from "#app/ui/command-ui-handler"; +import { Command } from "#enums/command"; import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; import { FieldPhase } from "./field-phase"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { isNullOrUndefined } from "#app/utils/common"; -import { ArenaTagSide } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagType } from "#app/enums/arena-tag-type"; export class CommandPhase extends FieldPhase { diff --git a/src/phases/common-anim-phase.ts b/src/phases/common-anim-phase.ts index 4a27db3a651..abfe8ed99f0 100644 --- a/src/phases/common-anim-phase.ts +++ b/src/phases/common-anim-phase.ts @@ -1,6 +1,6 @@ -import type { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#enums/battler-index"; import { globalScene } from "#app/global-scene"; -import type { CommonAnim } from "#app/data/battle-anims"; +import type { CommonAnim } from "#enums/move-anims-common"; import { CommonBattleAnim } from "#app/data/battle-anims"; import { PokemonPhase } from "./pokemon-phase"; diff --git a/src/phases/damage-anim-phase.ts b/src/phases/damage-anim-phase.ts index 85cb26e0a09..aa5a0a6c3e6 100644 --- a/src/phases/damage-anim-phase.ts +++ b/src/phases/damage-anim-phase.ts @@ -1,7 +1,8 @@ import { globalScene } from "#app/global-scene"; -import type { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#enums/battler-index"; import { BattleSpec } from "#enums/battle-spec"; -import { type DamageResult, HitResult } from "#app/field/pokemon"; +import type { DamageResult } from "#app/field/pokemon"; +import { HitResult } from "#enums/hit-result"; import { fixedInt } from "#app/utils/common"; import { PokemonPhase } from "#app/phases/pokemon-phase"; diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index e3b33122ac2..74623f947ee 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { BattleType } from "#enums/battle-type"; import { globalScene } from "#app/global-scene"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; @@ -17,7 +17,7 @@ import { TrainerSlot } from "#enums/trainer-slot"; import { getRandomWeatherType } from "#app/data/weather"; import { EncounterPhaseEvent } from "#app/events/battle-scene"; import type Pokemon from "#app/field/pokemon"; -import { FieldPosition } from "#app/field/pokemon"; +import { FieldPosition } from "#enums/field-position"; import { getPokemonNameWithAffix } from "#app/messages"; import { BoostBugSpawnModifier, IvScannerModifier, TurnHeldItemTransferModifier } from "#app/modifier/modifier"; import { ModifierPoolType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; diff --git a/src/phases/enemy-command-phase.ts b/src/phases/enemy-command-phase.ts index a81fc4d2107..0dc41a592e0 100644 --- a/src/phases/enemy-command-phase.ts +++ b/src/phases/enemy-command-phase.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; -import { BattlerIndex } from "#app/battle"; -import { Command } from "#app/ui/command-ui-handler"; +import { BattlerIndex } from "#enums/battler-index"; +import { Command } from "#enums/command"; import { FieldPhase } from "./field-phase"; import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#enums/battler-tag-type"; diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index eaedb6d32b0..bcc93b028bd 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -10,7 +10,7 @@ import { UiMode } from "#enums/ui-mode"; import { cos, sin } from "#app/field/anims"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { LearnMoveSituation } from "#app/field/pokemon"; +import { LearnMoveSituation } from "#enums/learn-move-situation"; import { getTypeRgb } from "#app/data/type"; import i18next from "i18next"; import { getPokemonNameWithAffix } from "#app/messages"; diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 38376af4356..ca23b20be12 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -1,4 +1,4 @@ -import type { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#enums/battler-index"; import { BattleType } from "#enums/battle-type"; import { globalScene } from "#app/global-scene"; import { @@ -9,16 +9,16 @@ import { PostKnockOutAbAttr, PostVictoryAbAttr, } from "#app/data/abilities/ability"; -import { BattlerTagLapseType } from "#app/data/battler-tags"; +import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { battleSpecDialogue } from "#app/data/dialogue"; -import { PostVictoryStatStageChangeAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; -import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { BattleSpec } from "#app/enums/battle-spec"; import { StatusEffect } from "#app/enums/status-effect"; import type { EnemyPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { HitResult, PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { HitResult } from "#enums/hit-result"; import type { PlayerPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonInstantReviveModifier } from "#app/modifier/modifier"; @@ -144,7 +144,7 @@ export class FaintPhase extends PokemonPhase { if (defeatSource?.isOnField()) { applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource); const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move]; - const pvattrs = pvmove.getAttrs(PostVictoryStatStageChangeAttr); + const pvattrs = pvmove.getAttrs("PostVictoryStatStageChangeAttr"); if (pvattrs.length) { for (const pvattr of pvattrs) { pvattr.applyPostVictory(defeatSource, defeatSource, pvmove); diff --git a/src/phases/form-change-phase.ts b/src/phases/form-change-phase.ts index 3813359d432..13cd410ef87 100644 --- a/src/phases/form-change-phase.ts +++ b/src/phases/form-change-phase.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import { fixedInt } from "#app/utils/common"; import { achvs } from "../system/achv"; import type { SpeciesFormChange } from "../data/pokemon-forms"; -import { getSpeciesFormChangeMessage } from "../data/pokemon-forms"; +import { getSpeciesFormChangeMessage } from "#app/data/pokemon-forms/form-change-triggers"; import type { PlayerPokemon } from "../field/pokemon"; import { UiMode } from "#enums/ui-mode"; import type PartyUiHandler from "../ui/party-ui-handler"; diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts index 7464cebe7da..e24efa63b5a 100644 --- a/src/phases/learn-move-phase.ts +++ b/src/phases/learn-move-phase.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; import type Move from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; -import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { MoveId } from "#enums/move-id"; import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; @@ -12,15 +12,7 @@ import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase"; import type Pokemon from "#app/field/pokemon"; - -export enum LearnMoveType { - /** For learning a move via level-up, evolution, or other non-item-based event */ - LEARN_MOVE, - /** For learning a move via Memory Mushroom */ - MEMORY, - /** For learning a move via TM */ - TM, -} +import { LearnMoveType } from "#enums/learn-move-type"; export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { public readonly phaseName = "LearnMovePhase"; diff --git a/src/phases/move-charge-phase.ts b/src/phases/move-charge-phase.ts index a481f4e37b8..15a98ebabd2 100644 --- a/src/phases/move-charge-phase.ts +++ b/src/phases/move-charge-phase.ts @@ -1,10 +1,10 @@ import { globalScene } from "#app/global-scene"; -import type { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#enums/battler-index"; import { MoveChargeAnim } from "#app/data/battle-anims"; -import { applyMoveChargeAttrs, MoveEffectAttr, InstantChargeAttr } from "#app/data/moves/move"; -import type { PokemonMove } from "#app/field/pokemon"; +import { applyMoveChargeAttrs } from "#app/data/moves/apply-attrs"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { BooleanHolder } from "#app/utils/common"; import { PokemonPhase } from "#app/phases/pokemon-phase"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -43,7 +43,7 @@ export class MoveChargePhase extends PokemonPhase { new MoveChargeAnim(move.chargeAnim, move.id, user).play(false, () => { move.showChargeText(user, target); - applyMoveChargeAttrs(MoveEffectAttr, user, target, move); + applyMoveChargeAttrs("MoveEffectAttr", user, target, move); user.addTag(BattlerTagType.CHARGING, 1, move.id, user.id); this.end(); }); @@ -57,7 +57,7 @@ export class MoveChargePhase extends PokemonPhase { if (move.isChargingMove()) { const instantCharge = new BooleanHolder(false); - applyMoveChargeAttrs(InstantChargeAttr, user, null, move, instantCharge); + applyMoveChargeAttrs("InstantChargeAttr", user, null, move, instantCharge); if (instantCharge.value) { // this MoveEndPhase will be duplicated by the queued MovePhase if not removed diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 84072b393f1..68c96ddce1e 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { globalScene } from "#app/global-scene"; import { AddSecondStrikeAbAttr, @@ -16,43 +16,31 @@ import { PostDefendAbAttr, ReflectStatusMoveAbAttr, } from "#app/data/abilities/ability"; -import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag"; +import { ConditionalProtectTag } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import { MoveAnim } from "#app/data/battle-anims"; import { - BattlerTagLapseType, DamageProtectedTag, ProtectedTag, SemiInvulnerableTag, SubstituteTag, TypeBoostTag, } from "#app/data/battler-tags"; +import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import type { MoveAttr } from "#app/data/moves/move"; -import { - applyFilteredMoveAttrs, - applyMoveAttrs, - AttackMove, - DelayedAttackAttr, - FlinchAttr, - getMoveTargets, - HitsTagAttr, - MissEffectAttr, - MoveEffectAttr, - MultiHitAttr, - NoEffectAttr, - OneHitKOAttr, - OverrideMoveEffectAttr, - StatChangeBeforeDmgCalcAttr, - ToxicAccuracyAttr, -} from "#app/data/moves/move"; +import { getMoveTargets } from "#app/data/moves/move-utils"; +import { applyFilteredMoveAttrs, applyMoveAttrs } from "#app/data/moves/apply-attrs"; import { MoveEffectTrigger } from "#enums/MoveEffectTrigger"; import { MoveFlags } from "#enums/MoveFlags"; import { MoveTarget } from "#enums/MoveTarget"; import { MoveCategory } from "#enums/MoveCategory"; -import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { PokemonType } from "#enums/pokemon-type"; -import { type DamageResult, PokemonMove, type TurnMove } from "#app/field/pokemon"; +import type { DamageResult, TurnMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; -import { HitResult, MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; +import { HitResult } from "#enums/hit-result"; import { getPokemonNameWithAffix } from "#app/messages"; import { ContactHeldItemTransferChanceModifier, @@ -238,13 +226,13 @@ export class MoveEffectPhase extends PokemonPhase { case HitCheckResult.NO_EFFECT_NO_MESSAGE: case HitCheckResult.PROTECTED: case HitCheckResult.TARGET_NOT_ON_FIELD: - applyMoveAttrs(NoEffectAttr, user, target, this.move); + applyMoveAttrs("NoEffectAttr", user, target, this.move); break; case HitCheckResult.MISS: globalScene.phaseManager.queueMessage( i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }), ); - applyMoveAttrs(MissEffectAttr, user, target, this.move); + applyMoveAttrs("MissEffectAttr", user, target, this.move); break; case HitCheckResult.REFLECTED: this.queueReflectedMove(user, target); @@ -274,7 +262,7 @@ export class MoveEffectPhase extends PokemonPhase { globalScene.currentBattle.lastPlayerInvolved = this.fieldIndex; } - const isDelayedAttack = this.move.hasAttr(DelayedAttackAttr); + const isDelayedAttack = this.move.hasAttr("DelayedAttackAttr"); /** If the user was somehow removed from the field and it's not a delayed attack, end this phase */ if (!user.isOnField()) { if (!isDelayedAttack) { @@ -300,7 +288,7 @@ export class MoveEffectPhase extends PokemonPhase { const move = this.move; // Assume single target for override - applyMoveAttrs(OverrideMoveEffectAttr, user, this.getFirstTarget() ?? null, move, overridden, this.virtual); + applyMoveAttrs("OverrideMoveEffectAttr", user, this.getFirstTarget() ?? null, move, overridden, this.virtual); // If other effects were overriden, stop this phase before they can be applied if (overridden.value) { @@ -327,7 +315,7 @@ export class MoveEffectPhase extends PokemonPhase { if (user.turnData.hitsLeft === -1) { const hitCount = new NumberHolder(1); // Assume single target for multi hit - applyMoveAttrs(MultiHitAttr, user, this.getFirstTarget() ?? null, move, hitCount); + applyMoveAttrs("MultiHitAttr", user, this.getFirstTarget() ?? null, move, hitCount); // If Parental Bond is applicable, add another hit applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, false, hitCount, null); // If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses @@ -359,7 +347,7 @@ export class MoveEffectPhase extends PokemonPhase { // Play the animation if the move was successful against any of its targets or it has a POST_TARGET effect (like self destruct) if ( this.moveHistoryEntry.result === MoveResult.SUCCESS || - move.getAttrs(MoveEffectAttr).some(attr => attr.trigger === MoveEffectTrigger.POST_TARGET) + move.getAttrs("MoveEffectAttr").some(attr => attr.trigger === MoveEffectTrigger.POST_TARGET) ) { const firstTarget = this.getFirstTarget(); new MoveAnim( @@ -458,7 +446,7 @@ export class MoveEffectPhase extends PokemonPhase { * @returns a function intended to be passed into a `then()` call. */ protected applyHeldItemFlinchCheck(user: Pokemon, target: Pokemon, dealsDamage: boolean): void { - if (this.move.hasAttr(FlinchAttr)) { + if (this.move.hasAttr("FlinchAttr")) { return; } @@ -600,7 +588,7 @@ export class MoveEffectPhase extends PokemonPhase { const bypassAccuracy = bypassAccAndInvuln || target.getTag(BattlerTagType.ALWAYS_GET_HIT) || - (target.getTag(BattlerTagType.TELEKINESIS) && !this.move.hasAttr(OneHitKOAttr)); + (target.getTag(BattlerTagType.TELEKINESIS) && !this.move.hasAttr("OneHitKOAttr")); if (moveAccuracy === -1 || bypassAccuracy) { return [HitCheckResult.HIT, effectiveness]; @@ -641,7 +629,7 @@ export class MoveEffectPhase extends PokemonPhase { if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) { return true; } - if (this.move.hasAttr(ToxicAccuracyAttr) && user.isOfType(PokemonType.POISON)) { + if (this.move.hasAttr("ToxicAccuracyAttr") && user.isOfType(PokemonType.POISON)) { return true; } // TODO: Fix lock on / mind reader check. @@ -666,7 +654,7 @@ export class MoveEffectPhase extends PokemonPhase { return false; } const move = this.move; - return move.getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType); + return move.getAttrs("HitsTagAttr").some(hta => hta.tagType === semiInvulnerableTag.tagType); } /** @returns The {@linkcode Pokemon} using this phase's invoked move */ @@ -757,7 +745,7 @@ export class MoveEffectPhase extends PokemonPhase { ): void { applyFilteredMoveAttrs( (attr: MoveAttr) => - attr instanceof MoveEffectAttr && + attr.is("MoveEffectAttr") && attr.trigger === triggerType && (isNullOrUndefined(selfTarget) || attr.selfTarget === selfTarget) && (!attr.firstHitOnly || this.firstHit) && @@ -820,7 +808,7 @@ export class MoveEffectPhase extends PokemonPhase { * Apply stat changes from {@linkcode move} and gives it to {@linkcode source} * before damage calculation */ - applyMoveAttrs(StatChangeBeforeDmgCalcAttr, user, target, this.move); + applyMoveAttrs("StatChangeBeforeDmgCalcAttr", user, target, this.move); const { result, damage: dmg } = target.getAttackDamage({ source: user, @@ -998,12 +986,12 @@ export class MoveEffectPhase extends PokemonPhase { applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move, hitResult); // We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens - if (!user.isPlayer() && this.move instanceof AttackMove) { + if (!user.isPlayer() && this.move.is("AttackMove")) { globalScene.applyShuffledModifiers(EnemyAttackStatusEffectChanceModifier, false, target); } // Apply Grip Claw's chance to steal an item from the target - if (this.move instanceof AttackMove) { + if (this.move.is("AttackMove")) { globalScene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target); } } diff --git a/src/phases/move-end-phase.ts b/src/phases/move-end-phase.ts index 6642b97773b..e5f87089fae 100644 --- a/src/phases/move-end-phase.ts +++ b/src/phases/move-end-phase.ts @@ -1,7 +1,7 @@ import { globalScene } from "#app/global-scene"; -import { BattlerTagLapseType } from "#app/data/battler-tags"; +import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { PokemonPhase } from "./pokemon-phase"; -import type { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#enums/battler-index"; import { applyPostSummonAbAttrs, PostSummonRemoveEffectAbAttr } from "#app/data/abilities/ability"; import type Pokemon from "#app/field/pokemon"; diff --git a/src/phases/move-header-phase.ts b/src/phases/move-header-phase.ts index 50100e827d6..8c2d184c3f5 100644 --- a/src/phases/move-header-phase.ts +++ b/src/phases/move-header-phase.ts @@ -1,5 +1,5 @@ -import { applyMoveAttrs, MoveHeaderAttr } from "#app/data/moves/move"; -import type { PokemonMove } from "#app/field/pokemon"; +import { applyMoveAttrs } from "#app/data/moves/apply-attrs"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; import { BattlePhase } from "./battle-phase"; @@ -23,7 +23,7 @@ export class MoveHeaderPhase extends BattlePhase { super.start(); if (this.canMove()) { - applyMoveAttrs(MoveHeaderAttr, this.pokemon, null, this.move.getMove()); + applyMoveAttrs("MoveHeaderAttr", this.pokemon, null, this.move.getMove()); } this.end(); } diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 7fc6a86e3f7..ca66ca745e7 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { globalScene } from "#app/global-scene"; import { applyAbAttrs, @@ -12,30 +12,21 @@ import { ReduceStatusEffectDurationAbAttr, } from "#app/data/abilities/ability"; import type { DelayedAttackTag } from "#app/data/arena-tag"; -import { CommonAnim } from "#app/data/battle-anims"; -import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags"; -import { - AddArenaTrapTagAttr, - applyMoveAttrs, - BypassRedirectAttr, - BypassSleepAttr, - CopyMoveAttr, - DelayedAttackAttr, - frenzyMissFunc, - HealStatusEffectAttr, - PreMoveMessageAttr, - PreUseInterruptAttr, -} from "#app/data/moves/move"; +import { CommonAnim } from "#enums/move-anims-common"; +import { CenterOfAttentionTag } from "#app/data/battler-tags"; +import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; +import type { HealStatusEffectAttr } from "#app/data/moves/move"; +import { applyMoveAttrs } from "#app/data/moves/apply-attrs"; import { allMoves } from "#app/data/data-lists"; import { MoveFlags } from "#enums/MoveFlags"; -import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect"; import { PokemonType } from "#enums/pokemon-type"; import { getTerrainBlockMessage, getWeatherBlockMessage } from "#app/data/weather"; import { MoveUsedEvent } from "#app/events/battle-scene"; -import type { PokemonMove } from "#app/field/pokemon"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; import { BattlePhase } from "#app/phases/battle-phase"; @@ -46,6 +37,7 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { MoveId } from "#enums/move-id"; import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; +import { frenzyMissFunc } from "#app/data/moves/move-utils"; export class MovePhase extends BattlePhase { public readonly phaseName = "MovePhase"; @@ -204,7 +196,7 @@ export class MovePhase extends BattlePhase { const moveQueue = this.pokemon.getMoveQueue(); if ( - (targets.length === 0 && !this.move.getMove().hasAttr(AddArenaTrapTagAttr)) || + (targets.length === 0 && !this.move.getMove().hasAttr("AddArenaTrapTagAttr")) || (moveQueue.length && moveQueue[0].move === MoveId.NONE) ) { this.showMoveText(); @@ -233,7 +225,7 @@ export class MovePhase extends BattlePhase { Overrides.STATUS_ACTIVATION_OVERRIDE !== false; break; case StatusEffect.SLEEP: { - applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove()); + applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove()); const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0); applyAbAttrs( ReduceStatusEffectDurationAbAttr, @@ -253,7 +245,10 @@ export class MovePhase extends BattlePhase { !!this.move .getMove() .findAttr( - attr => attr instanceof HealStatusEffectAttr && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE), + attr => + attr.is("HealStatusEffectAttr") && + attr.selfTarget && + (attr as unknown as HealStatusEffectAttr).isOfEffect(StatusEffect.FREEZE), ) || (!this.pokemon.randBattleSeedInt(5) && Overrides.STATUS_ACTIVATION_OVERRIDE !== true) || Overrides.STATUS_ACTIVATION_OVERRIDE === false; @@ -303,7 +298,7 @@ export class MovePhase extends BattlePhase { // form changes happen even before we know that the move wll execute. globalScene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger); - const isDelayedAttack = this.move.getMove().hasAttr(DelayedAttackAttr); + const isDelayedAttack = this.move.getMove().hasAttr("DelayedAttackAttr"); if (isDelayedAttack) { // Check the player side arena if future sight is active const futureSightTags = globalScene.arena.findTags(t => t.tagType === ArenaTagType.FUTURE_SIGHT); @@ -331,7 +326,7 @@ export class MovePhase extends BattlePhase { let success = true; // Check if there are any attributes that can interrupt the move, overriding the fail message. - for (const move of this.move.getMove().getAttrs(PreUseInterruptAttr)) { + for (const move of this.move.getMove().getAttrs("PreUseInterruptAttr")) { if (move.apply(this.pokemon, targets[0], this.move.getMove())) { success = false; break; @@ -386,7 +381,7 @@ export class MovePhase extends BattlePhase { } // Update the battle's "last move" pointer, unless we're currently mimicking a move. - if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) { + if (!allMoves[this.move.moveId].hasAttr("CopyMoveAttr")) { // The last move used is unaffected by moves that fail if (success) { globalScene.currentBattle.lastMove = this.move.moveId; @@ -543,7 +538,7 @@ export class MovePhase extends BattlePhase { }); if (currentTarget !== redirectTarget.value) { - const bypassRedirectAttrs = this.move.getMove().getAttrs(BypassRedirectAttr); + const bypassRedirectAttrs = this.move.getMove().getAttrs("BypassRedirectAttr"); bypassRedirectAttrs.forEach(attr => { if (!attr.abilitiesOnly || redirectedByAbility) { redirectTarget.value = currentTarget; @@ -664,7 +659,7 @@ export class MovePhase extends BattlePhase { }), 500, ); - applyMoveAttrs(PreMoveMessageAttr, this.pokemon, this.pokemon.getOpponents(false)[0], this.move.getMove()); + applyMoveAttrs("PreMoveMessageAttr", this.pokemon, this.pokemon.getOpponents(false)[0], this.move.getMove()); } public showFailedText(failedText: string = i18next.t("battle:attackFailed")): void { diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index 1f27db7ff64..9aae796211f 100644 --- a/src/phases/mystery-encounter-phases.ts +++ b/src/phases/mystery-encounter-phases.ts @@ -1,4 +1,4 @@ -import { BattlerTagLapseType } from "#app/data/battler-tags"; +import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import type { OptionPhaseCallback } 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"; diff --git a/src/phases/obtain-status-effect-phase.ts b/src/phases/obtain-status-effect-phase.ts index 2982bc982d9..bf172269d5f 100644 --- a/src/phases/obtain-status-effect-phase.ts +++ b/src/phases/obtain-status-effect-phase.ts @@ -1,12 +1,13 @@ import { globalScene } from "#app/global-scene"; -import type { BattlerIndex } from "#app/battle"; -import { CommonBattleAnim, CommonAnim } from "#app/data/battle-anims"; +import type { BattlerIndex } from "#enums/battler-index"; +import { CommonBattleAnim } from "#app/data/battle-anims"; +import { CommonAnim } from "#enums/move-anims-common"; import { getStatusEffectObtainText, getStatusEffectOverlapText } from "#app/data/status-effect"; import { StatusEffect } from "#app/enums/status-effect"; import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonPhase } from "./pokemon-phase"; -import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { applyPostSetStatusAbAttrs, PostSetStatusAbAttr } from "#app/data/abilities/ability"; import { isNullOrUndefined } from "#app/utils/common"; diff --git a/src/phases/pokemon-heal-phase.ts b/src/phases/pokemon-heal-phase.ts index 6e9b6b5c622..cf6cf40a923 100644 --- a/src/phases/pokemon-heal-phase.ts +++ b/src/phases/pokemon-heal-phase.ts @@ -1,9 +1,9 @@ import { globalScene } from "#app/global-scene"; -import type { BattlerIndex } from "#app/battle"; -import { CommonAnim } from "#app/data/battle-anims"; +import type { BattlerIndex } from "#enums/battler-index"; +import { CommonAnim } from "#enums/move-anims-common"; import { getStatusEffectHealText } from "#app/data/status-effect"; import { StatusEffect } from "#app/enums/status-effect"; -import { HitResult } from "#app/field/pokemon"; +import { HitResult } from "#enums/hit-result"; import { getPokemonNameWithAffix } from "#app/messages"; import { HealingBoosterModifier } from "#app/modifier/modifier"; import { HealAchv } from "#app/system/achv"; diff --git a/src/phases/pokemon-phase.ts b/src/phases/pokemon-phase.ts index 8c30512cdc4..d7fe58d0b80 100644 --- a/src/phases/pokemon-phase.ts +++ b/src/phases/pokemon-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import type Pokemon from "#app/field/pokemon"; import { FieldPhase } from "./field-phase"; diff --git a/src/phases/pokemon-transform-phase.ts b/src/phases/pokemon-transform-phase.ts index 4f18a19b2fb..ab0949c42b9 100644 --- a/src/phases/pokemon-transform-phase.ts +++ b/src/phases/pokemon-transform-phase.ts @@ -1,8 +1,8 @@ -import type { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#enums/battler-tag-type"; import { MoveId } from "#enums/move-id"; import { EFFECTIVE_STATS, BATTLE_STATS } from "#enums/stat"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { globalScene } from "#app/global-scene"; import { PokemonPhase } from "./pokemon-phase"; import { getPokemonNameWithAffix } from "#app/messages"; diff --git a/src/phases/post-turn-status-effect-phase.ts b/src/phases/post-turn-status-effect-phase.ts index 33fb012492d..c868b963f39 100644 --- a/src/phases/post-turn-status-effect-phase.ts +++ b/src/phases/post-turn-status-effect-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import type { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#enums/battler-index"; import { applyAbAttrs, applyPostDamageAbAttrs, @@ -8,7 +8,8 @@ import { PostDamageAbAttr, ReduceBurnDamageAbAttr, } from "#app/data/abilities/ability"; -import { CommonBattleAnim, CommonAnim } from "#app/data/battle-anims"; +import { CommonBattleAnim } from "#app/data/battle-anims"; +import { CommonAnim } from "#enums/move-anims-common"; import { getStatusEffectActivationText } from "#app/data/status-effect"; import { BattleSpec } from "#app/enums/battle-spec"; import { StatusEffect } from "#app/enums/status-effect"; diff --git a/src/phases/quiet-form-change-phase.ts b/src/phases/quiet-form-change-phase.ts index 6b65c2a5140..cfd9c521e2b 100644 --- a/src/phases/quiet-form-change-phase.ts +++ b/src/phases/quiet-form-change-phase.ts @@ -1,7 +1,10 @@ import { globalScene } from "#app/global-scene"; import { SemiInvulnerableTag } from "#app/data/battler-tags"; import type { SpeciesFormChange } from "#app/data/pokemon-forms"; -import { getSpeciesFormChangeMessage, SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms"; +import { + getSpeciesFormChangeMessage, + SpeciesFormChangeTeraTrigger, +} from "#app/data/pokemon-forms/form-change-triggers"; import { getTypeRgb } from "#app/data/type"; import { BattleSpec } from "#app/enums/battle-spec"; import { BattlerTagType } from "#app/enums/battler-tag-type"; diff --git a/src/phases/return-phase.ts b/src/phases/return-phase.ts index 6365256d40a..a8233f98bd1 100644 --- a/src/phases/return-phase.ts +++ b/src/phases/return-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { SwitchType } from "#enums/switch-type"; import { SwitchSummonPhase } from "./switch-summon-phase"; diff --git a/src/phases/scan-ivs-phase.ts b/src/phases/scan-ivs-phase.ts index df68a2d1cab..d296d87ca88 100644 --- a/src/phases/scan-ivs-phase.ts +++ b/src/phases/scan-ivs-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import type { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#enums/battler-index"; import { PERMANENT_STATS, Stat } from "#app/enums/stat"; import { getPokemonNameWithAffix } from "#app/messages"; import { getTextColor, TextStyle } from "#app/ui/text"; diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index 88d4fe06a9b..e7e87f5a25f 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -1,7 +1,8 @@ import { globalScene } from "#app/global-scene"; -import { applyChallenges, ChallengeType } from "#app/data/challenge"; +import { applyChallenges } from "#app/data/challenge"; +import { ChallengeType } from "#enums/challenge-type"; import { Gender } from "#app/data/gender"; -import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { overrideHeldItems, overrideModifiers } from "#app/modifier/modifier"; import Overrides from "#app/overrides"; diff --git a/src/phases/select-target-phase.ts b/src/phases/select-target-phase.ts index fcbd3aeb679..6d47ac18021 100644 --- a/src/phases/select-target-phase.ts +++ b/src/phases/select-target-phase.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; -import type { BattlerIndex } from "#app/battle"; -import { Command } from "#app/ui/command-ui-handler"; +import type { BattlerIndex } from "#enums/battler-index"; +import { Command } from "#enums/command"; import { UiMode } from "#enums/ui-mode"; import { PokemonPhase } from "./pokemon-phase"; import i18next from "#app/plugins/i18n"; diff --git a/src/phases/shiny-sparkle-phase.ts b/src/phases/shiny-sparkle-phase.ts index 93d7dd67209..53866af89e6 100644 --- a/src/phases/shiny-sparkle-phase.ts +++ b/src/phases/shiny-sparkle-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import type { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#enums/battler-index"; import { PokemonPhase } from "./pokemon-phase"; export class ShinySparklePhase extends PokemonPhase { diff --git a/src/phases/show-ability-phase.ts b/src/phases/show-ability-phase.ts index af295b72622..0f568819cde 100644 --- a/src/phases/show-ability-phase.ts +++ b/src/phases/show-ability-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import type { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#enums/battler-index"; import { PokemonPhase } from "./pokemon-phase"; import { getPokemonNameWithAffix } from "#app/messages"; diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index ad2eeae1c48..9c351096180 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import type { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#enums/battler-index"; import { applyAbAttrs, applyPostStatStageChangeAbAttrs, @@ -11,7 +11,8 @@ import { StatStageChangeCopyAbAttr, StatStageChangeMultiplierAbAttr, } from "#app/data/abilities/ability"; -import { ArenaTagSide, MistTag } from "#app/data/arena-tag"; +import { MistTag } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import type { ArenaTag } from "#app/data/arena-tag"; import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; diff --git a/src/phases/summon-phase.ts b/src/phases/summon-phase.ts index 921466dfead..e902fd0183e 100644 --- a/src/phases/summon-phase.ts +++ b/src/phases/summon-phase.ts @@ -1,11 +1,11 @@ import { BattleType } from "#enums/battle-type"; import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball"; -import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { TrainerSlot } from "#enums/trainer-slot"; import { PlayerGender } from "#app/enums/player-gender"; import { addPokeballOpenParticles } from "#app/field/anims"; import type Pokemon from "#app/field/pokemon"; -import { FieldPosition } from "#app/field/pokemon"; +import { FieldPosition } from "#enums/field-position"; import { getPokemonNameWithAffix } from "#app/messages"; import i18next from "i18next"; import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase"; diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index 103af3db275..f6395397920 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -6,15 +6,14 @@ import { PreSummonAbAttr, PreSwitchOutAbAttr, } from "#app/data/abilities/ability"; -import { ForceSwitchOutAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { getPokeballTintColor } from "#app/data/pokeball"; -import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { TrainerSlot } from "#enums/trainer-slot"; import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { SwitchEffectTransferModifier } from "#app/modifier/modifier"; -import { Command } from "#app/ui/command-ui-handler"; +import { Command } from "#enums/command"; import i18next from "i18next"; import { SummonPhase } from "./summon-phase"; import { SubstituteTag } from "#app/data/battler-tags"; @@ -226,7 +225,7 @@ export class SwitchSummonPhase extends SummonPhase { const currentCommand = globalScene.currentBattle.turnCommands[this.fieldIndex]?.command; const lastPokemonIsForceSwitchedAndNotFainted = - lastUsedMove?.hasAttr(ForceSwitchOutAttr) && !this.lastPokemon.isFainted(); + lastUsedMove?.hasAttr("ForceSwitchOutAttr") && !this.lastPokemon.isFainted(); const lastPokemonHasForceSwitchAbAttr = this.lastPokemon.hasAbilityWithAttr(PostDamageForceSwitchAbAttr) && !this.lastPokemon.isFainted(); diff --git a/src/phases/tera-phase.ts b/src/phases/tera-phase.ts index 5f403f5419e..a6025e20488 100644 --- a/src/phases/tera-phase.ts +++ b/src/phases/tera-phase.ts @@ -5,8 +5,9 @@ import i18next from "i18next"; import { globalScene } from "#app/global-scene"; import { PokemonType } from "#enums/pokemon-type"; import { achvs } from "#app/system/achv"; -import { SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms"; -import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims"; +import { SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms/form-change-triggers"; +import { CommonBattleAnim } from "#app/data/battle-anims"; +import { CommonAnim } from "#enums/move-anims-common"; export class TeraPhase extends BattlePhase { public readonly phaseName = "TeraPhase"; diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 37ce294f237..26311d52ab8 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -3,7 +3,8 @@ import { BattleType } from "#enums/battle-type"; import { fetchDailyRunSeed, getDailyRunStarters } from "#app/data/daily-run"; import { Gender } from "#app/data/gender"; import { getBiomeKey } from "#app/field/arena"; -import { GameMode, GameModes, getGameMode } from "#app/game-mode"; +import { GameMode, getGameMode } from "#app/game-mode"; +import { GameModes } from "#enums/game-modes"; import type { Modifier } from "#app/modifier/modifier"; import { getDailyRunStarterModifiers, diff --git a/src/phases/toggle-double-position-phase.ts b/src/phases/toggle-double-position-phase.ts index a6b8705f580..596bf87eb5b 100644 --- a/src/phases/toggle-double-position-phase.ts +++ b/src/phases/toggle-double-position-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { FieldPosition } from "#app/field/pokemon"; +import { FieldPosition } from "#enums/field-position"; import { BattlePhase } from "./battle-phase"; export class ToggleDoublePositionPhase extends BattlePhase { diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index 85590d667d6..a539b234a18 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -1,5 +1,5 @@ import { applyPostTurnAbAttrs, PostTurnAbAttr } from "#app/data/abilities/ability"; -import { BattlerTagLapseType } from "#app/data/battler-tags"; +import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { TerrainType } from "#app/data/terrain"; import { WeatherType } from "#app/enums/weather-type"; import { TurnEndEvent } from "#app/events/battle-scene"; diff --git a/src/phases/turn-init-phase.ts b/src/phases/turn-init-phase.ts index e9d6f60af5e..8d0508c5ebb 100644 --- a/src/phases/turn-init-phase.ts +++ b/src/phases/turn-init-phase.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { handleMysteryEncounterBattleStartEffects, handleMysteryEncounterTurnStartEffects, diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index 557b67b6091..e9a8a82afdc 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -1,15 +1,14 @@ import { applyAbAttrs, BypassSpeedChanceAbAttr, PreventBypassSpeedChanceAbAttr } from "#app/data/abilities/ability"; -import { MoveHeaderAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { Stat } from "#app/enums/stat"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { BypassSpeedChanceModifier } from "#app/modifier/modifier"; -import { Command } from "#app/ui/command-ui-handler"; +import { Command } from "#enums/command"; import { randSeedShuffle, BooleanHolder } from "#app/utils/common"; import { FieldPhase } from "./field-phase"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { TrickRoomTag } from "#app/data/arena-tag"; import { SwitchType } from "#enums/switch-type"; import { globalScene } from "#app/global-scene"; @@ -168,7 +167,7 @@ export class TurnStartPhase extends FieldPhase { const move = pokemon.getMoveset().find(m => m.moveId === queuedMove.move && m.ppUsed < m.getMovePp()) || new PokemonMove(queuedMove.move); - if (move.getMove().hasAttr(MoveHeaderAttr)) { + if (move.getMove().hasAttr("MoveHeaderAttr")) { phaseManager.unshiftNew("MoveHeaderPhase", pokemon, move); } if (pokemon.isPlayer()) { diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts index ca24e474cde..bf6ea6d4a43 100644 --- a/src/phases/victory-phase.ts +++ b/src/phases/victory-phase.ts @@ -1,4 +1,4 @@ -import type { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#enums/battler-index"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; import { BattleType } from "#enums/battle-type"; import type { CustomModifierSettings } from "#app/modifier/modifier-type"; diff --git a/src/phases/weather-effect-phase.ts b/src/phases/weather-effect-phase.ts index 2918cd462df..0873283652e 100644 --- a/src/phases/weather-effect-phase.ts +++ b/src/phases/weather-effect-phase.ts @@ -8,13 +8,13 @@ import { applyPostWeatherLapseAbAttrs, PostWeatherLapseAbAttr, } from "#app/data/abilities/ability"; -import { CommonAnim } from "#app/data/battle-anims"; +import { CommonAnim } from "#enums/move-anims-common"; import type { Weather } from "#app/data/weather"; import { getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weather"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { WeatherType } from "#app/enums/weather-type"; import type Pokemon from "#app/field/pokemon"; -import { HitResult } from "#app/field/pokemon"; +import { HitResult } from "#enums/hit-result"; import { BooleanHolder, toDmgValue } from "#app/utils/common"; import { CommonAnimPhase } from "./common-anim-phase"; diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 2949ecd51cf..31c7ad7e41c 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -14,7 +14,8 @@ import PokemonData from "#app/system/pokemon-data"; import PersistentModifierData from "#app/system/modifier-data"; import ArenaData from "#app/system/arena-data"; import { Unlockables } from "#app/system/unlockables"; -import { GameModes, getGameMode } from "#app/game-mode"; +import { getGameMode } from "#app/game-mode"; +import { GameModes } from "#enums/game-modes"; import { BattleType } from "#enums/battle-type"; import TrainerData from "#app/system/trainer-data"; import { trainerConfigs } from "#app/data/trainers/trainer-config"; @@ -31,7 +32,7 @@ import { GameStats } from "#app/system/game-stats"; import { Tutorial } from "#app/tutorial"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { allMoves } from "#app/data/data-lists"; -import { TrainerVariant } from "#app/field/trainer"; +import { TrainerVariant } from "#enums/trainer-variant"; import type { Variant } from "#app/sprites/variant"; import { setSettingGamepad, SettingGamepad, settingGamepadDefaults } from "#app/system/settings/settings-gamepad"; import type { SettingKeyboard } from "#app/system/settings/settings-keyboard"; @@ -46,7 +47,8 @@ import { GameDataType } from "#enums/game-data-type"; import type { MoveId } from "#enums/move-id"; import { PlayerGender } from "#enums/player-gender"; import { SpeciesId } from "#enums/species-id"; -import { applyChallenges, ChallengeType } from "#app/data/challenge"; +import { applyChallenges } from "#app/data/challenge"; +import { ChallengeType } from "#enums/challenge-type"; import { WeatherType } from "#enums/weather-type"; import { TerrainType } from "#app/data/terrain"; import { RUN_HISTORY_LIMIT } from "#app/ui/run-history-ui-handler"; @@ -62,40 +64,12 @@ import { ArenaTrapTag } from "#app/data/arena-tag"; import { pokemonFormChanges } from "#app/data/pokemon-forms"; import type { PokemonType } from "#enums/pokemon-type"; import type { DexData, DexEntry } from "../@types/dex-data"; +import { DexAttr } from "#enums/dex-attr"; +import { AbilityAttr } from "#enums/ability-attr"; +import { defaultStarterSpecies, saveKey } from "#app/constants"; +import { encrypt, decrypt } from "#app/utils/data"; -export const defaultStarterSpecies: SpeciesId[] = [ - SpeciesId.BULBASAUR, - SpeciesId.CHARMANDER, - SpeciesId.SQUIRTLE, - SpeciesId.CHIKORITA, - SpeciesId.CYNDAQUIL, - SpeciesId.TOTODILE, - SpeciesId.TREECKO, - SpeciesId.TORCHIC, - SpeciesId.MUDKIP, - SpeciesId.TURTWIG, - SpeciesId.CHIMCHAR, - SpeciesId.PIPLUP, - SpeciesId.SNIVY, - SpeciesId.TEPIG, - SpeciesId.OSHAWOTT, - SpeciesId.CHESPIN, - SpeciesId.FENNEKIN, - SpeciesId.FROAKIE, - SpeciesId.ROWLET, - SpeciesId.LITTEN, - SpeciesId.POPPLIO, - SpeciesId.GROOKEY, - SpeciesId.SCORBUNNY, - SpeciesId.SOBBLE, - SpeciesId.SPRIGATITO, - SpeciesId.FUECOCO, - SpeciesId.QUAXLY, -]; - -const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary - -export function getDataTypeKey(dataType: GameDataType, slotId = 0): string { +function getDataTypeKey(dataType: GameDataType, slotId = 0): string { switch (dataType) { case GameDataType.SYSTEM: return "data"; @@ -117,20 +91,6 @@ export function getDataTypeKey(dataType: GameDataType, slotId = 0): string { } } -export function encrypt(data: string, bypassLogin: boolean): string { - return (bypassLogin - ? (data: string) => btoa(encodeURIComponent(data)) - : (data: string) => AES.encrypt(data, saveKey))(data) as unknown as string; // TODO: is this correct? -} - -export function decrypt(data: string, bypassLogin: boolean): string { - return ( - bypassLogin - ? (data: string) => decodeURIComponent(atob(data)) - : (data: string) => AES.decrypt(data, saveKey).toString(enc.Utf8) - )(data); -} - // TODO: Move all these exported interfaces to @types export interface SystemSaveData { trainerId: number; @@ -214,10 +174,6 @@ export interface StarterAttributes { tera?: PokemonType; } -export interface StarterPreferences { - [key: number]: StarterAttributes; -} - export interface DexAttrProps { shiny: boolean; female: boolean; @@ -234,53 +190,6 @@ export interface RunEntry { isFavorite: boolean; } -export const DexAttr = { - NON_SHINY: 1n, - SHINY: 2n, - MALE: 4n, - FEMALE: 8n, - DEFAULT_VARIANT: 16n, - VARIANT_2: 32n, - VARIANT_3: 64n, - DEFAULT_FORM: 128n, -}; - -export const AbilityAttr = { - ABILITY_1: 1, - ABILITY_2: 2, - ABILITY_HIDDEN: 4, -}; - -// the latest data saved/loaded for the Starter Preferences. Required to reduce read/writes. Initialize as "{}", since this is the default value and no data needs to be stored if present. -// if they ever add private static variables, move this into StarterPrefs -const StarterPrefers_DEFAULT: string = "{}"; -let StarterPrefers_private_latest: string = StarterPrefers_DEFAULT; - -// called on starter selection show once -export function loadStarterPreferences(): StarterPreferences { - return JSON.parse( - (StarterPrefers_private_latest = - localStorage.getItem(`starterPrefs_${loggedInUser?.username}`) || StarterPrefers_DEFAULT), - ); -} - -// called on starter selection clear, always -export function saveStarterPreferences(prefs: StarterPreferences): void { - const pStr: string = JSON.stringify(prefs); - if (pStr !== StarterPrefers_private_latest) { - // something changed, store the update - localStorage.setItem(`starterPrefs_${loggedInUser?.username}`, pStr); - // update the latest prefs - StarterPrefers_private_latest = pStr; - } -} -// This is its own class as StarterPreferences... -// - don't need to be loaded on startup -// - isn't stored with other data -// - don't require to be encrypted -// - shouldn't require calls outside of the starter selection -export class StarterPrefs {} - export interface StarterDataEntry { moveset: StarterMoveset | StarterFormMoveData | null; eggMoves: number; diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 62fb2f4a2e4..7571f0cc82f 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -5,7 +5,8 @@ import { Nature } from "#enums/nature"; import { PokeballType } from "#enums/pokeball"; import { getPokemonSpecies, getPokemonSpeciesForm } from "../data/pokemon-species"; import { Status } from "../data/status-effect"; -import Pokemon, { EnemyPokemon, PokemonBattleData, PokemonMove, PokemonSummonData } from "../field/pokemon"; +import Pokemon, { EnemyPokemon, PokemonBattleData, PokemonSummonData } from "../field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { TrainerSlot } from "#enums/trainer-slot"; import type { Variant } from "#app/sprites/variant"; import type { BiomeId } from "#enums/biome-id"; diff --git a/src/system/trainer-data.ts b/src/system/trainer-data.ts index 0e6298309bc..7c9bffd99ee 100644 --- a/src/system/trainer-data.ts +++ b/src/system/trainer-data.ts @@ -1,5 +1,6 @@ import type { TrainerType } from "#enums/trainer-type"; -import Trainer, { TrainerVariant } from "../field/trainer"; +import Trainer from "../field/trainer"; +import { TrainerVariant } from "#enums/trainer-variant"; export default class TrainerData { public trainerType: TrainerType; diff --git a/src/system/unlockables.ts b/src/system/unlockables.ts index 2c396aad1f9..f64d4d33966 100644 --- a/src/system/unlockables.ts +++ b/src/system/unlockables.ts @@ -1,5 +1,6 @@ import i18next from "i18next"; -import { GameMode, GameModes } from "../game-mode"; +import { GameMode } from "../game-mode"; +import { GameModes } from "#enums/game-modes"; export enum Unlockables { ENDLESS_MODE, diff --git a/src/system/version_migration/versions/v1_0_4.ts b/src/system/version_migration/versions/v1_0_4.ts index 9e30ccdc2a7..fbbde49bc08 100644 --- a/src/system/version_migration/versions/v1_0_4.ts +++ b/src/system/version_migration/versions/v1_0_4.ts @@ -1,6 +1,8 @@ import { SettingKeys } from "#app/system/settings/settings"; import type { SystemSaveData, SessionSaveData } from "#app/system/game-data"; -import { AbilityAttr, defaultStarterSpecies, DexAttr } from "#app/system/game-data"; +import { defaultStarterSpecies } from "#app/constants"; +import { AbilityAttr } from "#enums/ability-attr"; +import { DexAttr } from "#enums/dex-attr"; import { allSpecies } from "#app/data/pokemon-species"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { isNullOrUndefined } from "#app/utils/common"; diff --git a/src/system/version_migration/versions/v1_7_0.ts b/src/system/version_migration/versions/v1_7_0.ts index dc7c0f48640..e309959317e 100644 --- a/src/system/version_migration/versions/v1_7_0.ts +++ b/src/system/version_migration/versions/v1_7_0.ts @@ -2,7 +2,8 @@ import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator"; import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { globalScene } from "#app/global-scene"; -import { DexAttr, type SessionSaveData, type SystemSaveData } from "#app/system/game-data"; +import type { SessionSaveData, SystemSaveData } from "#app/system/game-data"; +import { DexAttr } from "#enums/dex-attr"; import { isNullOrUndefined } from "#app/utils/common"; /** diff --git a/src/system/version_migration/versions/v1_8_3.ts b/src/system/version_migration/versions/v1_8_3.ts index cce37a53767..bd963290800 100644 --- a/src/system/version_migration/versions/v1_8_3.ts +++ b/src/system/version_migration/versions/v1_8_3.ts @@ -1,6 +1,7 @@ import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; import { getPokemonSpecies } from "#app/data/pokemon-species"; -import { DexAttr, type SystemSaveData } from "#app/system/game-data"; +import type { SystemSaveData } from "#app/system/game-data"; +import { DexAttr } from "#enums/dex-attr"; import { SpeciesId } from "#enums/species-id"; /** diff --git a/src/system/version_migration/versions/v1_9_0.ts b/src/system/version_migration/versions/v1_9_0.ts index 0f22b85d072..0bd5a422ffb 100644 --- a/src/system/version_migration/versions/v1_9_0.ts +++ b/src/system/version_migration/versions/v1_9_0.ts @@ -1,5 +1,5 @@ import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import type { SessionSaveData } from "#app/system/game-data"; import type PokemonData from "#app/system/pokemon-data"; import { MoveId } from "#enums/move-id"; diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index ab3bd13b47a..fec02ffb660 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -1,6 +1,7 @@ import { addTextObject, TextStyle } from "./text"; import { globalScene } from "#app/global-scene"; -import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; +import { ArenaTrapTag } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import { WeatherType } from "#enums/weather-type"; import { TerrainType } from "#app/data/terrain"; import { addWindow, WindowVariant } from "./ui-theme"; diff --git a/src/ui/ball-ui-handler.ts b/src/ui/ball-ui-handler.ts index bb1e8d0e85e..11fb485164a 100644 --- a/src/ui/ball-ui-handler.ts +++ b/src/ui/ball-ui-handler.ts @@ -1,6 +1,6 @@ import { getPokeballName } from "../data/pokeball"; import { addTextObject, getTextStyleOptions, TextStyle } from "./text"; -import { Command } from "./command-ui-handler"; +import { Command } from "#enums/command"; import { UiMode } from "#enums/ui-mode"; import UiHandler from "./ui-handler"; import { addWindow } from "./ui-theme"; diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts index 0d38672323a..8df399b6d9b 100644 --- a/src/ui/command-ui-handler.ts +++ b/src/ui/command-ui-handler.ts @@ -11,14 +11,7 @@ import { TerastallizeAccessModifier } from "#app/modifier/modifier"; import { PokemonType } from "#enums/pokemon-type"; import { getTypeRgb } from "#app/data/type"; import { SpeciesId } from "#enums/species-id"; - -export enum Command { - FIGHT = 0, - BALL, - POKEMON, - RUN, - TERA, -} +import { Command } from "#enums/command"; export default class CommandUiHandler extends UiHandler { private commandsContainer: Phaser.GameObjects.Container; diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 3fe06cdf039..f30c7a4935c 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -3,14 +3,15 @@ import { globalScene } from "#app/global-scene"; import { addTextObject, TextStyle } from "./text"; import { getTypeDamageMultiplierColor } from "#app/data/type"; import { PokemonType } from "#enums/pokemon-type"; -import { Command } from "./command-ui-handler"; +import { Command } from "#enums/command"; import { UiMode } from "#enums/ui-mode"; import UiHandler from "./ui-handler"; import { getLocalizedSpriteKey, fixedInt, padInt } from "#app/utils/common"; import { MoveCategory } from "#enums/MoveCategory"; import i18next from "i18next"; import { Button } from "#enums/buttons"; -import type { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; +import type { EnemyPokemon } from "#app/field/pokemon"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; import type { CommandPhase } from "#app/phases/command-phase"; import MoveInfoOverlay from "./move-info-overlay"; diff --git a/src/ui/game-stats-ui-handler.ts b/src/ui/game-stats-ui-handler.ts index dc184a34866..4213a244fdb 100644 --- a/src/ui/game-stats-ui-handler.ts +++ b/src/ui/game-stats-ui-handler.ts @@ -5,7 +5,7 @@ import UiHandler from "#app/ui/ui-handler"; import { addWindow } from "#app/ui/ui-theme"; import { getPlayTimeString, formatFancyLargeNumber, toReadableString } from "#app/utils/common"; import type { GameData } from "#app/system/game-data"; -import { DexAttr } from "#app/system/game-data"; +import { DexAttr } from "#enums/dex-attr"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { Button } from "#enums/buttons"; import i18next from "i18next"; diff --git a/src/ui/hatched-pokemon-container.ts b/src/ui/hatched-pokemon-container.ts index 9d1c13e19d5..8b9a5309a9c 100644 --- a/src/ui/hatched-pokemon-container.ts +++ b/src/ui/hatched-pokemon-container.ts @@ -1,7 +1,7 @@ import type { EggHatchData } from "#app/data/egg-hatch-data"; import { Gender } from "#app/data/gender"; import { getVariantTint } from "#app/sprites/variant"; -import { DexAttr } from "#app/system/game-data"; +import { DexAttr } from "#enums/dex-attr"; import { globalScene } from "#app/global-scene"; import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonIconAnimHandler from "./pokemon-icon-anim-handler"; diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 6ce192751df..f60bfa808ca 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -1,8 +1,9 @@ -import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "#app/ui/text"; -import { Command } from "#app/ui/command-ui-handler"; +import { Command } from "#enums/command"; import MessageUiHandler from "#app/ui/message-ui-handler"; import { UiMode } from "#enums/ui-mode"; import { BooleanHolder, toReadableString, randInt, getLocalizedSpriteKey } from "#app/utils/common"; @@ -11,17 +12,18 @@ import { PokemonHeldItemModifier, SwitchEffectTransferModifier, } from "#app/modifier/modifier"; -import { ForceSwitchOutAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender"; import { StatusEffect } from "#enums/status-effect"; import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { addWindow } from "#app/ui/ui-theme"; -import { SpeciesFormChangeItemTrigger, FormChangeItem } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers"; +import { FormChangeItem } from "#enums/form-change-item"; import { getVariantTint } from "#app/sprites/variant"; import { Button } from "#enums/buttons"; -import { applyChallenges, ChallengeType } from "#app/data/challenge"; +import { applyChallenges } from "#app/data/challenge"; +import { ChallengeType } from "#enums/challenge-type"; import MoveInfoOverlay from "#app/ui/move-info-overlay"; import i18next from "i18next"; import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; @@ -1175,7 +1177,7 @@ export default class PartyUiHandler extends MessageUiHandler { return !!( this.partyUiMode === PartyUiMode.FAINT_SWITCH && moveHistory.length && - allMoves[moveHistory[moveHistory.length - 1].move].getAttrs(ForceSwitchOutAttr)[0]?.isBatonPass() && + allMoves[moveHistory[moveHistory.length - 1].move].getAttrs("ForceSwitchOutAttr")[0]?.isBatonPass() && moveHistory[moveHistory.length - 1].result === MoveResult.SUCCESS ); } diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts index a81265e1a55..7ef4f8f920b 100644 --- a/src/ui/pokedex-page-ui-handler.ts +++ b/src/ui/pokedex-page-ui-handler.ts @@ -22,7 +22,8 @@ import { starterPassiveAbilities } from "#app/data/balance/passives"; import { PokemonType } from "#enums/pokemon-type"; import type { StarterAttributes } from "#app/system/game-data"; import type { DexEntry } from "#app/@types/dex-data"; -import { AbilityAttr, DexAttr } from "#app/system/game-data"; +import { AbilityAttr } from "#enums/ability-attr"; +import { DexAttr } from "#enums/dex-attr"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import MessageUiHandler from "#app/ui/message-ui-handler"; import { StatsContainer } from "#app/ui/stats-container"; diff --git a/src/ui/pokedex-ui-handler.ts b/src/ui/pokedex-ui-handler.ts index 96451041306..5b292e7232f 100644 --- a/src/ui/pokedex-ui-handler.ts +++ b/src/ui/pokedex-ui-handler.ts @@ -11,9 +11,12 @@ import { allSpecies, getPokemonSpeciesForm, getPokerusStarters, normalForm } fro import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { catchableSpecies } from "#app/data/balance/biomes"; import { PokemonType } from "#enums/pokemon-type"; -import type { DexAttrProps, StarterAttributes, StarterPreferences } from "#app/system/game-data"; +import type { DexAttrProps, StarterAttributes } from "#app/system/game-data"; +import type { StarterPreferences } from "#app/utils/data"; import type { DexEntry } from "#app/@types/dex-data"; -import { AbilityAttr, DexAttr, loadStarterPreferences } from "#app/system/game-data"; +import { loadStarterPreferences } from "#app/utils/data"; +import { AbilityAttr } from "#enums/ability-attr"; +import { DexAttr } from "#enums/dex-attr"; import MessageUiHandler from "#app/ui/message-ui-handler"; import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler"; import { TextStyle, addTextObject } from "#app/ui/text"; diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 3dbe3b7af7d..0056c3e2f11 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -8,7 +8,7 @@ import type Pokemon from "../field/pokemon"; import i18next from "i18next"; import type { StarterDataEntry } from "../system/game-data"; import type { DexEntry } from "#app/@types/dex-data"; -import { DexAttr } from "../system/game-data"; +import { DexAttr } from "#enums/dex-attr"; import { fixedInt, getShinyDescriptor } from "#app/utils/common"; import ConfirmUiHandler from "./confirm-ui-handler"; import { StatsContainer } from "./stats-container"; diff --git a/src/ui/run-history-ui-handler.ts b/src/ui/run-history-ui-handler.ts index 92c5a2fde07..06ef590c1e8 100644 --- a/src/ui/run-history-ui-handler.ts +++ b/src/ui/run-history-ui-handler.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { GameModes } from "../game-mode"; +import { GameModes } from "#enums/game-modes"; import { TextStyle, addTextObject } from "./text"; import { UiMode } from "#enums/ui-mode"; import { addWindow } from "./ui-theme"; @@ -11,7 +11,7 @@ import { Button } from "../enums/buttons"; import { BattleType } from "#enums/battle-type"; import type { RunEntry } from "../system/game-data"; import { PlayerGender } from "#enums/player-gender"; -import { TrainerVariant } from "../field/trainer"; +import { TrainerVariant } from "#enums/trainer-variant"; import { RunDisplayMode } from "#app/ui/run-info-ui-handler"; export type RunSelectCallback = (cursor: number) => void; diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index c8dade8878f..a4de2215fa4 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -1,4 +1,4 @@ -import { GameModes } from "../game-mode"; +import { GameModes } from "#enums/game-modes"; import UiHandler from "./ui-handler"; import type { SessionSaveData } from "../system/game-data"; import { TextStyle, addTextObject, addBBCodeTextObject, getTextColor } from "./text"; @@ -10,7 +10,7 @@ import type PokemonData from "../system/pokemon-data"; import i18next from "i18next"; import { Button } from "../enums/buttons"; import { BattleType } from "#enums/battle-type"; -import { TrainerVariant } from "../field/trainer"; +import { TrainerVariant } from "#enums/trainer-variant"; import { Challenges } from "#enums/challenges"; import { getLuckString, getLuckTextTint } from "../modifier/modifier-type"; import RoundRectangle from "phaser3-rex-plugins/plugins/roundrectangle"; diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 47226de3354..0b1e690a918 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -22,10 +22,13 @@ import type PokemonSpecies from "#app/data/pokemon-species"; import { allSpecies, getPokemonSpeciesForm, getPokerusStarters } from "#app/data/pokemon-species"; import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { PokemonType } from "#enums/pokemon-type"; -import { GameModes } from "#app/game-mode"; -import type { DexAttrProps, StarterMoveset, StarterAttributes, StarterPreferences } from "#app/system/game-data"; +import { GameModes } from "#enums/game-modes"; +import type { DexAttrProps, StarterMoveset, StarterAttributes } from "#app/system/game-data"; +import type { StarterPreferences } from "#app/utils/data"; import type { DexEntry } from "#app/@types/dex-data"; -import { AbilityAttr, DexAttr, loadStarterPreferences, saveStarterPreferences } from "#app/system/game-data"; +import { loadStarterPreferences, saveStarterPreferences } from "#app/utils/data"; +import { AbilityAttr } from "#enums/ability-attr"; +import { DexAttr } from "#enums/dex-attr"; import { Tutorial, handleTutorial } from "#app/tutorial"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import MessageUiHandler from "#app/ui/message-ui-handler"; @@ -38,7 +41,8 @@ import { Egg } from "#app/data/egg"; import Overrides from "#app/overrides"; import { SettingKeyboard } from "#app/system/settings/settings-keyboard"; import { Passive as PassiveAttr } from "#enums/passive"; -import { applyChallenges, ChallengeType } from "#app/data/challenge"; +import { applyChallenges } from "#app/data/challenge"; +import { ChallengeType } from "#enums/challenge-type"; import MoveInfoOverlay from "#app/ui/move-info-overlay"; import { getEggTierForSpecies } from "#app/data/egg"; import { Device } from "#enums/devices"; diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index a6f640b436f..05b0ea2097f 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -13,7 +13,8 @@ import { formatStat, getShinyDescriptor, } from "#app/utils/common"; -import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { argbFromRgba } from "@material/material-color-utilities"; import { getTypeRgb } from "#app/data/type"; diff --git a/src/ui/target-select-ui-handler.ts b/src/ui/target-select-ui-handler.ts index a2f89d38970..8106e4de2da 100644 --- a/src/ui/target-select-ui-handler.ts +++ b/src/ui/target-select-ui-handler.ts @@ -1,8 +1,8 @@ -import { BattlerIndex } from "../battle"; +import { BattlerIndex } from "#enums/battler-index"; import { UiMode } from "#enums/ui-mode"; import UiHandler from "./ui-handler"; import { isNullOrUndefined, fixedInt } from "#app/utils/common"; -import { getMoveTargets } from "../data/moves/move"; +import { getMoveTargets } from "#app/data/moves/move-utils"; import { Button } from "#enums/buttons"; import type { MoveId } from "#enums/move-id"; import type Pokemon from "#app/field/pokemon"; diff --git a/src/utils/data.ts b/src/utils/data.ts index 33623dc5e40..5a28657d034 100644 --- a/src/utils/data.ts +++ b/src/utils/data.ts @@ -1,3 +1,8 @@ +import { loggedInUser } from "#app/account"; +import type { StarterAttributes } from "#app/system/game-data"; +import { AES, enc } from "crypto-js"; +import { saveKey } from "#app/constants"; + /** * Perform a deep copy of an object. * @param values - The object to be deep copied. @@ -38,3 +43,45 @@ export function deepMergeSpriteData(dest: object, source: object) { } } } + +export function encrypt(data: string, bypassLogin: boolean): string { + return (bypassLogin + ? (data: string) => btoa(encodeURIComponent(data)) + : (data: string) => AES.encrypt(data, saveKey))(data) as unknown as string; // TODO: is this correct? +} + +export function decrypt(data: string, bypassLogin: boolean): string { + return ( + bypassLogin + ? (data: string) => decodeURIComponent(atob(data)) + : (data: string) => AES.decrypt(data, saveKey).toString(enc.Utf8) + )(data); +} + +// the latest data saved/loaded for the Starter Preferences. Required to reduce read/writes. Initialize as "{}", since this is the default value and no data needs to be stored if present. +// if they ever add private static variables, move this into StarterPrefs +const StarterPrefers_DEFAULT: string = "{}"; +let StarterPrefers_private_latest: string = StarterPrefers_DEFAULT; + +export interface StarterPreferences { + [key: number]: StarterAttributes; +} +// called on starter selection show once + +export function loadStarterPreferences(): StarterPreferences { + return JSON.parse( + (StarterPrefers_private_latest = + localStorage.getItem(`starterPrefs_${loggedInUser?.username}`) || StarterPrefers_DEFAULT), + ); +} +// called on starter selection clear, always + +export function saveStarterPreferences(prefs: StarterPreferences): void { + const pStr: string = JSON.stringify(prefs); + if (pStr !== StarterPrefers_private_latest) { + // something changed, store the update + localStorage.setItem(`starterPrefs_${loggedInUser?.username}`, pStr); + // update the latest prefs + StarterPrefers_private_latest = pStr; + } +} diff --git a/test/abilities/analytic.test.ts b/test/abilities/analytic.test.ts index bf6ef72c3f1..2cfea64d20e 100644 --- a/test/abilities/analytic.test.ts +++ b/test/abilities/analytic.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { isBetween, toDmgValue } from "#app/utils/common"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; diff --git a/test/abilities/aroma_veil.test.ts b/test/abilities/aroma_veil.test.ts index 00baa6b6268..56cce749b9f 100644 --- a/test/abilities/aroma_veil.test.ts +++ b/test/abilities/aroma_veil.test.ts @@ -6,7 +6,7 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { BattlerTagType } from "#enums/battler-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import type { PlayerPokemon } from "#app/field/pokemon"; describe("Moves - Aroma Veil", () => { diff --git a/test/abilities/battle_bond.test.ts b/test/abilities/battle_bond.test.ts index b4ce73d107b..adb4a9c6e7a 100644 --- a/test/abilities/battle_bond.test.ts +++ b/test/abilities/battle_bond.test.ts @@ -1,4 +1,3 @@ -import { MultiHitAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { MultiHitType } from "#enums/MultiHitType"; import { Status } from "#app/data/status-effect"; @@ -66,7 +65,7 @@ describe("Abilities - BATTLE BOND", () => { vi.spyOn(waterShuriken, "calculateBattlePower"); let actualMultiHitType: MultiHitType | null = null; - const multiHitAttr = waterShuriken.getAttrs(MultiHitAttr)[0]; + const multiHitAttr = waterShuriken.getAttrs("MultiHitAttr")[0]; vi.spyOn(multiHitAttr, "getHitCount").mockImplementation(() => { actualMultiHitType = multiHitAttr.getMultiHitType(); return 3; diff --git a/test/abilities/beast_boost.test.ts b/test/abilities/beast_boost.test.ts index 17ba4020961..1d9eb5c5f2e 100644 --- a/test/abilities/beast_boost.test.ts +++ b/test/abilities/beast_boost.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/abilities/commander.test.ts b/test/abilities/commander.test.ts index bb2670ec2f0..39d9d0f4d7f 100644 --- a/test/abilities/commander.test.ts +++ b/test/abilities/commander.test.ts @@ -1,11 +1,11 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#enums/battler-tag-type"; import { PokemonAnimType } from "#enums/pokemon-anim-type"; import type { EffectiveStat } from "#enums/stat"; import { Stat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import { WeatherType } from "#enums/weather-type"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/abilities/dancer.test.ts b/test/abilities/dancer.test.ts index 518c96ea124..2a4a3c36bcc 100644 --- a/test/abilities/dancer.test.ts +++ b/test/abilities/dancer.test.ts @@ -1,5 +1,5 @@ -import { BattlerIndex } from "#app/battle"; -import { MoveResult } from "#app/field/pokemon"; +import { BattlerIndex } from "#enums/battler-index"; +import { MoveResult } from "#enums/move-result"; import type { MovePhase } from "#app/phases/move-phase"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; diff --git a/test/abilities/desolate-land.test.ts b/test/abilities/desolate-land.test.ts index bcd980187ac..90565d9caf8 100644 --- a/test/abilities/desolate-land.test.ts +++ b/test/abilities/desolate-land.test.ts @@ -1,7 +1,7 @@ import { PokeballType } from "#app/enums/pokeball"; import { WeatherType } from "#app/enums/weather-type"; import type { CommandPhase } from "#app/phases/command-phase"; -import { Command } from "#app/ui/command-ui-handler"; +import { Command } from "#enums/command"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/abilities/disguise.test.ts b/test/abilities/disguise.test.ts index 15ded41527a..64e0715774d 100644 --- a/test/abilities/disguise.test.ts +++ b/test/abilities/disguise.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { toDmgValue } from "#app/utils/common"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; diff --git a/test/abilities/early_bird.test.ts b/test/abilities/early_bird.test.ts index e158ce1888b..e19f2653b57 100644 --- a/test/abilities/early_bird.test.ts +++ b/test/abilities/early_bird.test.ts @@ -1,5 +1,5 @@ import { Status } from "#app/data/status-effect"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/abilities/flash_fire.test.ts b/test/abilities/flash_fire.test.ts index 8fabda95c80..92bd7b52e57 100644 --- a/test/abilities/flash_fire.test.ts +++ b/test/abilities/flash_fire.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { SpeciesId } from "#enums/species-id"; import { MovePhase } from "#app/phases/move-phase"; diff --git a/test/abilities/flower_gift.test.ts b/test/abilities/flower_gift.test.ts index d4a03bb2330..26180492cbd 100644 --- a/test/abilities/flower_gift.test.ts +++ b/test/abilities/flower_gift.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allAbilities } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { Stat } from "#app/enums/stat"; diff --git a/test/abilities/flower_veil.test.ts b/test/abilities/flower_veil.test.ts index bd76541495d..c59d8c6eb29 100644 --- a/test/abilities/flower_veil.test.ts +++ b/test/abilities/flower_veil.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/abilities/forecast.test.ts b/test/abilities/forecast.test.ts index b87519ae80a..8d3a679c917 100644 --- a/test/abilities/forecast.test.ts +++ b/test/abilities/forecast.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allAbilities } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { WeatherType } from "#app/enums/weather-type"; diff --git a/test/abilities/friend_guard.test.ts b/test/abilities/friend_guard.test.ts index d401fa96feb..c5809e18e96 100644 --- a/test/abilities/friend_guard.test.ts +++ b/test/abilities/friend_guard.test.ts @@ -4,7 +4,7 @@ import { AbilityId } from "#enums/ability-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allAbilities } from "#app/data/data-lists"; import { allMoves } from "#app/data/data-lists"; import { MoveCategory } from "#enums/MoveCategory"; diff --git a/test/abilities/good_as_gold.test.ts b/test/abilities/good_as_gold.test.ts index d6c80a5347f..85fadd472dc 100644 --- a/test/abilities/good_as_gold.test.ts +++ b/test/abilities/good_as_gold.test.ts @@ -1,5 +1,5 @@ -import { BattlerIndex } from "#app/battle"; -import { ArenaTagSide } from "#app/data/arena-tag"; +import { BattlerIndex } from "#enums/battler-index"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import { allAbilities } from "#app/data/data-lists"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import { BattlerTagType } from "#app/enums/battler-tag-type"; diff --git a/test/abilities/gorilla_tactics.test.ts b/test/abilities/gorilla_tactics.test.ts index 3ad138749a8..1759267f16d 100644 --- a/test/abilities/gorilla_tactics.test.ts +++ b/test/abilities/gorilla_tactics.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#app/enums/stat"; diff --git a/test/abilities/gulp_missile.test.ts b/test/abilities/gulp_missile.test.ts index b56b316484f..b41eea9ce18 100644 --- a/test/abilities/gulp_missile.test.ts +++ b/test/abilities/gulp_missile.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import type Pokemon from "#app/field/pokemon"; import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#enums/battler-tag-type"; diff --git a/test/abilities/harvest.test.ts b/test/abilities/harvest.test.ts index 5a6ddf35459..40e6a6abd62 100644 --- a/test/abilities/harvest.test.ts +++ b/test/abilities/harvest.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { PostTurnRestoreBerryAbAttr } from "#app/data/abilities/ability"; import type Pokemon from "#app/field/pokemon"; import { BerryModifier, PreserveBerryModifier } from "#app/modifier/modifier"; diff --git a/test/abilities/honey_gather.test.ts b/test/abilities/honey_gather.test.ts index a069654ae21..06d351b2a61 100644 --- a/test/abilities/honey_gather.test.ts +++ b/test/abilities/honey_gather.test.ts @@ -1,5 +1,5 @@ import type { CommandPhase } from "#app/phases/command-phase"; -import { Command } from "#app/ui/command-ui-handler"; +import { Command } from "#enums/command"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/abilities/ice_face.test.ts b/test/abilities/ice_face.test.ts index 6ef91981cf3..2899120db5a 100644 --- a/test/abilities/ice_face.test.ts +++ b/test/abilities/ice_face.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; diff --git a/test/abilities/infiltrator.test.ts b/test/abilities/infiltrator.test.ts index 891a716f7a4..03b55e925cd 100644 --- a/test/abilities/infiltrator.test.ts +++ b/test/abilities/infiltrator.test.ts @@ -1,4 +1,4 @@ -import { ArenaTagSide } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import { allMoves } from "#app/data/data-lists"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; diff --git a/test/abilities/lightningrod.test.ts b/test/abilities/lightningrod.test.ts index 777ccb88ba9..a69f5e94067 100644 --- a/test/abilities/lightningrod.test.ts +++ b/test/abilities/lightningrod.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/abilities/magic_bounce.test.ts b/test/abilities/magic_bounce.test.ts index 8c3eeec974c..3c4ff2ad909 100644 --- a/test/abilities/magic_bounce.test.ts +++ b/test/abilities/magic_bounce.test.ts @@ -1,6 +1,6 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allAbilities } from "#app/data/data-lists"; -import { ArenaTagSide } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import { allMoves } from "#app/data/data-lists"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import { BattlerTagType } from "#app/enums/battler-tag-type"; diff --git a/test/abilities/magic_guard.test.ts b/test/abilities/magic_guard.test.ts index c36b35c09b7..40bb3203673 100644 --- a/test/abilities/magic_guard.test.ts +++ b/test/abilities/magic_guard.test.ts @@ -1,4 +1,5 @@ -import { ArenaTagSide, getArenaTag } from "#app/data/arena-tag"; +import { getArenaTag } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import { getStatusEffectCatchRateMultiplier } from "#app/data/status-effect"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { AbilityId } from "#enums/ability-id"; diff --git a/test/abilities/mirror_armor.test.ts b/test/abilities/mirror_armor.test.ts index 319e47cbfb3..540a3b5d426 100644 --- a/test/abilities/mirror_armor.test.ts +++ b/test/abilities/mirror_armor.test.ts @@ -5,7 +5,7 @@ import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; // TODO: When Magic Bounce is implemented, make a test for its interaction with mirror guard, use screech diff --git a/test/abilities/mold_breaker.test.ts b/test/abilities/mold_breaker.test.ts index 099fb54c998..e2e8a73da9d 100644 --- a/test/abilities/mold_breaker.test.ts +++ b/test/abilities/mold_breaker.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { globalScene } from "#app/global-scene"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; diff --git a/test/abilities/moxie.test.ts b/test/abilities/moxie.test.ts index 04ca68325e6..8b658f60bf6 100644 --- a/test/abilities/moxie.test.ts +++ b/test/abilities/moxie.test.ts @@ -5,7 +5,7 @@ import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { EnemyCommandPhase } from "#app/phases/enemy-command-phase"; import { VictoryPhase } from "#app/phases/victory-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; diff --git a/test/abilities/neutralizing_gas.test.ts b/test/abilities/neutralizing_gas.test.ts index 2d1fef29688..067d164e835 100644 --- a/test/abilities/neutralizing_gas.test.ts +++ b/test/abilities/neutralizing_gas.test.ts @@ -1,6 +1,6 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import type { CommandPhase } from "#app/phases/command-phase"; -import { Command } from "#app/ui/command-ui-handler"; +import { Command } from "#enums/command"; import { PostSummonWeatherChangeAbAttr } from "#app/data/abilities/ability"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#enums/arena-tag-type"; diff --git a/test/abilities/no_guard.test.ts b/test/abilities/no_guard.test.ts index 5b127204f9f..dc35e0e1b9a 100644 --- a/test/abilities/no_guard.test.ts +++ b/test/abilities/no_guard.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { AbilityId } from "#enums/ability-id"; diff --git a/test/abilities/normal-move-type-change.test.ts b/test/abilities/normal-move-type-change.test.ts index 578a6ad2a21..03fb5b2e7be 100644 --- a/test/abilities/normal-move-type-change.test.ts +++ b/test/abilities/normal-move-type-change.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allMoves } from "#app/data/data-lists"; import { PokemonType } from "#enums/pokemon-type"; import { AbilityId } from "#enums/ability-id"; diff --git a/test/abilities/pastel_veil.test.ts b/test/abilities/pastel_veil.test.ts index 8a3aec918d0..b8e8873ed36 100644 --- a/test/abilities/pastel_veil.test.ts +++ b/test/abilities/pastel_veil.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { CommandPhase } from "#app/phases/command-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; diff --git a/test/abilities/protosynthesis.test.ts b/test/abilities/protosynthesis.test.ts index 3bf74acaca7..ba7bb5d084b 100644 --- a/test/abilities/protosynthesis.test.ts +++ b/test/abilities/protosynthesis.test.ts @@ -5,7 +5,7 @@ import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; describe("Abilities - Protosynthesis", () => { diff --git a/test/abilities/serene_grace.test.ts b/test/abilities/serene_grace.test.ts index bfdbd5324bb..c5c12fe217e 100644 --- a/test/abilities/serene_grace.test.ts +++ b/test/abilities/serene_grace.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; @@ -6,7 +6,6 @@ import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { allMoves } from "#app/data/data-lists"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { FlinchAttr } from "#app/data/moves/move"; describe("Abilities - Serene Grace", () => { let phaserGame: Phaser.Game; @@ -39,7 +38,7 @@ describe("Abilities - Serene Grace", () => { await game.classicMode.startBattle([SpeciesId.SHUCKLE]); const airSlashMove = allMoves[MoveId.AIR_SLASH]; - const airSlashFlinchAttr = airSlashMove.getAttrs(FlinchAttr)[0]; + const airSlashFlinchAttr = airSlashMove.getAttrs("FlinchAttr")[0]; vi.spyOn(airSlashFlinchAttr, "getMoveChance"); game.move.select(MoveId.AIR_SLASH); diff --git a/test/abilities/sheer_force.test.ts b/test/abilities/sheer_force.test.ts index a5b1cf3b5b2..922025d8be2 100644 --- a/test/abilities/sheer_force.test.ts +++ b/test/abilities/sheer_force.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { PokemonType } from "#enums/pokemon-type"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; @@ -7,7 +7,6 @@ import { Stat } from "#enums/stat"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { FlinchAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; describe("Abilities - Sheer Force", () => { @@ -43,7 +42,7 @@ describe("Abilities - Sheer Force", () => { const airSlashMove = allMoves[MoveId.AIR_SLASH]; vi.spyOn(airSlashMove, "calculateBattlePower"); - const airSlashFlinchAttr = airSlashMove.getAttrs(FlinchAttr)[0]; + const airSlashFlinchAttr = airSlashMove.getAttrs("FlinchAttr")[0]; vi.spyOn(airSlashFlinchAttr, "getMoveChance"); game.move.select(MoveId.AIR_SLASH); @@ -98,7 +97,7 @@ describe("Abilities - Sheer Force", () => { const enemyPokemon = game.scene.getEnemyPokemon(); const headbuttMove = allMoves[MoveId.HEADBUTT]; vi.spyOn(headbuttMove, "calculateBattlePower"); - const headbuttFlinchAttr = headbuttMove.getAttrs(FlinchAttr)[0]; + const headbuttFlinchAttr = headbuttMove.getAttrs("FlinchAttr")[0]; vi.spyOn(headbuttFlinchAttr, "getMoveChance"); game.move.select(MoveId.HEADBUTT); diff --git a/test/abilities/shield_dust.test.ts b/test/abilities/shield_dust.test.ts index 03b175c3ca5..e071cf7a245 100644 --- a/test/abilities/shield_dust.test.ts +++ b/test/abilities/shield_dust.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { applyAbAttrs, applyPreDefendAbAttrs, diff --git a/test/abilities/speed_boost.test.ts b/test/abilities/speed_boost.test.ts index 245bf22345d..a4445d085f3 100644 --- a/test/abilities/speed_boost.test.ts +++ b/test/abilities/speed_boost.test.ts @@ -6,7 +6,7 @@ import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import type { CommandPhase } from "#app/phases/command-phase"; -import { Command } from "#app/ui/command-ui-handler"; +import { Command } from "#enums/command"; import { AttemptRunPhase } from "#app/phases/attempt-run-phase"; describe("Abilities - Speed Boost", () => { diff --git a/test/abilities/stakeout.test.ts b/test/abilities/stakeout.test.ts index 24c8c47df5c..ba4325e0295 100644 --- a/test/abilities/stakeout.test.ts +++ b/test/abilities/stakeout.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { isBetween } from "#app/utils/common"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; diff --git a/test/abilities/storm_drain.test.ts b/test/abilities/storm_drain.test.ts index 36a2112edda..3a6c0aba100 100644 --- a/test/abilities/storm_drain.test.ts +++ b/test/abilities/storm_drain.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/abilities/supreme_overlord.test.ts b/test/abilities/supreme_overlord.test.ts index 6c2ca2d3677..7143b590f68 100644 --- a/test/abilities/supreme_overlord.test.ts +++ b/test/abilities/supreme_overlord.test.ts @@ -2,7 +2,7 @@ import { MoveId } from "#enums/move-id"; import type Move from "#app/data/moves/move"; import { AbilityId } from "#enums/ability-id"; import { SpeciesId } from "#enums/species-id"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; diff --git a/test/abilities/sweet_veil.test.ts b/test/abilities/sweet_veil.test.ts index 131feaf7f56..8a31e676ec5 100644 --- a/test/abilities/sweet_veil.test.ts +++ b/test/abilities/sweet_veil.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { CommandPhase } from "#app/phases/command-phase"; diff --git a/test/abilities/tera_shell.test.ts b/test/abilities/tera_shell.test.ts index 5889115ee95..26babca240d 100644 --- a/test/abilities/tera_shell.test.ts +++ b/test/abilities/tera_shell.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/abilities/unburden.test.ts b/test/abilities/unburden.test.ts index b1b10c378a3..9748b6340f0 100644 --- a/test/abilities/unburden.test.ts +++ b/test/abilities/unburden.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { PostItemLostAbAttr } from "#app/data/abilities/ability"; import { StealHeldItemChanceAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; diff --git a/test/abilities/victory_star.test.ts b/test/abilities/victory_star.test.ts index 77f5de41bb1..a15beac8b2c 100644 --- a/test/abilities/victory_star.test.ts +++ b/test/abilities/victory_star.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; diff --git a/test/abilities/volt_absorb.test.ts b/test/abilities/volt_absorb.test.ts index 2e6abf30885..34dbbbd4783 100644 --- a/test/abilities/volt_absorb.test.ts +++ b/test/abilities/volt_absorb.test.ts @@ -7,7 +7,7 @@ import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; // See also: TypeImmunityAbAttr describe("Abilities - Volt Absorb", () => { diff --git a/test/abilities/wimp_out.test.ts b/test/abilities/wimp_out.test.ts index 2c2ab636961..05c848a75c0 100644 --- a/test/abilities/wimp_out.test.ts +++ b/test/abilities/wimp_out.test.ts @@ -1,5 +1,5 @@ -import { BattlerIndex } from "#app/battle"; -import { ArenaTagSide } from "#app/data/arena-tag"; +import { BattlerIndex } from "#enums/battler-index"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import { allMoves } from "#app/data/data-lists"; import GameManager from "#test/testUtils/gameManager"; import { toDmgValue } from "#app/utils/common"; diff --git a/test/arena/arena_gravity.test.ts b/test/arena/arena_gravity.test.ts index 776bfa0a564..36fe0b58308 100644 --- a/test/arena/arena_gravity.test.ts +++ b/test/arena/arena_gravity.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allMoves } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#enums/arena-tag-type"; diff --git a/test/arena/weather_hail.test.ts b/test/arena/weather_hail.test.ts index 072fbd20498..27cf46fa9f2 100644 --- a/test/arena/weather_hail.test.ts +++ b/test/arena/weather_hail.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { WeatherType } from "#enums/weather-type"; diff --git a/test/battle/battle.test.ts b/test/battle/battle.test.ts index b93c913d09a..3eab06e2f48 100644 --- a/test/battle/battle.test.ts +++ b/test/battle/battle.test.ts @@ -1,6 +1,7 @@ import { allSpecies } from "#app/data/pokemon-species"; import { Stat } from "#enums/stat"; -import { GameModes, getGameMode } from "#app/game-mode"; +import { getGameMode } from "#app/game-mode"; +import { GameModes } from "#enums/game-modes"; import { BattleEndPhase } from "#app/phases/battle-end-phase"; import { CommandPhase } from "#app/phases/command-phase"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; diff --git a/test/battle/double_battle.test.ts b/test/battle/double_battle.test.ts index 31bfc480c47..54c573813d7 100644 --- a/test/battle/double_battle.test.ts +++ b/test/battle/double_battle.test.ts @@ -1,6 +1,7 @@ import { Status } from "#app/data/status-effect"; import { AbilityId } from "#enums/ability-id"; -import { GameModes, getGameMode } from "#app/game-mode"; +import { getGameMode } from "#app/game-mode"; +import { GameModes } from "#enums/game-modes"; import { BattleEndPhase } from "#app/phases/battle-end-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { MoveId } from "#enums/move-id"; diff --git a/test/battle/inverse_battle.test.ts b/test/battle/inverse_battle.test.ts index b0d9c7bb755..66cab3e2d84 100644 --- a/test/battle/inverse_battle.test.ts +++ b/test/battle/inverse_battle.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { PokemonType } from "#enums/pokemon-type"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#enums/arena-tag-type"; diff --git a/test/battlerTags/octolock.test.ts b/test/battlerTags/octolock.test.ts index 63784ed7f1b..d0214f495fc 100644 --- a/test/battlerTags/octolock.test.ts +++ b/test/battlerTags/octolock.test.ts @@ -1,6 +1,7 @@ import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import type Pokemon from "#app/field/pokemon"; -import { BattlerTagLapseType, OctolockTag, TrappedTag } from "#app/data/battler-tags"; +import { OctolockTag, TrappedTag } from "#app/data/battler-tags"; +import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { Stat } from "#enums/stat"; import GameManager from "#test/testUtils/gameManager"; diff --git a/test/battlerTags/substitute.test.ts b/test/battlerTags/substitute.test.ts index 703e0ae75f6..06aedab19f4 100644 --- a/test/battlerTags/substitute.test.ts +++ b/test/battlerTags/substitute.test.ts @@ -1,9 +1,10 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import type { PokemonTurnData, TurnMove } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import type BattleScene from "#app/battle-scene"; -import { BattlerTagLapseType, BindTag, SubstituteTag } from "#app/data/battler-tags"; +import { BindTag, SubstituteTag } from "#app/data/battler-tags"; +import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { MoveId } from "#enums/move-id"; import { PokemonAnimType } from "#app/enums/pokemon-anim-type"; import * as messages from "#app/messages"; diff --git a/test/data/status_effect.test.ts b/test/data/status_effect.test.ts index 4a543a04a1b..06e11dfeb76 100644 --- a/test/data/status_effect.test.ts +++ b/test/data/status_effect.test.ts @@ -6,7 +6,7 @@ import { getStatusEffectObtainText, getStatusEffectOverlapText, } from "#app/data/status-effect"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/endless_boss.test.ts b/test/endless_boss.test.ts index 05c6594bad6..6814cf5e12a 100644 --- a/test/endless_boss.test.ts +++ b/test/endless_boss.test.ts @@ -1,6 +1,6 @@ import { BiomeId } from "#enums/biome-id"; import { SpeciesId } from "#enums/species-id"; -import { GameModes } from "#app/game-mode"; +import { GameModes } from "#enums/game-modes"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import GameManager from "#test/testUtils/gameManager"; diff --git a/test/enemy_command.test.ts b/test/enemy_command.test.ts index 8d6d1756a69..fabb1f1fdb0 100644 --- a/test/enemy_command.test.ts +++ b/test/enemy_command.test.ts @@ -5,7 +5,7 @@ import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import type { EnemyPokemon } from "#app/field/pokemon"; -import { AiType } from "#app/field/pokemon"; +import { AiType } from "#enums/ai-type"; import { randSeedInt } from "#app/utils/common"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; diff --git a/test/escape-calculations.test.ts b/test/escape-calculations.test.ts index fd1e411e786..ca51f40a509 100644 --- a/test/escape-calculations.test.ts +++ b/test/escape-calculations.test.ts @@ -1,6 +1,6 @@ import { AttemptRunPhase } from "#app/phases/attempt-run-phase"; import type { CommandPhase } from "#app/phases/command-phase"; -import { Command } from "#app/ui/command-ui-handler"; +import { Command } from "#enums/command"; import { NumberHolder } from "#app/utils/common"; import { AbilityId } from "#enums/ability-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/final_boss.test.ts b/test/final_boss.test.ts index f1f894a5984..d3fcd3214bb 100644 --- a/test/final_boss.test.ts +++ b/test/final_boss.test.ts @@ -1,4 +1,4 @@ -import { GameModes } from "#app/game-mode"; +import { GameModes } from "#enums/game-modes"; import { TurnHeldItemTransferModifier } from "#app/modifier/modifier"; import { AbilityId } from "#enums/ability-id"; import { BiomeId } from "#enums/biome-id"; diff --git a/test/game-mode.test.ts b/test/game-mode.test.ts index 0483d18e492..4a53739c45e 100644 --- a/test/game-mode.test.ts +++ b/test/game-mode.test.ts @@ -1,5 +1,6 @@ import type { GameMode } from "#app/game-mode"; -import { GameModes, getGameMode } from "#app/game-mode"; +import { getGameMode } from "#app/game-mode"; +import { GameModes } from "#enums/game-modes"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import * as Utils from "#app/utils/common"; import GameManager from "#test/testUtils/gameManager"; diff --git a/test/imports.test.ts b/test/imports.test.ts index 540620d8bb4..8a5c735e950 100644 --- a/test/imports.test.ts +++ b/test/imports.test.ts @@ -4,7 +4,7 @@ import { describe, expect, it } from "vitest"; async function importModule() { try { initStatsKeys(); - const { PokemonMove } = await import("#app/field/pokemon"); + const { PokemonMove } = await import("#app/data/moves/pokemon-move"); const { SpeciesId: Species } = await import("#enums/species-id"); return { PokemonMove, diff --git a/test/items/grip_claw.test.ts b/test/items/grip_claw.test.ts index 3c51e7f868d..9c3e6548140 100644 --- a/test/items/grip_claw.test.ts +++ b/test/items/grip_claw.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import type Pokemon from "#app/field/pokemon"; import type { ContactHeldItemTransferChanceModifier } from "#app/modifier/modifier"; import { AbilityId } from "#enums/ability-id"; diff --git a/test/items/multi_lens.test.ts b/test/items/multi_lens.test.ts index be697eabcf8..11e4c04ec21 100644 --- a/test/items/multi_lens.test.ts +++ b/test/items/multi_lens.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { Stat } from "#enums/stat"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; diff --git a/test/items/reviver_seed.test.ts b/test/items/reviver_seed.test.ts index 2ab3c5ffe74..54927130869 100644 --- a/test/items/reviver_seed.test.ts +++ b/test/items/reviver_seed.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allMoves } from "#app/data/data-lists"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import type { PokemonInstantReviveModifier } from "#app/modifier/modifier"; diff --git a/test/moves/after_you.test.ts b/test/moves/after_you.test.ts index 0e5ee1e7a8f..78372de3fb6 100644 --- a/test/moves/after_you.test.ts +++ b/test/moves/after_you.test.ts @@ -1,6 +1,6 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { MovePhase } from "#app/phases/move-phase"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/alluring_voice.test.ts b/test/moves/alluring_voice.test.ts index 2cfb7a76317..132f83cd4c1 100644 --- a/test/moves/alluring_voice.test.ts +++ b/test/moves/alluring_voice.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { BerryPhase } from "#app/phases/berry-phase"; diff --git a/test/moves/assist.test.ts b/test/moves/assist.test.ts index c0bdf2deea2..bc51f8bd06a 100644 --- a/test/moves/assist.test.ts +++ b/test/moves/assist.test.ts @@ -1,6 +1,6 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { Stat } from "#app/enums/stat"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { CommandPhase } from "#app/phases/command-phase"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; diff --git a/test/moves/aurora_veil.test.ts b/test/moves/aurora_veil.test.ts index 7a00be40d3d..98b490b0e32 100644 --- a/test/moves/aurora_veil.test.ts +++ b/test/moves/aurora_veil.test.ts @@ -1,7 +1,6 @@ import type BattleScene from "#app/battle-scene"; -import { ArenaTagSide } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import type Move from "#app/data/moves/move"; -import { CritOnlyAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import type Pokemon from "#app/field/pokemon"; @@ -166,7 +165,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) = const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (globalScene.arena.getTagOnSide(ArenaTagType.AURORA_VEIL, side)) { - if (move.getAttrs(CritOnlyAttr).length === 0) { + if (move.getAttrs("CritOnlyAttr").length === 0) { globalScene.arena.applyTagsForSide( ArenaTagType.AURORA_VEIL, side, diff --git a/test/moves/baneful_bunker.test.ts b/test/moves/baneful_bunker.test.ts index 80b9b5470b1..eddfa87ead4 100644 --- a/test/moves/baneful_bunker.test.ts +++ b/test/moves/baneful_bunker.test.ts @@ -4,7 +4,7 @@ import GameManager from "#test/testUtils/gameManager"; import { SpeciesId } from "#enums/species-id"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { StatusEffect } from "#app/enums/status-effect"; describe("Moves - Baneful Bunker", () => { diff --git a/test/moves/baton_pass.test.ts b/test/moves/baton_pass.test.ts index b39e51428b1..f6256e95ce8 100644 --- a/test/moves/baton_pass.test.ts +++ b/test/moves/baton_pass.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import GameManager from "#test/testUtils/gameManager"; import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#enums/battler-tag-type"; diff --git a/test/moves/burning_jealousy.test.ts b/test/moves/burning_jealousy.test.ts index 2a74f661922..9e1898d9cd1 100644 --- a/test/moves/burning_jealousy.test.ts +++ b/test/moves/burning_jealousy.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allMoves } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { StatusEffect } from "#app/enums/status-effect"; diff --git a/test/moves/camouflage.test.ts b/test/moves/camouflage.test.ts index 53c44f1386b..03364265179 100644 --- a/test/moves/camouflage.test.ts +++ b/test/moves/camouflage.test.ts @@ -3,7 +3,7 @@ import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { TerrainType } from "#app/data/terrain"; import { PokemonType } from "#enums/pokemon-type"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/test/moves/ceaseless_edge.test.ts b/test/moves/ceaseless_edge.test.ts index 65f9b69509d..fcaddfb0d76 100644 --- a/test/moves/ceaseless_edge.test.ts +++ b/test/moves/ceaseless_edge.test.ts @@ -1,4 +1,5 @@ -import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; +import { ArenaTrapTag } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import { allMoves } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#app/enums/arena-tag-type"; diff --git a/test/moves/chloroblast.test.ts b/test/moves/chloroblast.test.ts index 02f7ac2165c..cf5e791d0b9 100644 --- a/test/moves/chloroblast.test.ts +++ b/test/moves/chloroblast.test.ts @@ -1,4 +1,4 @@ -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/copycat.test.ts b/test/moves/copycat.test.ts index 51d7d36535f..1691cc1478c 100644 --- a/test/moves/copycat.test.ts +++ b/test/moves/copycat.test.ts @@ -1,8 +1,8 @@ -import { BattlerIndex } from "#app/battle"; -import { RandomMoveAttr } from "#app/data/moves/move"; +import { BattlerIndex } from "#enums/battler-index"; +import type { RandomMoveAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { Stat } from "#app/enums/stat"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; @@ -27,7 +27,7 @@ describe("Moves - Copycat", () => { }); beforeEach(() => { - randomMoveAttr = allMoves[MoveId.METRONOME].getAttrs(RandomMoveAttr)[0]; + randomMoveAttr = allMoves[MoveId.METRONOME].getAttrs("RandomMoveAttr")[0]; game = new GameManager(phaserGame); game.override .moveset([MoveId.COPYCAT, MoveId.SPIKY_SHIELD, MoveId.SWORDS_DANCE, MoveId.SPLASH]) diff --git a/test/moves/destiny_bond.test.ts b/test/moves/destiny_bond.test.ts index 81c17551bec..a78d46b464b 100644 --- a/test/moves/destiny_bond.test.ts +++ b/test/moves/destiny_bond.test.ts @@ -1,5 +1,5 @@ import type { ArenaTrapTag } from "#app/data/arena-tag"; -import { ArenaTagSide } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import { allMoves } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#enums/arena-tag-type"; @@ -8,7 +8,7 @@ import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { StatusEffect } from "#enums/status-effect"; import { PokemonInstantReviveModifier } from "#app/modifier/modifier"; diff --git a/test/moves/dig.test.ts b/test/moves/dig.test.ts index 052845ec50d..73540c6ed48 100644 --- a/test/moves/dig.test.ts +++ b/test/moves/dig.test.ts @@ -1,11 +1,11 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allMoves } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#enums/battler-tag-type"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { describe, beforeAll, afterEach, beforeEach, it, expect } from "vitest"; import GameManager from "#test/testUtils/gameManager"; diff --git a/test/moves/disable.test.ts b/test/moves/disable.test.ts index eacf8bf4857..a269a8177aa 100644 --- a/test/moves/disable.test.ts +++ b/test/moves/disable.test.ts @@ -1,5 +1,5 @@ -import { BattlerIndex } from "#app/battle"; -import { MoveResult } from "#app/field/pokemon"; +import { BattlerIndex } from "#enums/battler-index"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/dive.test.ts b/test/moves/dive.test.ts index cce1b5a6d26..9c467976775 100644 --- a/test/moves/dive.test.ts +++ b/test/moves/dive.test.ts @@ -1,6 +1,6 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { StatusEffect } from "#enums/status-effect"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/doodle.test.ts b/test/moves/doodle.test.ts index 686bef144dd..d4d09b3a195 100644 --- a/test/moves/doodle.test.ts +++ b/test/moves/doodle.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { Stat } from "#app/enums/stat"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; diff --git a/test/moves/dragon_cheer.test.ts b/test/moves/dragon_cheer.test.ts index b1eaa3ad747..56feac513a1 100644 --- a/test/moves/dragon_cheer.test.ts +++ b/test/moves/dragon_cheer.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { PokemonType } from "#enums/pokemon-type"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/dragon_tail.test.ts b/test/moves/dragon_tail.test.ts index 07441d9fb2d..8c456f27853 100644 --- a/test/moves/dragon_tail.test.ts +++ b/test/moves/dragon_tail.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allMoves } from "#app/data/data-lists"; import { Status } from "#app/data/status-effect"; import { Challenges } from "#enums/challenges"; diff --git a/test/moves/dynamax_cannon.test.ts b/test/moves/dynamax_cannon.test.ts index 6207ef9b6ca..2cdba48c0ea 100644 --- a/test/moves/dynamax_cannon.test.ts +++ b/test/moves/dynamax_cannon.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allMoves } from "#app/data/data-lists"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; diff --git a/test/moves/electrify.test.ts b/test/moves/electrify.test.ts index 00f96d570a3..b6a3cac9fff 100644 --- a/test/moves/electrify.test.ts +++ b/test/moves/electrify.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { PokemonType } from "#enums/pokemon-type"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; diff --git a/test/moves/electro_shot.test.ts b/test/moves/electro_shot.test.ts index 160f90163e3..3f751687c59 100644 --- a/test/moves/electro_shot.test.ts +++ b/test/moves/electro_shot.test.ts @@ -1,7 +1,7 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { Stat } from "#enums/stat"; import { WeatherType } from "#enums/weather-type"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/encore.test.ts b/test/moves/encore.test.ts index cded90c4a73..120d065d528 100644 --- a/test/moves/encore.test.ts +++ b/test/moves/encore.test.ts @@ -1,6 +1,6 @@ import { BattlerTagType } from "#enums/battler-tag-type"; -import { BattlerIndex } from "#app/battle"; -import { MoveResult } from "#app/field/pokemon"; +import { BattlerIndex } from "#enums/battler-index"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/fairy_lock.test.ts b/test/moves/fairy_lock.test.ts index 5624d038595..74524d67b38 100644 --- a/test/moves/fairy_lock.test.ts +++ b/test/moves/fairy_lock.test.ts @@ -1,4 +1,4 @@ -import { ArenaTagSide } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; diff --git a/test/moves/false_swipe.test.ts b/test/moves/false_swipe.test.ts index 48e4de6fb65..c98b76f1ef1 100644 --- a/test/moves/false_swipe.test.ts +++ b/test/moves/false_swipe.test.ts @@ -1,4 +1,4 @@ -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/fly.test.ts b/test/moves/fly.test.ts index 964d1b65a1e..7d8a6ee659e 100644 --- a/test/moves/fly.test.ts +++ b/test/moves/fly.test.ts @@ -1,13 +1,13 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { StatusEffect } from "#enums/status-effect"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allMoves } from "#app/data/data-lists"; describe("Moves - Fly", () => { diff --git a/test/moves/follow_me.test.ts b/test/moves/follow_me.test.ts index 567320a18e9..8279e7b325a 100644 --- a/test/moves/follow_me.test.ts +++ b/test/moves/follow_me.test.ts @@ -1,5 +1,5 @@ import { Stat } from "#enums/stat"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { MoveId } from "#enums/move-id"; diff --git a/test/moves/freeze_dry.test.ts b/test/moves/freeze_dry.test.ts index dc6af507b16..f1577d3d6c5 100644 --- a/test/moves/freeze_dry.test.ts +++ b/test/moves/freeze_dry.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/fusion_flare_bolt.test.ts b/test/moves/fusion_flare_bolt.test.ts index 1a5d9e44bab..f10ede8717c 100644 --- a/test/moves/fusion_flare_bolt.test.ts +++ b/test/moves/fusion_flare_bolt.test.ts @@ -1,5 +1,5 @@ import { Stat } from "#enums/stat"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allMoves } from "#app/data/data-lists"; import type Move from "#app/data/moves/move"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; diff --git a/test/moves/gastro_acid.test.ts b/test/moves/gastro_acid.test.ts index 6e06f441959..28f2c8e8476 100644 --- a/test/moves/gastro_acid.test.ts +++ b/test/moves/gastro_acid.test.ts @@ -1,8 +1,8 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import GameManager from "#test/testUtils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/test/moves/geomancy.test.ts b/test/moves/geomancy.test.ts index 16244fed93f..452022b4cf1 100644 --- a/test/moves/geomancy.test.ts +++ b/test/moves/geomancy.test.ts @@ -1,6 +1,6 @@ import type { EffectiveStat } from "#enums/stat"; import { Stat } from "#enums/stat"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/gigaton_hammer.test.ts b/test/moves/gigaton_hammer.test.ts index e61a383acc5..be7adf53aa7 100644 --- a/test/moves/gigaton_hammer.test.ts +++ b/test/moves/gigaton_hammer.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import GameManager from "#test/testUtils/gameManager"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/grudge.test.ts b/test/moves/grudge.test.ts index b6ef25d41ff..63018a419e3 100644 --- a/test/moves/grudge.test.ts +++ b/test/moves/grudge.test.ts @@ -1,7 +1,7 @@ import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/test/moves/heal_block.test.ts b/test/moves/heal_block.test.ts index 58e1a5e04a5..39e8efea827 100644 --- a/test/moves/heal_block.test.ts +++ b/test/moves/heal_block.test.ts @@ -1,5 +1,5 @@ -import { BattlerIndex } from "#app/battle"; -import { ArenaTagSide } from "#app/data/arena-tag"; +import { BattlerIndex } from "#enums/battler-index"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import GameManager from "#test/testUtils/gameManager"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#enums/arena-tag-type"; diff --git a/test/moves/instruct.test.ts b/test/moves/instruct.test.ts index e0cfa93cf59..74de4c5da17 100644 --- a/test/moves/instruct.test.ts +++ b/test/moves/instruct.test.ts @@ -1,7 +1,7 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allMoves } from "#app/data/data-lists"; import type Pokemon from "#app/field/pokemon"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import type { MovePhase } from "#app/phases/move-phase"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; diff --git a/test/moves/jaw_lock.test.ts b/test/moves/jaw_lock.test.ts index 6084a47ad99..e0815a4c3c9 100644 --- a/test/moves/jaw_lock.test.ts +++ b/test/moves/jaw_lock.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { BerryPhase } from "#app/phases/berry-phase"; diff --git a/test/moves/lash_out.test.ts b/test/moves/lash_out.test.ts index a63ab2a467c..c0c0881b340 100644 --- a/test/moves/lash_out.test.ts +++ b/test/moves/lash_out.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allMoves } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; diff --git a/test/moves/last-resort.test.ts b/test/moves/last-resort.test.ts index 0e6c1a6ba15..e4b8346053b 100644 --- a/test/moves/last-resort.test.ts +++ b/test/moves/last-resort.test.ts @@ -1,5 +1,5 @@ -import { BattlerIndex } from "#app/battle"; -import { MoveResult } from "#app/field/pokemon"; +import { BattlerIndex } from "#enums/battler-index"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/last_respects.test.ts b/test/moves/last_respects.test.ts index 96a8b5955d0..b2eb4c6695b 100644 --- a/test/moves/last_respects.test.ts +++ b/test/moves/last_respects.test.ts @@ -1,5 +1,5 @@ import { MoveId } from "#enums/move-id"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { SpeciesId } from "#enums/species-id"; import { AbilityId } from "#enums/ability-id"; import GameManager from "#test/testUtils/gameManager"; diff --git a/test/moves/light_screen.test.ts b/test/moves/light_screen.test.ts index 4230790e688..404d30920c7 100644 --- a/test/moves/light_screen.test.ts +++ b/test/moves/light_screen.test.ts @@ -1,7 +1,6 @@ import type BattleScene from "#app/battle-scene"; -import { ArenaTagSide } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import type Move from "#app/data/moves/move"; -import { CritOnlyAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#app/enums/arena-tag-type"; @@ -129,7 +128,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) = const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (globalScene.arena.getTagOnSide(ArenaTagType.LIGHT_SCREEN, side)) { - if (move.getAttrs(CritOnlyAttr).length === 0) { + if (move.getAttrs("CritOnlyAttr").length === 0) { globalScene.arena.applyTagsForSide( ArenaTagType.LIGHT_SCREEN, side, diff --git a/test/moves/magic_coat.test.ts b/test/moves/magic_coat.test.ts index 4f9e3977305..a20aaf38043 100644 --- a/test/moves/magic_coat.test.ts +++ b/test/moves/magic_coat.test.ts @@ -1,11 +1,11 @@ -import { BattlerIndex } from "#app/battle"; -import { ArenaTagSide } from "#app/data/arena-tag"; +import { BattlerIndex } from "#enums/battler-index"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import { allMoves } from "#app/data/data-lists"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Stat } from "#app/enums/stat"; import { StatusEffect } from "#app/enums/status-effect"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/metal_burst.test.ts b/test/moves/metal_burst.test.ts index 0222dd222be..bcb9d938985 100644 --- a/test/moves/metal_burst.test.ts +++ b/test/moves/metal_burst.test.ts @@ -1,5 +1,5 @@ -import { BattlerIndex } from "#app/battle"; -import { MoveResult } from "#app/field/pokemon"; +import { BattlerIndex } from "#enums/battler-index"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/metronome.test.ts b/test/moves/metronome.test.ts index 0e6db09ae5c..ec610eeed45 100644 --- a/test/moves/metronome.test.ts +++ b/test/moves/metronome.test.ts @@ -1,5 +1,5 @@ import { RechargingTag, SemiInvulnerableTag } from "#app/data/battler-tags"; -import { RandomMoveAttr } from "#app/data/moves/move"; +import type { RandomMoveAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { Stat } from "#app/enums/stat"; @@ -27,7 +27,7 @@ describe("Moves - Metronome", () => { }); beforeEach(() => { - randomMoveAttr = allMoves[MoveId.METRONOME].getAttrs(RandomMoveAttr)[0]; + randomMoveAttr = allMoves[MoveId.METRONOME].getAttrs("RandomMoveAttr")[0]; game = new GameManager(phaserGame); game.override .moveset([MoveId.METRONOME, MoveId.SPLASH]) diff --git a/test/moves/miracle_eye.test.ts b/test/moves/miracle_eye.test.ts index dd5fb1c355b..1238ae6a650 100644 --- a/test/moves/miracle_eye.test.ts +++ b/test/moves/miracle_eye.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; diff --git a/test/moves/mirror_move.test.ts b/test/moves/mirror_move.test.ts index 18e115745b9..fe40402e5a2 100644 --- a/test/moves/mirror_move.test.ts +++ b/test/moves/mirror_move.test.ts @@ -1,6 +1,6 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { Stat } from "#app/enums/stat"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/moongeist_beam.test.ts b/test/moves/moongeist_beam.test.ts index f462d81943f..28bd3f70daa 100644 --- a/test/moves/moongeist_beam.test.ts +++ b/test/moves/moongeist_beam.test.ts @@ -1,4 +1,3 @@ -import { RandomMoveAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; @@ -49,7 +48,7 @@ describe("Moves - Moongeist Beam", () => { // Also covers Photon Geyser and Sunsteel Strike it("should not ignore enemy abilities when called by another move, such as metronome", async () => { await game.classicMode.startBattle([SpeciesId.MILOTIC]); - vi.spyOn(allMoves[MoveId.METRONOME].getAttrs(RandomMoveAttr)[0], "getMoveOverride").mockReturnValue( + vi.spyOn(allMoves[MoveId.METRONOME].getAttrs("RandomMoveAttr")[0], "getMoveOverride").mockReturnValue( MoveId.MOONGEIST_BEAM, ); diff --git a/test/moves/multi_target.test.ts b/test/moves/multi_target.test.ts index 4572097296c..139b669da7b 100644 --- a/test/moves/multi_target.test.ts +++ b/test/moves/multi_target.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { SpeciesId } from "#enums/species-id"; import { toDmgValue } from "#app/utils/common"; diff --git a/test/moves/order_up.test.ts b/test/moves/order_up.test.ts index c6ca7ac06da..adf37c0719e 100644 --- a/test/moves/order_up.test.ts +++ b/test/moves/order_up.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#enums/battler-tag-type"; import { PokemonAnimType } from "#enums/pokemon-anim-type"; import type { EffectiveStat } from "#enums/stat"; diff --git a/test/moves/plasma_fists.test.ts b/test/moves/plasma_fists.test.ts index 9493f69d70f..7d1985be13e 100644 --- a/test/moves/plasma_fists.test.ts +++ b/test/moves/plasma_fists.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { PokemonType } from "#enums/pokemon-type"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; diff --git a/test/moves/pledge_moves.test.ts b/test/moves/pledge_moves.test.ts index 2500563d44e..f653e245f6f 100644 --- a/test/moves/pledge_moves.test.ts +++ b/test/moves/pledge_moves.test.ts @@ -1,7 +1,6 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allAbilities } from "#app/data/data-lists"; -import { ArenaTagSide } from "#app/data/arena-tag"; -import { FlinchAttr } from "#app/data/moves/move"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import { allMoves } from "#app/data/data-lists"; import { PokemonType } from "#enums/pokemon-type"; import { ArenaTagType } from "#enums/arena-tag-type"; @@ -228,7 +227,7 @@ describe("Moves - Pledge Moves", () => { await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); - const ironHeadFlinchAttr = allMoves[MoveId.IRON_HEAD].getAttrs(FlinchAttr)[0]; + const ironHeadFlinchAttr = allMoves[MoveId.IRON_HEAD].getAttrs("FlinchAttr")[0]; vi.spyOn(ironHeadFlinchAttr, "getMoveChance"); game.move.select(MoveId.WATER_PLEDGE, 0, BattlerIndex.ENEMY); diff --git a/test/moves/pollen_puff.test.ts b/test/moves/pollen_puff.test.ts index d61303bcfcc..e6dcd2c41d0 100644 --- a/test/moves/pollen_puff.test.ts +++ b/test/moves/pollen_puff.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/powder.test.ts b/test/moves/powder.test.ts index d335e29996e..38e35d60335 100644 --- a/test/moves/powder.test.ts +++ b/test/moves/powder.test.ts @@ -1,5 +1,6 @@ -import { BattlerIndex } from "#app/battle"; -import { MoveResult, PokemonMove } from "#app/field/pokemon"; +import { BattlerIndex } from "#enums/battler-index"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { MoveResult } from "#enums/move-result"; import { BerryPhase } from "#app/phases/berry-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { AbilityId } from "#enums/ability-id"; diff --git a/test/moves/protect.test.ts b/test/moves/protect.test.ts index 519021023fa..754a3433701 100644 --- a/test/moves/protect.test.ts +++ b/test/moves/protect.test.ts @@ -6,9 +6,10 @@ import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { Stat } from "#enums/stat"; import { allMoves } from "#app/data/data-lists"; -import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; -import { BattlerIndex } from "#app/battle"; -import { MoveResult } from "#app/field/pokemon"; +import { ArenaTrapTag } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; +import { BattlerIndex } from "#enums/battler-index"; +import { MoveResult } from "#enums/move-result"; describe("Moves - Protect", () => { let phaserGame: Phaser.Game; diff --git a/test/moves/purify.test.ts b/test/moves/purify.test.ts index cab0a70818f..0510260b755 100644 --- a/test/moves/purify.test.ts +++ b/test/moves/purify.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { Status } from "#app/data/status-effect"; import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import { MoveEndPhase } from "#app/phases/move-end-phase"; diff --git a/test/moves/quash.test.ts b/test/moves/quash.test.ts index 88cb0aba31f..7c8306acd22 100644 --- a/test/moves/quash.test.ts +++ b/test/moves/quash.test.ts @@ -1,9 +1,9 @@ import { SpeciesId } from "#enums/species-id"; import { MoveId } from "#enums/move-id"; import { AbilityId } from "#enums/ability-id"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { WeatherType } from "#enums/weather-type"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { describe, beforeAll, afterEach, beforeEach, it, expect } from "vitest"; diff --git a/test/moves/quick_guard.test.ts b/test/moves/quick_guard.test.ts index 0c0ab8d6ad0..49f501fb839 100644 --- a/test/moves/quick_guard.test.ts +++ b/test/moves/quick_guard.test.ts @@ -5,8 +5,8 @@ import { SpeciesId } from "#enums/species-id"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { Stat } from "#enums/stat"; -import { BattlerIndex } from "#app/battle"; -import { MoveResult } from "#app/field/pokemon"; +import { BattlerIndex } from "#enums/battler-index"; +import { MoveResult } from "#enums/move-result"; describe("Moves - Quick Guard", () => { let phaserGame: Phaser.Game; diff --git a/test/moves/rage_fist.test.ts b/test/moves/rage_fist.test.ts index cd9147637a5..31dd987cb87 100644 --- a/test/moves/rage_fist.test.ts +++ b/test/moves/rage_fist.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/rage_powder.test.ts b/test/moves/rage_powder.test.ts index e3212e9876c..807000a0ff0 100644 --- a/test/moves/rage_powder.test.ts +++ b/test/moves/rage_powder.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/reflect.test.ts b/test/moves/reflect.test.ts index d13fb7a095c..5e10de42a3c 100644 --- a/test/moves/reflect.test.ts +++ b/test/moves/reflect.test.ts @@ -1,7 +1,6 @@ import type BattleScene from "#app/battle-scene"; -import { ArenaTagSide } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import type Move from "#app/data/moves/move"; -import { CritOnlyAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#app/enums/arena-tag-type"; @@ -145,7 +144,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) = const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (globalScene.arena.getTagOnSide(ArenaTagType.REFLECT, side)) { - if (move.getAttrs(CritOnlyAttr).length === 0) { + if (move.getAttrs("CritOnlyAttr").length === 0) { globalScene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, false, attacker, move.category, multiplierHolder); } } diff --git a/test/moves/revival_blessing.test.ts b/test/moves/revival_blessing.test.ts index 07ebf657c66..f22c0467378 100644 --- a/test/moves/revival_blessing.test.ts +++ b/test/moves/revival_blessing.test.ts @@ -1,5 +1,5 @@ -import { BattlerIndex } from "#app/battle"; -import { MoveResult } from "#app/field/pokemon"; +import { BattlerIndex } from "#enums/battler-index"; +import { MoveResult } from "#enums/move-result"; import { toDmgValue } from "#app/utils/common"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; diff --git a/test/moves/roost.test.ts b/test/moves/roost.test.ts index 76aab1e572f..1e588cb1e15 100644 --- a/test/moves/roost.test.ts +++ b/test/moves/roost.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { PokemonType } from "#enums/pokemon-type"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { MoveId } from "#enums/move-id"; diff --git a/test/moves/round.test.ts b/test/moves/round.test.ts index 503ce125582..630137b7dce 100644 --- a/test/moves/round.test.ts +++ b/test/moves/round.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allMoves } from "#app/data/data-lists"; import type { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { AbilityId } from "#enums/ability-id"; diff --git a/test/moves/safeguard.test.ts b/test/moves/safeguard.test.ts index fc8bef80d6d..2ba53016833 100644 --- a/test/moves/safeguard.test.ts +++ b/test/moves/safeguard.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { PostDefendContactApplyStatusEffectAbAttr } from "#app/data/abilities/ability"; import { allAbilities } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; diff --git a/test/moves/scale_shot.test.ts b/test/moves/scale_shot.test.ts index f61a9a1e276..a5872579003 100644 --- a/test/moves/scale_shot.test.ts +++ b/test/moves/scale_shot.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allMoves } from "#app/data/data-lists"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; diff --git a/test/moves/secret_power.test.ts b/test/moves/secret_power.test.ts index ac06f03698c..d555431656f 100644 --- a/test/moves/secret_power.test.ts +++ b/test/moves/secret_power.test.ts @@ -8,9 +8,9 @@ import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { StatusEffect } from "#enums/status-effect"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { ArenaTagType } from "#enums/arena-tag-type"; -import { ArenaTagSide } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import { MoveEffectChanceMultiplierAbAttr } from "#app/data/abilities/ability"; import { allAbilities } from "#app/data/data-lists"; diff --git a/test/moves/shed_tail.test.ts b/test/moves/shed_tail.test.ts index 84a5d5ba914..81ab9215bc9 100644 --- a/test/moves/shed_tail.test.ts +++ b/test/moves/shed_tail.test.ts @@ -1,5 +1,5 @@ import { SubstituteTag } from "#app/data/battler-tags"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/shell_side_arm.test.ts b/test/moves/shell_side_arm.test.ts index ad79091a5e3..35246e10e3f 100644 --- a/test/moves/shell_side_arm.test.ts +++ b/test/moves/shell_side_arm.test.ts @@ -1,5 +1,5 @@ -import { BattlerIndex } from "#app/battle"; -import { ShellSideArmCategoryAttr } from "#app/data/moves/move"; +import { BattlerIndex } from "#enums/battler-index"; +import type { ShellSideArmCategoryAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import type Move from "#app/data/moves/move"; import { AbilityId } from "#enums/ability-id"; @@ -27,7 +27,7 @@ describe("Moves - Shell Side Arm", () => { beforeEach(() => { shellSideArm = allMoves[MoveId.SHELL_SIDE_ARM]; - shellSideArmAttr = shellSideArm.getAttrs(ShellSideArmCategoryAttr)[0]; + shellSideArmAttr = shellSideArm.getAttrs("ShellSideArmCategoryAttr")[0]; game = new GameManager(phaserGame); game.override .moveset([MoveId.SHELL_SIDE_ARM, MoveId.SPLASH]) diff --git a/test/moves/shell_trap.test.ts b/test/moves/shell_trap.test.ts index 9a38bf4486b..a3f55cef10f 100644 --- a/test/moves/shell_trap.test.ts +++ b/test/moves/shell_trap.test.ts @@ -1,8 +1,8 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allMoves } from "#app/data/data-lists"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { BerryPhase } from "#app/phases/berry-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { MovePhase } from "#app/phases/move-phase"; diff --git a/test/moves/sketch.test.ts b/test/moves/sketch.test.ts index 23e7f4ef3ab..c6fb7b4a32a 100644 --- a/test/moves/sketch.test.ts +++ b/test/moves/sketch.test.ts @@ -1,12 +1,13 @@ import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; -import { MoveResult, PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { MoveResult } from "#enums/move-result"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { StatusEffect } from "#app/enums/status-effect"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { RandomMoveAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; diff --git a/test/moves/sleep_talk.test.ts b/test/moves/sleep_talk.test.ts index 820a5f9082a..1d9aec77ea2 100644 --- a/test/moves/sleep_talk.test.ts +++ b/test/moves/sleep_talk.test.ts @@ -1,6 +1,6 @@ import { Stat } from "#app/enums/stat"; import { StatusEffect } from "#app/enums/status-effect"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/solar_beam.test.ts b/test/moves/solar_beam.test.ts index 7515843a13f..55d2ee5d13c 100644 --- a/test/moves/solar_beam.test.ts +++ b/test/moves/solar_beam.test.ts @@ -1,7 +1,7 @@ import { allMoves } from "#app/data/data-lists"; import { BattlerTagType } from "#enums/battler-tag-type"; import { WeatherType } from "#enums/weather-type"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/spectral_thief.test.ts b/test/moves/spectral_thief.test.ts index df560169078..808e9174caf 100644 --- a/test/moves/spectral_thief.test.ts +++ b/test/moves/spectral_thief.test.ts @@ -1,5 +1,5 @@ import { AbilityId } from "#enums/ability-id"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { Stat } from "#enums/stat"; import { allMoves } from "#app/data/data-lists"; import { MoveId } from "#enums/move-id"; diff --git a/test/moves/spikes.test.ts b/test/moves/spikes.test.ts index 278c4510239..d847f296e59 100644 --- a/test/moves/spikes.test.ts +++ b/test/moves/spikes.test.ts @@ -4,7 +4,8 @@ import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; +import { ArenaTrapTag } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; describe("Moves - Spikes", () => { let phaserGame: Phaser.Game; diff --git a/test/moves/spit_up.test.ts b/test/moves/spit_up.test.ts index 2d576168f4c..83549c28f40 100644 --- a/test/moves/spit_up.test.ts +++ b/test/moves/spit_up.test.ts @@ -3,7 +3,7 @@ import { StockpilingTag } from "#app/data/battler-tags"; import { allMoves } from "#app/data/data-lists"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import type { TurnMove } from "#app/field/pokemon"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import GameManager from "#test/testUtils/gameManager"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; diff --git a/test/moves/spotlight.test.ts b/test/moves/spotlight.test.ts index 2798dfa282a..602cedcaec9 100644 --- a/test/moves/spotlight.test.ts +++ b/test/moves/spotlight.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/steamroller.test.ts b/test/moves/steamroller.test.ts index f4f8131ff7b..4eb011c47f5 100644 --- a/test/moves/steamroller.test.ts +++ b/test/moves/steamroller.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allMoves } from "#app/data/data-lists"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import type { DamageCalculationResult } from "#app/field/pokemon"; diff --git a/test/moves/stockpile.test.ts b/test/moves/stockpile.test.ts index 5bf2b74d4d9..4baa7949bc6 100644 --- a/test/moves/stockpile.test.ts +++ b/test/moves/stockpile.test.ts @@ -1,7 +1,7 @@ import { Stat } from "#enums/stat"; import { StockpilingTag } from "#app/data/battler-tags"; import type { TurnMove } from "#app/field/pokemon"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { CommandPhase } from "#app/phases/command-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { AbilityId } from "#enums/ability-id"; diff --git a/test/moves/substitute.test.ts b/test/moves/substitute.test.ts index 454e729a67b..857f20c7fa0 100644 --- a/test/moves/substitute.test.ts +++ b/test/moves/substitute.test.ts @@ -1,12 +1,12 @@ -import { BattlerIndex } from "#app/battle"; -import { ArenaTagSide } from "#app/data/arena-tag"; +import { BattlerIndex } from "#enums/battler-index"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import { SubstituteTag, TrappedTag } from "#app/data/battler-tags"; import { StealHeldItemChanceAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import type { CommandPhase } from "#app/phases/command-phase"; import GameManager from "#test/testUtils/gameManager"; -import { Command } from "#app/ui/command-ui-handler"; +import { Command } from "#enums/command"; import { UiMode } from "#enums/ui-mode"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#enums/arena-tag-type"; diff --git a/test/moves/swallow.test.ts b/test/moves/swallow.test.ts index 4452636c3af..bb95c2c593d 100644 --- a/test/moves/swallow.test.ts +++ b/test/moves/swallow.test.ts @@ -2,7 +2,7 @@ import { Stat } from "#enums/stat"; import { StockpilingTag } from "#app/data/battler-tags"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import type { TurnMove } from "#app/field/pokemon"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { MovePhase } from "#app/phases/move-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { AbilityId } from "#enums/ability-id"; diff --git a/test/moves/syrup_bomb.test.ts b/test/moves/syrup_bomb.test.ts index 76bc7e7bae0..4b2821b439c 100644 --- a/test/moves/syrup_bomb.test.ts +++ b/test/moves/syrup_bomb.test.ts @@ -5,7 +5,7 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { Stat } from "#enums/stat"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; describe("Moves - SYRUP BOMB", () => { diff --git a/test/moves/tailwind.test.ts b/test/moves/tailwind.test.ts index 83078d7bf58..874934fc8f3 100644 --- a/test/moves/tailwind.test.ts +++ b/test/moves/tailwind.test.ts @@ -1,4 +1,4 @@ -import { ArenaTagSide } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; diff --git a/test/moves/tar_shot.test.ts b/test/moves/tar_shot.test.ts index 21eace0ef1f..fefd1a30e1e 100644 --- a/test/moves/tar_shot.test.ts +++ b/test/moves/tar_shot.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { PokemonType } from "#enums/pokemon-type"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/taunt.test.ts b/test/moves/taunt.test.ts index e214bd77ef7..fabb95f98b2 100644 --- a/test/moves/taunt.test.ts +++ b/test/moves/taunt.test.ts @@ -4,7 +4,7 @@ import { AbilityId } from "#enums/ability-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { BattlerTagType } from "#enums/battler-tag-type"; describe("Moves - Taunt", () => { diff --git a/test/moves/telekinesis.test.ts b/test/moves/telekinesis.test.ts index 18df17f1587..5c9f1e22395 100644 --- a/test/moves/telekinesis.test.ts +++ b/test/moves/telekinesis.test.ts @@ -3,11 +3,11 @@ import { allMoves } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; describe("Moves - Telekinesis", () => { let phaserGame: Phaser.Game; diff --git a/test/moves/tera_blast.test.ts b/test/moves/tera_blast.test.ts index 4e3f9c6869b..2f3bdcebfcf 100644 --- a/test/moves/tera_blast.test.ts +++ b/test/moves/tera_blast.test.ts @@ -1,6 +1,6 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { Stat } from "#enums/stat"; -import { TeraMoveCategoryAttr } from "#app/data/moves/move"; +import type { TeraMoveCategoryAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import type Move from "#app/data/moves/move"; import { PokemonType } from "#enums/pokemon-type"; @@ -23,7 +23,7 @@ describe("Moves - Tera Blast", () => { type: Phaser.HEADLESS, }); moveToCheck = allMoves[MoveId.TERA_BLAST]; - teraBlastAttr = moveToCheck.getAttrs(TeraMoveCategoryAttr)[0]; + teraBlastAttr = moveToCheck.getAttrs("TeraMoveCategoryAttr")[0]; }); afterEach(() => { diff --git a/test/moves/tera_starstorm.test.ts b/test/moves/tera_starstorm.test.ts index bd1fa14398d..2eaf2ec0372 100644 --- a/test/moves/tera_starstorm.test.ts +++ b/test/moves/tera_starstorm.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { PokemonType } from "#enums/pokemon-type"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; diff --git a/test/moves/throat_chop.test.ts b/test/moves/throat_chop.test.ts index c1c9c4e94ad..aa92f657c86 100644 --- a/test/moves/throat_chop.test.ts +++ b/test/moves/throat_chop.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#app/enums/stat"; diff --git a/test/moves/torment.test.ts b/test/moves/torment.test.ts index b35b16249ef..e4d926d9601 100644 --- a/test/moves/torment.test.ts +++ b/test/moves/torment.test.ts @@ -4,7 +4,7 @@ import { AbilityId } from "#enums/ability-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { BattlerTagType } from "#enums/battler-tag-type"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; diff --git a/test/moves/toxic.test.ts b/test/moves/toxic.test.ts index 5cdfe78d502..eb23885b4b5 100644 --- a/test/moves/toxic.test.ts +++ b/test/moves/toxic.test.ts @@ -4,7 +4,7 @@ import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { StatusEffect } from "#enums/status-effect"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { allMoves } from "#app/data/data-lists"; describe("Moves - Toxic", () => { diff --git a/test/moves/toxic_spikes.test.ts b/test/moves/toxic_spikes.test.ts index 11ed7514633..bf1f5acc1cb 100644 --- a/test/moves/toxic_spikes.test.ts +++ b/test/moves/toxic_spikes.test.ts @@ -1,7 +1,8 @@ import type { ArenaTrapTag } from "#app/data/arena-tag"; -import { ArenaTagSide } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#enums/arena-tag-side"; import type { SessionSaveData } from "#app/system/game-data"; -import { decrypt, encrypt, GameData } from "#app/system/game-data"; +import { GameData } from "#app/system/game-data"; +import { decrypt, encrypt } from "#app/utils/data"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#enums/arena-tag-type"; import { MoveId } from "#enums/move-id"; diff --git a/test/moves/transform.test.ts b/test/moves/transform.test.ts index ca326da5748..8ee65802b37 100644 --- a/test/moves/transform.test.ts +++ b/test/moves/transform.test.ts @@ -6,7 +6,7 @@ import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { MoveId } from "#enums/move-id"; import { Stat, EFFECTIVE_STATS } from "#enums/stat"; import { AbilityId } from "#enums/ability-id"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; // TODO: Add more tests once Transform is fully implemented describe("Moves - Transform", () => { diff --git a/test/moves/triple_arrows.test.ts b/test/moves/triple_arrows.test.ts index 6a14a7642fa..89ccb4e5b04 100644 --- a/test/moves/triple_arrows.test.ts +++ b/test/moves/triple_arrows.test.ts @@ -1,4 +1,4 @@ -import { FlinchAttr, StatStageChangeAttr } from "#app/data/moves/move"; +import type { FlinchAttr, StatStageChangeAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; @@ -20,8 +20,8 @@ describe("Moves - Triple Arrows", () => { type: Phaser.HEADLESS, }); tripleArrows = allMoves[MoveId.TRIPLE_ARROWS]; - flinchAttr = tripleArrows.getAttrs(FlinchAttr)[0]; - defDropAttr = tripleArrows.getAttrs(StatStageChangeAttr)[0]; + flinchAttr = tripleArrows.getAttrs("FlinchAttr")[0]; + defDropAttr = tripleArrows.getAttrs("StatStageChangeAttr")[0]; }); afterEach(() => { diff --git a/test/moves/upper_hand.test.ts b/test/moves/upper_hand.test.ts index 741594c7e47..e3d490ba6fa 100644 --- a/test/moves/upper_hand.test.ts +++ b/test/moves/upper_hand.test.ts @@ -1,5 +1,5 @@ -import { BattlerIndex } from "#app/battle"; -import { MoveResult } from "#app/field/pokemon"; +import { BattlerIndex } from "#enums/battler-index"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/whirlwind.test.ts b/test/moves/whirlwind.test.ts index c457bdb67d7..00d7c16561c 100644 --- a/test/moves/whirlwind.test.ts +++ b/test/moves/whirlwind.test.ts @@ -1,7 +1,7 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { Challenges } from "#enums/challenges"; import { PokemonType } from "#enums/pokemon-type"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; @@ -11,7 +11,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite import { Status } from "#app/data/status-effect"; import { StatusEffect } from "#enums/status-effect"; import { globalScene } from "#app/global-scene"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { BattleType } from "#enums/battle-type"; import { TrainerType } from "#enums/trainer-type"; diff --git a/test/moves/will_o_wisp.test.ts b/test/moves/will_o_wisp.test.ts index ce747dbad0b..40495009d2a 100644 --- a/test/moves/will_o_wisp.test.ts +++ b/test/moves/will_o_wisp.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts index 786090aa8d6..d208a859825 100644 --- a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts +++ b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts @@ -8,7 +8,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; import type BattleScene from "#app/battle-scene"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { AnOfferYouCantRefuseEncounter } from "#app/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; diff --git a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts index 7e569d0cdf7..8e58a0ca242 100644 --- a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts +++ b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts @@ -11,7 +11,7 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import { MoveId } from "#enums/move-id"; import type BattleScene from "#app/battle-scene"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { UiMode } from "#enums/ui-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; diff --git a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index fc0ef0e5a86..778f8397417 100644 --- a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -15,7 +15,7 @@ import { import { MoveId } from "#enums/move-id"; import type BattleScene from "#app/battle-scene"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { UiMode } from "#enums/ui-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; diff --git a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts index 76a562b5851..ee4aefd9904 100644 --- a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts +++ b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts @@ -17,7 +17,7 @@ import { MoveId } from "#enums/move-id"; import { DancingLessonsEncounter } from "#app/data/mystery-encounters/encounters/dancing-lessons-encounter"; import { UiMode } from "#enums/ui-mode"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; diff --git a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts index 7038fff3117..e0e8b3d90d6 100644 --- a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts +++ b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts @@ -11,7 +11,7 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import { MoveId } from "#enums/move-id"; import type BattleScene from "#app/battle-scene"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { UiMode } from "#enums/ui-mode"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; diff --git a/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts b/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts index 4eaf2cef1da..e31a3a5cc3c 100644 --- a/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts +++ b/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts @@ -22,7 +22,7 @@ import { CommandPhase } from "#app/phases/command-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { FunAndGamesEncounter } from "#app/data/mystery-encounters/encounters/fun-and-games-encounter"; import { MoveId } from "#enums/move-id"; -import { Command } from "#app/ui/command-ui-handler"; +import { Command } from "#enums/command"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; const namespace = "mysteryEncounters/funAndGames"; diff --git a/test/mystery-encounter/encounters/part-timer-encounter.test.ts b/test/mystery-encounter/encounters/part-timer-encounter.test.ts index fa85f373a35..be985ea0593 100644 --- a/test/mystery-encounter/encounters/part-timer-encounter.test.ts +++ b/test/mystery-encounter/encounters/part-timer-encounter.test.ts @@ -14,7 +14,7 @@ import { CIVILIZATION_ENCOUNTER_BIOMES } from "#app/data/mystery-encounters/myst import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { PartTimerEncounter } from "#app/data/mystery-encounters/encounters/part-timer-encounter"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { MoveId } from "#enums/move-id"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; diff --git a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index e50a19a0a80..5926c1ed2e7 100644 --- a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -17,7 +17,7 @@ import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters import { Nature } from "#enums/nature"; import { BerryType } from "#enums/berry-type"; import { BattlerTagType } from "#enums/battler-tag-type"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { UiMode } from "#enums/ui-mode"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { BerryModifier, PokemonBaseStatTotalModifier } from "#app/modifier/modifier"; diff --git a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index 999936c8832..47c75eb19fc 100644 --- a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -12,7 +12,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { BiomeId } from "#enums/biome-id"; import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { HealShopCostModifier, HitHealModifier, TurnHealModifier } from "#app/modifier/modifier"; import { ModifierTier } from "#app/modifier/modifier-tier"; import { modifierTypes, type PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; diff --git a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts index 0c3131de1d2..12545b8d70a 100644 --- a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts +++ b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts @@ -10,7 +10,7 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import { MoveId } from "#enums/move-id"; import type BattleScene from "#app/battle-scene"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; diff --git a/test/phases/frenzy-move-reset.test.ts b/test/phases/frenzy-move-reset.test.ts index 1879a14d301..878c58d8666 100644 --- a/test/phases/frenzy-move-reset.test.ts +++ b/test/phases/frenzy-move-reset.test.ts @@ -1,4 +1,4 @@ -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#enums/battler-tag-type"; import { StatusEffect } from "#enums/status-effect"; diff --git a/test/reload.test.ts b/test/reload.test.ts index 6c535ca4722..8b817bbfe97 100644 --- a/test/reload.test.ts +++ b/test/reload.test.ts @@ -1,4 +1,4 @@ -import { GameModes } from "#app/game-mode"; +import { GameModes } from "#enums/game-modes"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import type OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler"; import { UiMode } from "#enums/ui-mode"; diff --git a/test/testUtils/gameManager.ts b/test/testUtils/gameManager.ts index 437c8d9f083..128217a6472 100644 --- a/test/testUtils/gameManager.ts +++ b/test/testUtils/gameManager.ts @@ -1,9 +1,10 @@ import { updateUserInfo } from "#app/account"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import BattleScene from "#app/battle-scene"; import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import Trainer from "#app/field/trainer"; -import { GameModes, getGameMode } from "#app/game-mode"; +import { getGameMode } from "#app/game-mode"; +import { GameModes } from "#enums/game-modes"; import { globalScene } from "#app/global-scene"; import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type"; import overrides from "#app/overrides"; diff --git a/test/testUtils/gameManagerUtils.ts b/test/testUtils/gameManagerUtils.ts index 18dd83995a3..d582f8c04e3 100644 --- a/test/testUtils/gameManagerUtils.ts +++ b/test/testUtils/gameManagerUtils.ts @@ -5,7 +5,8 @@ import { getDailyRunStarters } from "#app/data/daily-run"; import { Gender } from "#app/data/gender"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { PlayerPokemon } from "#app/field/pokemon"; -import { GameModes, getGameMode } from "#app/game-mode"; +import { getGameMode } from "#app/game-mode"; +import { GameModes } from "#enums/game-modes"; import type { StarterMoveset } from "#app/system/game-data"; import type { Starter } from "#app/ui/starter-select-ui-handler"; import { MoveId } from "#enums/move-id"; diff --git a/test/testUtils/helpers/classicModeHelper.ts b/test/testUtils/helpers/classicModeHelper.ts index c4f086bd628..eff97483777 100644 --- a/test/testUtils/helpers/classicModeHelper.ts +++ b/test/testUtils/helpers/classicModeHelper.ts @@ -1,6 +1,7 @@ import { BattleStyle } from "#app/enums/battle-style"; import type { SpeciesId } from "#enums/species-id"; -import { GameModes, getGameMode } from "#app/game-mode"; +import { getGameMode } from "#app/game-mode"; +import { GameModes } from "#enums/game-modes"; import overrides from "#app/overrides"; import { CommandPhase } from "#app/phases/command-phase"; import { EncounterPhase } from "#app/phases/encounter-phase"; diff --git a/test/testUtils/helpers/field-helper.ts b/test/testUtils/helpers/field-helper.ts index aa01ef7497d..72498fd8e16 100644 --- a/test/testUtils/helpers/field-helper.ts +++ b/test/testUtils/helpers/field-helper.ts @@ -3,7 +3,7 @@ import type { globalScene } from "#app/global-scene"; // -- end tsdoc imports -- -import type { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#enums/battler-index"; import type { Ability } from "#app/data/abilities/ability-class"; import { allAbilities } from "#app/data/data-lists"; import type Pokemon from "#app/field/pokemon"; diff --git a/test/testUtils/helpers/moveHelper.ts b/test/testUtils/helpers/moveHelper.ts index c7dea05b095..1b799e12da7 100644 --- a/test/testUtils/helpers/moveHelper.ts +++ b/test/testUtils/helpers/moveHelper.ts @@ -1,12 +1,12 @@ -import type { BattlerIndex } from "#app/battle"; -import { getMoveTargets } from "#app/data/moves/move"; +import type { BattlerIndex } from "#enums/battler-index"; +import { getMoveTargets } from "#app/data/moves/move-utils"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import Overrides from "#app/overrides"; import type { CommandPhase } from "#app/phases/command-phase"; import type { EnemyCommandPhase } from "#app/phases/enemy-command-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; -import { Command } from "#app/ui/command-ui-handler"; +import { Command } from "#enums/command"; import { MoveId } from "#enums/move-id"; import { UiMode } from "#enums/ui-mode"; import { getMovePosition } from "#test/testUtils/gameManagerUtils"; diff --git a/test/ui/starter-select.test.ts b/test/ui/starter-select.test.ts index 2884323b4ea..be508a2b69c 100644 --- a/test/ui/starter-select.test.ts +++ b/test/ui/starter-select.test.ts @@ -1,7 +1,7 @@ import { Gender } from "#app/data/gender"; import { Nature } from "#enums/nature"; import { allSpecies } from "#app/data/pokemon-species"; -import { GameModes } from "#app/game-mode"; +import { GameModes } from "#enums/game-modes"; import { EncounterPhase } from "#app/phases/encounter-phase"; import { SelectStarterPhase } from "#app/phases/select-starter-phase"; import type { TitlePhase } from "#app/phases/title-phase"; From 60105f0402a4e8e58add1252246c31bb81ca89e8 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Mon, 9 Jun 2025 17:48:41 -0700 Subject: [PATCH 28/51] [Dev] Update test creation script (#5939) --- create-test-boilerplate.js | 172 ------------------------ package.json | 2 +- scripts/create-test/create-test.js | 147 ++++++++++++++++++++ scripts/create-test/test-boilerplate.ts | 44 ++++++ 4 files changed, 192 insertions(+), 173 deletions(-) delete mode 100644 create-test-boilerplate.js create mode 100644 scripts/create-test/create-test.js create mode 100644 scripts/create-test/test-boilerplate.ts diff --git a/create-test-boilerplate.js b/create-test-boilerplate.js deleted file mode 100644 index d47b7c4afeb..00000000000 --- a/create-test-boilerplate.js +++ /dev/null @@ -1,172 +0,0 @@ -/** - * This script creates a test boilerplate file in the appropriate - * directory based on the type selected. - * @example npm run create-test - */ - -import fs from "fs"; -import inquirer from "inquirer"; -import path from "path"; -import { fileURLToPath } from "url"; - -// Get the directory name of the current module file -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const typeChoices = ["Move", "Ability", "Item", "Mystery Encounter"]; - -/** - * Prompts the user to select a type via list. - * @returns {Promise<{selectedOption: string}>} the selected type - */ -async function promptTestType() { - const typeAnswer = await inquirer.prompt([ - { - type: "list", - name: "selectedOption", - message: "What type of test would you like to create:", - choices: [...typeChoices, "EXIT"], - }, - ]); - - if (typeAnswer.selectedOption === "EXIT") { - console.log("Exiting..."); - return process.exit(); - } - if (!typeChoices.includes(typeAnswer.selectedOption)) { - console.error(`Please provide a valid type (${typeChoices.join(", ")})!`); - return await promptTestType(); - } - - return typeAnswer; -} - -/** - * Prompts the user to provide a file name. - * @param {string} selectedType - * @returns {Promise<{userInput: string}>} the selected file name - */ -async function promptFileName(selectedType) { - const fileNameAnswer = await inquirer.prompt([ - { - type: "input", - name: "userInput", - message: `Please provide the name of the ${selectedType}:`, - }, - ]); - - if (!fileNameAnswer.userInput || fileNameAnswer.userInput.trim().length === 0) { - console.error("Please provide a valid file name!"); - return await promptFileName(selectedType); - } - - return fileNameAnswer; -} - -/** - * Runs the interactive create-test "CLI" - * @returns {Promise} - */ -async function runInteractive() { - const typeAnswer = await promptTestType(); - const fileNameAnswer = await promptFileName(typeAnswer.selectedOption); - - const type = typeAnswer.selectedOption.toLowerCase(); - // Convert fileName from kebab-case or camelCase to snake_case - const fileName = fileNameAnswer.userInput - .replace(/-+/g, "_") // Convert kebab-case (dashes) to underscores - .replace(/([a-z])([A-Z])/g, "$1_$2") // Convert camelCase to snake_case - .replace(/\s+/g, "_") // Replace spaces with underscores - .toLowerCase(); // Ensure all lowercase - // Format the description for the test case - - const formattedName = fileName.replace(/_/g, " ").replace(/\b\w/g, char => char.toUpperCase()); - // Determine the directory based on the type - let dir; - let description; - switch (type) { - case "move": - dir = path.join(__dirname, "test", "moves"); - description = `Moves - ${formattedName}`; - break; - case "ability": - dir = path.join(__dirname, "test", "abilities"); - description = `Abilities - ${formattedName}`; - break; - case "item": - dir = path.join(__dirname, "test", "items"); - description = `Items - ${formattedName}`; - break; - case "mystery encounter": - dir = path.join(__dirname, "test", "mystery-encounter", "encounters"); - description = `Mystery Encounter - ${formattedName}`; - break; - default: - console.error(`Invalid type. Please use one of the following: ${typeChoices.join(", ")}.`); - process.exit(1); - } - - // Define the content template - const content = `import { Abilities } from "#enums/abilities"; -import { Moves } from "#enums/moves"; -import { Species } from "#enums/species"; -import GameManager from "#test/testUtils/gameManager"; -import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; - -describe("${description}", () => { - let phaserGame: Phaser.Game; - let game: GameManager; - - beforeAll(() => { - phaserGame = new Phaser.Game({ - type: Phaser.HEADLESS, - }); - }); - - afterEach(() => { - game.phaseInterceptor.restoreOg(); - }); - - beforeEach(() => { - game = new GameManager(phaserGame); - game.override - .moveset([ Moves.SPLASH ]) - .ability(Abilities.BALL_FETCH) - .battleType("single") - .disableCrits() - .enemySpecies(Species.MAGIKARP) - .enemyAbility(Abilities.BALL_FETCH) - .enemyMoveset(Moves.SPLASH); - }); - - it("should do X", async () => { - await game.classicMode.startBattle([ Species.FEEBAS ]); - - game.move.select(Moves.SPLASH); - await game.phaseInterceptor.to("BerryPhase"); - - expect(true).toBe(true); - }); -}); -`; - - // Ensure the directory exists - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - - // Create the file with the given name - const filePath = path.join(dir, `${fileName}.test.ts`); - - if (fs.existsSync(filePath)) { - console.error(`File "${fileName}.test.ts" already exists.`); - process.exit(1); - } - - // Write the template content to the file - fs.writeFileSync(filePath, content, "utf8"); - - console.log(`File created at: ${filePath}`); -} - -runInteractive(); diff --git a/package.json b/package.json index ce41dfc2a05..36c3c2b919f 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "test:cov": "vitest run --coverage --no-isolate", "test:watch": "vitest watch --coverage --no-isolate", "test:silent": "vitest run --silent --no-isolate", + "test:create": "node scripts/create-test/create-test.js", "typecheck": "tsc --noEmit", "eslint": "eslint --fix .", "eslint-ci": "eslint .", @@ -21,7 +22,6 @@ "docs": "typedoc", "depcruise": "depcruise src", "depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg", - "create-test": "node ./create-test-boilerplate.js", "postinstall": "npx lefthook install && npx lefthook run post-merge", "update-version:patch": "npm version patch --force --no-git-tag-version", "update-version:minor": "npm version minor --force --no-git-tag-version", diff --git a/scripts/create-test/create-test.js b/scripts/create-test/create-test.js new file mode 100644 index 00000000000..e71994472f3 --- /dev/null +++ b/scripts/create-test/create-test.js @@ -0,0 +1,147 @@ +/** + * This script creates a test boilerplate file in the appropriate + * directory based on the type selected. + * @example npm run test:create + */ + +import chalk from "chalk"; +import inquirer from "inquirer"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +//#region Constants + +const version = "2.0.1"; +// Get the directory name of the current module file +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const projectRoot = path.join(__dirname, "..", ".."); +const boilerplateFilePath = path.join(__dirname, "test-boilerplate.ts"); +const choices = [ + { label: "Move", dir: "moves" }, + { label: "Ability", dir: "abilities" }, + { label: "Item", dir: "items" }, + { label: "Mystery Encounter", dir: "mystery-encounter/encounters" }, + { label: "Utils", dir: "utils" }, + { label: "UI", dir: "ui" }, +]; + +//#endregion +//#region Functions + +/** + * Get the path to a given folder in the test directory + * @param {...string} folders the subfolders to append to the base path + * @returns {string} the path to the requested folder + */ +function getTestFolderPath(...folders) { + return path.join(projectRoot, "test", ...folders); +} + +/** + * Prompts the user to select a type via list. + * @returns {Promise<{selectedOption: {label: string, dir: string}}>} the selected type + */ +async function promptTestType() { + const typeAnswer = await inquirer.prompt([ + { + type: "list", + name: "selectedOption", + message: "What type of test would you like to create:", + choices: [...choices.map(choice => ({ name: choice.label, value: choice })), "EXIT"], + }, + ]); + + if (typeAnswer.selectedOption === "EXIT") { + console.log("Exiting..."); + return process.exit(); + } + if (!choices.some(choice => choice.dir === typeAnswer.selectedOption.dir)) { + console.error(`Please provide a valid type: (${choices.map(choice => choice.label).join(", ")})!`); + return await promptTestType(); + } + + return typeAnswer; +} + +/** + * Prompts the user to provide a file name. + * @param {string} selectedType + * @returns {Promise<{userInput: string}>} the selected file name + */ +async function promptFileName(selectedType) { + /** @type {{userInput: string}} */ + const fileNameAnswer = await inquirer.prompt([ + { + type: "input", + name: "userInput", + message: `Please provide the name of the ${selectedType}:`, + }, + ]); + + if (!fileNameAnswer.userInput || fileNameAnswer.userInput.trim().length === 0) { + console.error("Please provide a valid file name!"); + return await promptFileName(selectedType); + } + + return fileNameAnswer; +} + +/** + * Runs the interactive test:create "CLI" + * @returns {Promise} + */ +async function runInteractive() { + console.group(chalk.grey(`Create Test - v${version}\n`)); + + try { + const typeAnswer = await promptTestType(); + const fileNameAnswer = await promptFileName(typeAnswer.selectedOption.label); + + const type = typeAnswer.selectedOption; + // Convert fileName from snake_case or camelCase to kebab-case + const fileName = fileNameAnswer.userInput + .replace(/_+/g, "-") // Convert snake_case (underscore) to kebab-case (dashes) + .replace(/([a-z])([A-Z])/g, "$1-$2") // Convert camelCase to kebab-case + .replace(/\s+/g, "-") // Replace spaces with dashes + .toLowerCase(); // Ensure all lowercase + // Format the description for the test case + + const formattedName = fileName.replace(/-/g, " ").replace(/\b\w/g, char => char.toUpperCase()); + // Determine the directory based on the type + const dir = getTestFolderPath(type.dir); + const description = `${type.label} - ${formattedName}`; + + // Define the content template + const content = fs.readFileSync(boilerplateFilePath, "utf8").replace("{{description}}", description); + + // Ensure the directory exists + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + // Create the file with the given name + const filePath = path.join(dir, `${fileName}.test.ts`); + + if (fs.existsSync(filePath)) { + console.error(chalk.red.bold(`\n✗ File "${fileName}.test.ts" already exists!\n`)); + process.exit(1); + } + + // Write the template content to the file + fs.writeFileSync(filePath, content, "utf8"); + + console.log(chalk.green.bold(`\n✔ File created at: test/${type.dir}/${fileName}.test.ts\n`)); + console.groupEnd(); + } catch (err) { + console.error(chalk.red("✗ Error: ", err.message)); + } +} + +//#endregion +//#region Run + +runInteractive(); + +//#endregion diff --git a/scripts/create-test/test-boilerplate.ts b/scripts/create-test/test-boilerplate.ts new file mode 100644 index 00000000000..337b5269c3c --- /dev/null +++ b/scripts/create-test/test-boilerplate.ts @@ -0,0 +1,44 @@ +import { AbilityId } from "#enums/ability-id"; +import { MoveId } from "#enums/move-id"; +import { SpeciesId } from "#enums/species-id"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("{{description}}", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .ability(AbilityId.BALL_FETCH) + .battleStyle("single") + .disableCrits() + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyMoveset(MoveId.SPLASH) + .startingLevel(100) + .enemyLevel(100); + }); + + it("should do X", async () => { + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + game.move.use(MoveId.SPLASH); + + await game.toEndOfTurn(); + + expect(true).toBe(true); + }); +}); From 581348ec0a079a1187e9cb33300ac7565d57f2e6 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Mon, 9 Jun 2025 21:28:39 -0400 Subject: [PATCH 29/51] [Misc] Remove extra newline in test-boilerplate.ts (#5965) --- scripts/create-test/test-boilerplate.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/create-test/test-boilerplate.ts b/scripts/create-test/test-boilerplate.ts index 337b5269c3c..40912be6c5f 100644 --- a/scripts/create-test/test-boilerplate.ts +++ b/scripts/create-test/test-boilerplate.ts @@ -32,11 +32,10 @@ describe("{{description}}", () => { .enemyLevel(100); }); - it("should do X", async () => { + it("should do XYZ", async () => { await game.classicMode.startBattle([SpeciesId.FEEBAS]); game.move.use(MoveId.SPLASH); - await game.toEndOfTurn(); expect(true).toBe(true); From aea2c178c9fcf2aa7320f64d055333abeef00a5e Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Tue, 10 Jun 2025 19:54:44 -0400 Subject: [PATCH 30/51] [i18n] Added locales text for force switch moves (#5930) --- src/phases/switch-summon-phase.ts | 42 +++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index f6395397920..71136be0c93 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -174,19 +174,7 @@ export class SwitchSummonPhase extends SummonPhase { party[this.slotIndex] = this.lastPokemon; party[this.fieldIndex] = switchedInPokemon; const showTextAndSummon = () => { - globalScene.ui.showText( - this.player - ? i18next.t("battle:playerGo", { - pokemonName: getPokemonNameWithAffix(switchedInPokemon), - }) - : i18next.t("battle:trainerGo", { - trainerName: globalScene.currentBattle.trainer?.getName( - !(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER, - ), - pokemonName: this.getPokemon().getNameToRender(), - }), - ); - + globalScene.ui.showText(this.getSendOutText(switchedInPokemon)); /** * If this switch is passing a Substitute, make the switched Pokemon matches the returned Pokemon's state as it left. * Otherwise, clear any persisting tags on the returned Pokemon. @@ -265,4 +253,32 @@ export class SwitchSummonPhase extends SummonPhase { queuePostSummon(): void { globalScene.phaseManager.unshiftNew("PostSummonPhase", this.getPokemon().getBattlerIndex()); } + + /** + * Get the text to be displayed when a pokemon is forced to switch and leave the field. + * @param switchedInPokemon - The Pokemon having newly been sent in. + * @returns The text to display. + */ + private getSendOutText(switchedInPokemon: Pokemon): string { + if (this.switchType === SwitchType.FORCE_SWITCH) { + // "XYZ was dragged out!" + return i18next.t("battle:pokemonDraggedOut", { + pokemonName: getPokemonNameWithAffix(switchedInPokemon), + }); + } + if (this.player) { + // "Go! XYZ!" + return i18next.t("battle:playerGo", { + pokemonName: getPokemonNameWithAffix(switchedInPokemon), + }); + } + + // "Trainer sent out XYZ!" + return i18next.t("battle:trainerGo", { + trainerName: globalScene.currentBattle.trainer?.getName( + !(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER, + ), + pokemonName: this.getPokemon().getNameToRender(), + }); + } } From f499ea056866c17bb2fbfce7292469a2a939cdc8 Mon Sep 17 00:00:00 2001 From: Lugiad <2070109+Adri1@users.noreply.github.com> Date: Wed, 11 Jun 2025 06:44:30 +0200 Subject: [PATCH 31/51] [UI/UX] [Localization] Type icons corrections (#5952) * Updated both Spanish type icons * Update Brazilian Portuguese type icons * Update Catalan type icons * Update Traditional Chinese type icons * Update Turkish type icons * Update Russian type icons * Update Catalan type icons * Delete error * Update Russian type icon * Update Russian type icons --- public/images/types_ca.png | Bin 6383 -> 6381 bytes public/images/types_es-ES.png | Bin 2388 -> 5902 bytes public/images/types_es-MX.png | Bin 6131 -> 6290 bytes public/images/types_pt-BR.png | Bin 2374 -> 5919 bytes public/images/types_ru.png | Bin 7570 -> 7637 bytes public/images/types_tr.png | Bin 4467 -> 6175 bytes public/images/types_zh-CN.png | Bin 4412 -> 5646 bytes 7 files changed, 0 insertions(+), 0 deletions(-) diff --git a/public/images/types_ca.png b/public/images/types_ca.png index e85c84ed9c987049fd9965771a4a64a12dcc6b0b..a1c295d7b9b1c737a045d399597b661ad82ca5a4 100644 GIT binary patch delta 4498 zcma)9cQD-F*Z#1qh=|^zMvb!37K;_DM+=EWCwd4WN?3y3Xpsb~NAD#?U4)1bb@dj} z`$mhtHbj5-`}gmk_s*TU&zyVDbMBmZX3jag(%aHiE#&m_5GjR+(uzu?Ee8{ql1UmO z=l);g8M&c=y|bgEqJo1xMApH<86xEjb%NN-%Sc09oTL>UWb7TBUF2&#K;J-u|Br`K zNoX+h|Az6RDnV&Yg{uMpeUq-H+Cya4fK7-o%LL2icAvXero5D)Nc{N8-2_0}MJX46sx2dS&W(Ft;f;u{37vb*cUHeD3F$&TP#R}JAC0by+C{l7(W6J&>${x2_h-T z%PvU3AIr@p&0@po@%A@CBoyN0)Nv->hmm{SBb~7ts}-EXwN%G*Z)_l?ssP4A`*L1e z%e#q#i(_q2w(IM`Nk5(_ZK)&3HtAVUXN=PKp3i{ox$+RkJEpGf`?)G6gy6oTogx*k zdtel38Ei?e5c>L_yI$HIX$I7;@jlVy-g7jr`2UCy$kwj|sZ_F{4%iH`_I@LUN6n~A z(UQ3$4lq{>GEiU~Sd%NfdyhGW5ZTQ%d8ep9(C(DJ7JFunSaK=aXpAe-IY;S{-#Slt zd?2s#XX5fgz4GcMaCX8nVe^-=g?|^hHcNS`*Whi&3GkhEZKNr?e^L}~8f?kMatXjK zqsE!0(v{uC02d)a@SBkxyqCi{9gTqyxlfGz-4?=Dl?=sfF>CH(zb_9*S&)`0eOi$~q*UsC%Do#&7@DG|wJsJzn8tR(9s+L9B~ z)VQyy)?k~q$E>lGfY7|MF?vnPg~HrK9|?yQ^Iln!=T>tuO-WxpG*hrb!aY7j1_O-(i3g8~|-=cJZ@Nsy2Iqsl*K>jZt;O)l`gYY0qvXRu{! zoIFREEUU6=G|Kzs7EEE({(T8{nA7ZgyrQ2eLHg|h(uW_`3~3SR^P+jX_vOX0w^1tZ z%wj$2pmD3o&{s@@4gk!g8Ex;9d2A9uXlHQ58rb4%ydL6UmaDs0tDFMcc$10`)c7!8 zogHm*;+Byz11(Jy7YRFw?0%NdZuGFZwx>b9tUK6Pm-R+asPn97$U2+Z=RG=L?_zV< z<$MTJC+2&ySoX#lYHBKr{nF%i9!?~-m;r;<(!yVnTZQ@9c_IdqO~ zazED16Kwc0PgCOg&{RE08@lZ;@sz9^eVZosLoxzfMA1tQ2pn@cL+mrIg39n5&SaFNCeM@$2c5?j57fV%mYP}*yEnX;=Uqz7u6gSD%T|^$ z<0!jkn2wA0{t~Y~L!o4R=pGEV%Qzvqd+8IgJ- z#yoHHF46k5{R+Qgs*bIjt?gjY(RV({C*L!pjqgRVrMS1Jo(%Ks=P{sC^lRF_xepMv znSqa`9@B0Sg`k)ev?rrO^xhRiM5)zSS}oTI%arWIE5xbxjKum+A;~8yN6g8J_qMQy z5@a93aBimfSkj%NLeux)@I;9%ui)-UTFklbfG6xF19Z1qRSi{EY!itpQ;(}Cw+=Mk zKB6_NfPbj84jex8L-qG(nwtM3rWHFAHQ}2zEA^JUOk)$0(7?IZp$giPwZoc{-T2KB z9Cdb1(`++Gv4~{7^BWu!H`@pllk-6cy`#}0`$RLD8Us#GV4B3&4$fL-8lJ`p|I*BvwoH3f()3~SRc++o%+!AQpxYx`J1pl z{Jo8+T02?Rxy(3Du?N*R%cqLm=q+-%@{jKLB)VgGK>aV&M!s*95X5OiW~1MLOyhou zHY&^!#71i%sLm}9t>;OFn0Kydi?_PdNVm0~5xbwaKR=;H7xmF<4i4nU@YGYtI4|~e z8~muIfLG1M=%~~5(z*am>vKqt($^Z|q|#Y%a9;C+MBn9^)*@!8%X+ACnBn~lxnI7m zN1jHklgl_-wwn37#xwW1O_lZXO5^xpP2hW+=iPYJ!9lyQO=?2S-1qUyaBLGc#)&!P z3D&T3T{#JI=$hjQbf{TqV1pWciRa6N#4rHLD z_oOzrog@R^>rw-0vhP?M$hvdLppZVt{NS5}t@HLk=fR_krKW05)}gclL7JDgcigw4 za441#Pni2v=hBnPE!R$HUFP}B{uBa=yH`f zOhOFdQ*-+WL7da%ZjYb^^dOu7g z@p>F{>6e(0(@^b?8D0x5v(UrsQ_LNBp&(k)I{ibS>d9Wrg*9SeE$ZEYk6-!U4emby z?X%7+t{kVtCXcA9CP zK0jv~=Q=i?G01`{;x>UBBVRwSBnZ9l{`^;->HO}zj@$xIy`J%3VsWibur2UErNlDb0;W^O8(_& z{Q!SE!8H*0r@tUC8yyd#fAu7OFS@<-!CW>euRn}L2bf0glf;_kd7H-wj5j5p;wvIw z%+~W1ijQ;zirOvKJSNQSG7aCW(6lMh>IVl*C1;Ns{q&qOlB%FdKwX0y(Vm&6AC!Va zNqSgltWeiwy*L>H<3%%CE_@P5{SY8|So^2FMUe-)+!6QmpNanc4gIS#ff9M{8dLd~ z|J0yane4rx#=O0u!vyQ~#tvn_R3ju@aXzhB>ku+Qc`^0?Us*|=2|gJ}ABCfyeiYSz z5}vyB>X$&34aYue)N(^#hGhfKpFx#S<}5@!lA9_Jf@^+Wy;eqMym51|-|i$MB6e-p zy{%YC&YtoIgv?u}x^S8?<_d|phK>I^T5fCC9m_7FHe#rY)p$Q_i1}enbtNeq0M!92shgxG`Yyo83^&0}p!xJF`c0f^hN z3F}dQFHi+)oy@WA)z&ZWa4&Ntj$2|MuMlB@I%|gb}gT zoXw$X1KFdSm659<=j6oiLjj~Ip@4F#(e!8VZ^A4sSsvEzHejb4NQw37ZGg)Nn?JC9 z71sssOIwd(MmV=xONdqifFD#oEy{uif&-(}``&~-x1kPvYoX{YU&^nj00SdRM|)ri zg-?3%`-=@65YfuT5;iHlv0u$rw{(!)v9u*zboMC-)7#68Re8!;InpzKx6&C*fm)Dy z%>d3_JE7tVspMl!!ebN~rvpfBAEbD}EHP5R(1`upQbOTV|8^_Ss8tmo=Ae{t&i(iiM+QTT#pL*_!NmK5EaPOriPiYck5yn@GW7{+ zXJc%fGJ~n7k8RycFknuRyNx#V*+rdDnT%KDEgsls$BKZgfLfVnSy3awa;eof%>;Z#>_n@Y|H6kpNFFAO1O__c+dw%(2C2Z159zq3e&|d9S8u6Wc(Jnyl{wD z^*GI)>_Yhk?f*g#biD(7nqd+|1_gd3@W-i4H;!nc%7DBxB7DR5Nj|D#WovL;_WNLq z(5bRzyK`cM6B+OzBXJM4!tz+IPi}92&G2ivWHS|D z9@1Wi{3qPL5oZ?G5`OmT$i+XW6UU}Z9idsflMJrbu9Q;G}sA`JuL& zZd;Z!ROe-VGf|FMY?+%DeA=?RT#_6pY}jHcBNeP<%05@C=#2a;gl}_RO>{7b2055S zJ@a8&4)cP|^S(UTAGy?)hB>5!?9?qP__CEkI=E#C;X-NhB1j?qb|@`TzQQQcM6zYg?w4KGWEo`% zS;~^^6p|3xm;e0!&x_~9^X56%b*}R{_qp$Lf6jI8bMAW$wT`N6r8y^uL}N_QvWl|u zifEjaEGlt;2JwG|M>P5(vN!@-P7Y^_w6VQ{LOP%^wn$qP$^mJEL1XL)ID!oURpSPI z3l;r;D9R>=)1PPhU&o2B=#3Ja8O%XNTJLKBaIQsH=}q0pfdz$8Jj3Zh^e_@A}(@TSaYgb^jNk#U}fT;u6oHd<%f5)oGdmk0c}U~-%#SJ zs-iNs40T);6zUWLT>gD5^5guBdrOlS+8{80lC~kT$D0j@ci@7FL&rD=_b+PMHx?`4 z+j>i+muOe`0=?D+yh2aGlgfr-1zD@woKT@ZQS9YT3wtNWgMhWcgXHHD@R^wcXK?5> z1aHcRxeOe8XlEPQ92F%sirpKsj!b2TiVBEFeh6}1Xmj0)ug8AE zo-=%u%<*)+;e34Y#=%3x<_~<-YYB2aZXV0K(*O9ZSRxIdC_-42;9!`Wg53FAn3GLd6Qw45x&_nqA!=Y@^lOqeSy|p=iXEw znc?Rg@>iP3g`oD49$ex$;rsyD3AQ&DBr3mLUx_e?i~!y^+G!u(zTUF+2hepyD{~ZbC2i>*&#SBR-M*Aoi>Cs z4cEPX{3^j-qr@(1647Lx&gV3W8NR7zl{Nv2zcH{jP$N9>#v>Vc6q;N{Pu288!)xcM>d_H5}{54a@5`1}Hw$kcgLtOq>ISF!++z{pC|_rK9yyxQR~O-|26GH* z4f@*g38sYT#^YKfH~G=izg~rwCU9WUSPk>u6-Tq5Jweytbr3^)>?HzX$|S~g^^T->|$Y9jkw+J0POleozO zV3e}danJ_197QJJ2IZIs-+uOeceUTY>vOBbbm&F2Fz%(};kH?6+;nE4#puH(rEFhX zr}!6utZ?EI)LJ7ZnLzmGm~n^n_|s}}B!^O?3rI=z%G2L!#!~NV6$<4+Y<(#K+cFl_ zMq>4toD?#Jgo@f2=dvPhNeWJvvvzys&uR$?Xr@DzG|kB}D#MNhO``s~f$`f5TFoyt z!}(4HF{inK_`jLAd|@SCf>#Z;a(#3JU`~3!x@>jEqJNuW4pC=jk_}7Ko%Uac2Gx)p z4>4Vd9POLL{VDSY>BdptroVTAROt3-=9Ayo#(p0u^#B04gJPEvWvC0-hqR%gl*Vhf zW%4-(GtwdEjl~7{+w%4d_V}VpCT9?yOsZ23-?0N_iyvch9te3`NOW=}UG~71vq+)& zjo3-iS@(GZ^aPGRq?n8|0D-4ofw3n=+7?MXr`$QNhFtcR)YX(LV1Vt1FBgpBA0X^; z!w-Iz$@eWZt0WX0(%MX2w5ziH$wIDpe{mTD zUX<=591y+Lc6%KS8`Cny-Lu!oF7uX8g>6te7MO-BYBt=mi=N#N?fy5yc zpAs&4p$7t6#p{+wQ${+~&mjMrN?6{DeQtT!aYh)IEK-3>10Hm@LwiyV>U_ zIM~vIWj^rj>HXTKOh+ImdVSrLh80J2Ip1D*ia!*ylW)!o&7=O@PNCG3JX0-lkk6<& z^Ky63XHbCXe8)!Mb$p(y)E)j?hhZH_QmiXd;2r*IWrW=74=(Pk-!(*DWj4pKx+Cmr zSj52rNo*`^FTie%l#Cr>_6}7(P;f5Nw{V|{8!klFYsPfNkYgt>+J;}|@w;;~X>!6J zqw=mfEYj{MlqCjP)Zkj|n4CWK=cMy1Z&4#fjOmS4*s)rv6)}bJkR`_AUS(xrxG@wo zm@E3GWKh`8Es*wajQITQcOY~czWeI-E4s!*@+Lcpqbm#Z`^6~2;uS>&Xba&%9BdWw zjhGommJ=1sJqeSNQ>lE?ow*^yo_3l{*g|YNkxwI8Tzle>U*!Yh*>7l{-?brS=fXd) zS;8!z#XxS8MyuSvT%dioZcsXzuNso+FZi1)$3jId-37hf z=?U8nlJUAwLoEjyvgGW;G6yC2OJjFg#B9)dXH^ovfyKVsB3tHqHoX;bSFHOhhutb%J5_tygSBJb^)0wC%eVdZmW z(b$2XQL@za?C4w%!@3o&u#jqk(n}xTa2|>4*$#7o-oTBo8QI@(!SG>+%t{=d`SHZi zY!qw*uVyeHXd)xbK+%WvlGXrEQa%ult8sd`8vctHUr)ese>^Wd(4uN-o)zdG?J@k#6UC8>t1S72|A7d(Sk zxvyvcSXro!zx&v~s8i_OwEXK)`q0)#xNdVlyXNiBRbw#u!BTX@_A!>6uC^qsI2D2f(cqw+Y}!kqhLhN zmeN9t-*C{gm(T?|Ls2fcq=5(GaOb%1)|HtSu)z7(w_SmgrBZeVl>oooAIBT2j(;7Y zE-Lbt87Xe-!I63-&)Ljzac>#%Bvm1-vZ*(bV z*%d(hcg)?RB0KPG|J7#Z0}ew00eKNFgq)|($0<-^bBb`)U;M}NM90^63)m1_I4}lj zTNBTdT-QA$wXAXTCg**J%u-PyhtO3)8V@{$Ez+>C;R5S7NVveQmmIZu^8h36o~4oj z{X#O0xp+Q&HdD`CGMaxgHwu^+hJduzu>FY-`K&!n}c5 zAbhV3yakw%*oUv)=Ojp0VE3))p~0{$+Vb zJ)e^ow~Uj?^vDkAYi;Yd=y_7@FqmS0X}C+1$gfeIoHRHyX1|r}`S|uHUNb}vMLjO< z)Akic=7E{9sNU-?!U5sfvpoA{FL+Wlp7d9og;$V|B@0r=ZxXrryqLlZBx1Z2Nhd90 z+R8LO2tm;cws#wbQ|w=RQ-&(4gSi<@+C>ySR9XI|;@`nge%XYbbpQAr_@M!NL^~ki z)fTz#G;O#MH$8InhUu{TF6`dbKVSYxtxEDm1oir-uRSSPY1m?rlMYp|)PbEN08R?y zu~gX)SA~pj#<9aFB~3Q%IK`eS&p3R?wFQCF22z4!{Blz}iiYd@=l*z=&D6bYhjV=v zb>CiyG_m1;ZW4-pD!v>f>tb{)-jirvLAR~*`?>kjgnBo9HUFEDp13w|ytgZQI<;|F z-%q96#oB87E84@-#HVOGdNn6#rr}K3%vR>5UwKZ~>dHqj0`JfP>Oiq{H!UD7S%JT8 zz?&ix7IE=A*Rqpa<#vp_4$Oqx-c~n=4#>%?YKNzOcfyT=YH7sN2*w%p8OB}{}y~BEnVX*9?0VsYDyvoPf}A7zj4il>0oBe`jFk=YtCcv ze%W387@E)Tui;STvNW%zV~>Okt&4ORzxJ*sv1x!(2lGvi6E3NQ0fRHu{JNwoI!y8Y znMU*%R~tEOzKboHzSRc8H3v&FP0SoGd~ufMo=mE2B`9Z{lA<4edVVuU0}e^6YGBj3 zR4S*wL~G6~jw|8R5MB@yuf5=(%p(Na$FiT{Hos+)`84riKjdy2e@67p&@0I;LU%-7 zu;B_G2(cs2Wz$nB#Z7tsL0r+x^_;`k79x&&t-`kqG46Y%Th9u@_U%sAS)l;E_{;uh zr~CVYs&0qbh2gXxU-)zLd|vaWrtwvUgSP8WbS%R|0d9mtRFJ+Jba<)jV} z;3~r>9ynlmtRlNFJp+mOa%;8qQP^(cbsc2plf?qvhEX|2c^TH0rPkFhy8(d~c}VL~ zpV}(^PV2Kr!{t}L0UHl@xig-cyZQ+}5Z$9A$uX2=XT?hZcyD8ny`^DZ%Atu~<)Bou zeysq60;7|G2>zz8vQ1kfv+IK3>^oAX;~=C-&Q%u$%-VNZ-8h(m_aUuZy&V+~P+{)8 z?jKJijc7wRZeIGyFlKu`{6h&d3cSnh-xACgYkYp*93afdZtw6EHJ(eJ^7M70dEQzz z#qMLWBz2?wV z_hRoMRXrWpMn*;=>Z8hG@J4#$zurRRwjw1{RpbLNUZ0RBoPN2{ zzI^3AML$3_$_DrxJC<6#s4#R$6CU?vzs;0BB|$M{2GbpRdn1u!GAz8$H}KM_HjC{{ z_2~f(qW^tO`(I(L%74V)zo+=@{EftRxOZyieD=Yj%s50)q5JCKReB`Ri$zS#vRY%# z`-t<79mfn#T**Jk^ZD1To$1v*ahbH^flzz4krP&>2cRzZ86Gk~ZMvEU8s%!%5&r=h C4ruQH diff --git a/public/images/types_es-ES.png b/public/images/types_es-ES.png index 8a321e515c491a3589a7ff7fd2b0c9cd495e3602..3596b7f6a0abf9a35bf0c3f288eeae39a28ef0a8 100644 GIT binary patch literal 5902 zcmbVQ2{@E}*T3x{dl5YtlP%kfVJu_IR)mq|$-d5vF_@(>#*(eVgDfdaCZv?5h^!@v z$WC@jp@l37A!PeTPfyqTe&6%H@ArLkT{HK&&;2{+{Lb<}bIrt^L7w8`5a9p-fXmdx z$eMly0RY311FUpOM{+fieqkq>IQRnqck}+6Ax)n9C;+fj;%x0H_7>&{6cMlFfhKxl zl!EXiIvN19^@2zqC|?W(2IFk@E#wG-5iwf~Y zX`;b;x*+W!1l<81L-7Cw;r$5yh#(#C55EYyysw6UK|dfAUmdXien61D#Tk$xk&FSs zmDChb%BnDsx~7t{8dMXessMsPmDM3ob%?TxB2*axRYRy~f_}ch^k`(X7sA@;nNWhLl-Nz(>RsMw~c@rr_e{bTyaQ*A?KLwzd*23afjKAfA$Nvi9Pcf#_)A(7CzeW4o z29q!lYm7fJfQ-TzQ|UC1?Po(m7?Lp_6e8J{Nc8)wQD^?53^FtX$=cxvXkwuM@qd70 zj65h99WV?EgDXPeiYicB7!;ujN5J6nP#6LV{S#_IMB}`I{}HOD2vxUL*4%F|4E9e@ z`oN$)C?5Y2j7A~6h-AD6JzX5$1B-!>2v{)aR}m40L_Z>#9+=Kf}kW!v%IR{u6RZw!tPwxzaIVe!eMYmT9n^i zaNrGRuJi6k$U&R|rgC%Vo=8UNB$PcjM#``kOLOFCmT_;Vz?4o}{3aPE9mqMDsbE<0 zxL$93^+veP+r_7ud}Ee};cq8)cQ;mRw$?J#_27U8DLC~6gIdoM8-LM9+NVR3x1Yps z>?^&K_vSC3_Og6OzP;+X`~iqOe_6aC{tvPE(}L<3gf5G>K7GLh0OPN=wB9&vM=pEI z_#1#5`7BP%bQLKmF;dPxY|Cn(vX8Fa)z9i69AMz+qao598kfa_u#1b(x`+t#@}mas z`QI*ny(B9^qrCM#xH!SX1)SAR&6DJ8+S-D10my5PQp{ia=Tt{<)@HgItoa$|5gB*v0qdBZIT+eosj< z4%xKqsqO(-?t2gAGZce3I>2D2?(?OLk*N>6G^;KP@(I&T^o<|pQ14eUb z3oK&nKPzMay;#$!&-v&8qg;o|0TsT5DaB}RY)(ZV_zP$+&@`r=ZH zoa3+OPCec^{p>mj4(@iYu!Lr76i5v_R)Hx9Uu#`x9BJUkbETD1euY5~bV+ zM7c&Qt({WLPYz1icw&lCt}XNZK%F_76HuY@Y8o@Xb(ZV({}%jI#H%>e(NqvA8i6d0 zi7G6Lq2=S03=T9-zE#$|UVomKYm+eeeYzQKc|bx$xGfgRh zAC2b-&N%WHer1DljXo$%b5-4XAEU@{*;}n}f!|)@i~I?r$b7+Gtix2Gbly>dNFS!v z=mdJkwuPYkT4?s`&UK#KN28+<6NfR4^PzUDJ~K`?Z%cC48`>ETEww$O`E1w)dMlC9 zS?1s2hnMw}5(Mz?iPD7Z5iSQwxMbt`I;&B^HHS)H>aU&|*3-jmnV5Kq1CB@cPqcf7!XNHyr3|}6XSSnew^nGB- zqjPF}cTM;3^78DhPqD+UR|4j3o1Q1>Qh34`GZTnTiS4UF%@p3l)YV&(0m?w`&hsr< zQyViW{7zwXO}@(HekDJ-I6+22;UqX8xpuGOlM!?I zS0O7-x}CS^rBPk?oi{1wmh(r9EI{tQmgkz%BhyySOUf~x*f!(k^4pW}z&7{LvN2D` zxDKqMtF0NqQnf|sFnOKv0$HsNp4-d?PXeUN!a%F$IE#21_Vj#dOwRGAB4<`=J>s1P zob8yR>dt`+!MYDv5A->Hvv@1A9&H}0K&>dN&8_r0Q+%ynOy?+i%d*_SyhK5;`f_P%Pr;FaOW>V7_sz9L=qL{69> z9`@1iCXuIZExsgMk>Os~wcF(vYFyc?Mrf}Gl@tWV;!F-)PBF1kW9oZUtZ2Z@Ks%kT ze8ZJPcL6+_$jQEB*3glqkuZ$3Pf`)Y$D3r!rh&`BZf9fX_ZYN3fmPupFN#(~JsHo= zV#mM^3V7g5IImRM?4ws20riHZF@dE@R#&61wZ2~S35s2nl_+!V9CH4{ghw;vp0RQ0 zZ(tpx`??Of^&$i(k?~mLG~o?`u}HPz0`#>);RBQ8YOTO5{_m)*L+r(1cz9{BQCm9e zD+bSzccibQUi^UR3-ORCmJ&1U zhc3>SH(t|)qYrO}R0+WBN@3K1=s42y;3?}Fk3)0Zc^IB!`_6lC1nT;0jkp^FjW3x-n6yP?C3 z-D{gwJnj0=Xd_&D&%R0YeXH85`0jJv&OYfu@BM;Mr@@;3n(_tyIs@O-57j+{YX+_! znjP^RXU^KXiwLy8Mm$NevnTkAqze8rBaZw&a^<(L<9l_h-TsAF0W5qNpIG8MU}@#7T3T8;#qJn z^+V!aabjjM-Pg}?e`c@qgN+IVIds$x?i5ZvGD)tv^>BY+kX|sGeSK+6=8Cw@)#zJh zkZ4`v#@NVU==hS1Ucc(sv5~fqW-{-|se?|OZm*k`YK2Z>GPXy_-U7bid?TMxTu6yJ zh{;?Dd{=}?=-5ZwbUF@LO04s_x=cNLMeId=xiAIhsmKMEfS{ypN33P-mfz}rgGlHuhz7=`^P`LsU8PPyuqQaGcZ`w2I$jR&v5W%(T`tqSaUK{Rgvti^`^j8-&Ebz9swL zM|~>g#k!iOPxV`TwMSd=<+cF5+bz6uVqL0YsFZe-(VQr-VP*nrRsRQ%%nQ@%tK#+# z&GKYHRaKWTtotbI+AZ~AJsQKzJaVlN@5~HfaS5$z_iC1V{d%j-1-b4p+hZ%ciK+-g zSLyOJ{Z0MUNwk0WA?n_=`HWEZ@ZI~H#sxJm>EHBkT77bkyL`={O(WKoC9+R#**3R6 zPG;|$LnOU%9O`wt!Uc?;alMfB26ijq+i|s2yWE<)uLd(-Ir1+FqEUvyEWo~k)YWOP zlM|b*XlW3{8+U#BW4bYO+N@@ZDSKH_CD~|g)FBQ{jg>ulv_n2>mSt6HZV`8F&dPyk zR&fi<)@n9s`?9D+ib3!*Hx}Eq7_!?@R-VT1p5}dHBJ_6mkwitFDK@K%1KluznMK^d z{dcr}n3TiJ*g(ErQ`4y-+or0C5!I1*c~OJ1-(5nubJm zxyhtCR0TWn-C?tGn^hGI5di?9C-=&FZG=KpJI^>ywNVM-CD5dW#JDyRAzYCTxPvV5 z3$_<~L5G393n$VY<=W;j$UBLp3)|1&pw{i33!sX`cmU+aC=+|0JxF0y^+?g(3W4_9 z7gB-wgzB|!q59&PgTcaMHOKDKe`9FLdhw%n5l=H7#Fjkz7C!54r7eKCb1Ve8E-VyGfnT_L|=*Uo{(H%yQFWu*~V|P zCvN7Uy}K;b-ZHV#;cQqy80WW{xU!>R5h2v=g6TYHz{cT}xI0^2F}h(Q7>H}iwp6^Eo6s&xS+|~ZWaXqL&~Q_ibWEeFA#42OIqk?oREKW5(Fp+B^KS^E&n})^P(1SO(z!PTI!F@C_zH-ro8%*31^S?3i_I;Jb|D z?I(1hyK9RdtnanD~>7wJA%VPu|T#tKlj$--*U zueV1Bv2oYUxgx0BIsKu(X=WslfsqGn=cF`nw1t_~Q}!7&RORs*e&Cj;>UR3>F%J1J zE1oaHro;QyFPXUVlj~Nwo0jaixQ_`maMx{qxzRHEeJc1w>>(an5yn2cZ#FqMEHL2i z>=D;WPn8~?#gqN&0UuV^#LV-(CPP0dh1JcNW-Y#nzO$zsF|tNya&jwac^v@U9&;CG zJ>zeMh_V4){5+PR6t}8&Ehd`+u)-*)Es7}zGuB5S{ZlMCJgBb z<5$|*O^j`zHe#UZ91V9j-v#!u3b1qlwd?TL1t6 literal 2388 zcmcJQi9gia8^_O#u^V(tiAG4%V93}hn#sO18iVYNX_E;z24%)lF}E5^((F$3bQPzBy=tpb}gN0My)+{t+w+?PiETcTb-yV&LO)wF5MDl5_*l)!iA;fO9XnH8tJF7vaQ%R&|2rsjF4HlB*h3HP}&+6T&JMtI+c=-&m&>m@hWIlPo&-@aPMbv{_9%P*2N7fl@s>b$3VVQ~Uhej6A6!ciONPkvZ-xcWo!)g+5~YqSYa*1p5Rmm1(JM z744pYvtv5wxR?jU;^H`DeX_N_3q%ivyLSci(Cni^H?^kRI zj}=l?=nHLk->i@I^n|TYY7=fBC8d2aAYF>wpV~+5Hyyow(jDO!c<;$!E=d`v-ys?B zZua(+=N=P@oC$vcG|>h|=7ukGVke{YJ;ffe^qX8RO@H-Ops!FLuEeZg<@aYDw2_?5 zo0q$CpP9+n-o_%>SP|m$D;|>U%lEI4R)$^va#@=?uIhRw`3lg zSXp*?Wvm&S*cVNro5!~Eqr)6L;HD;wDX};ucB;#|C{3g}&HOhOQ;eqKG*_r$Cohsn z)LV=Qv!33&kRfx;Nri+PyJGE@pg$4_g_jR!!`a zCefSq-e{Q=t%?o_P6j8@*kWH_Se;oOGXye7vmd^HP+{`Ox+gC9_}Z(;UZQvabwv8n z-dCW5La{7CI|1+a0@L?LF(n?Ge`aB}DPNS-Usd!z9&Po$Rm!@p6!nC|)y+CsviQ^K zw&LVpao!CYDXV&3j9`0MA6B3Du3A$wjO?vOaRHZ*)!2|?ZA0seq7&*$W9BPCjQtin zs{~~!OumG>%aiH)NQceb+Ys2TZoY2zL7Tmkwi;m_8lE!@WrD_VK6*fJToS~3?PM5? zr=zMv(WtoHKj*cFRB{`^fddi-cTBCta%{vnhQ|Y1IH^_iVl6jvrPuFiSj`NW3BM~i zJG_mdY*SQ@Y|>J;t%pD7dDM6@kRI*ojS{pYhA7g!Xf^JI+tp+=aq2CDZ(m(&+Y{YV zyKGcNnwk4&jbE7wmAWJ8DOb5g)+%{MaWrU$V#+QyLX87EGjwIAYrRrcbryPA7hFfO z5uPSI^cvURNTjKzHBBRA$3V_fr~I&j6C>ANP}uc6dK8Kqd17X+tB-%4eb5F*`|z7N zA*&`GKH0T{_{e#W8IN<=2{cy0FZ#%S(udcCF#hfy+;br2qxD{3B8swurBDn;ca>#K z$N7Ait`CH`HS$+t=1ckKpQxTFhWr_iSV3S< zbxqj&<1%8O)hz=C5%f9#UG1}NQ0SJx*B1W`Esc&irLIu*d%S$!}7=*;V}G*oA+4K zt;KkkB0mq$>3Rx8QSRZ&w`C8KTu)Zvr_8$?cCbLF6iFNY!Rq4bC<9q8#YvR*Tx-O; z)=TwB`5oAUu?R8MCMP*n^3b1ir?2Z#x>h%Y7(rS2>6MX(ko3Aq^!THmuS0+3dVV)E zwjcs+U>_B9ad!fDB{dzPIvDS$gkR2yfz{Paz`b=FXVYQ6($tX0{=2xHhAGsm=dx0x zFZDHFdyo;{bM{r%ve7;0&GZcG3e23W-JWNa&2A5uN=?pqP_v!A&?2+5`!3V%QBnYI z++CNZ>CX)H(*Q zN&f~3XLf5)zO452H5m*`279{TsIOOQ)mE#P+pDCC&t_0*;QcyfQ-=d4gAdw+#%e~h zk8_S5fDMOuUr+&aFXw63iIM{)QEZEHef;|0wWOUL!>iR6J~rS%s!V>E0`r$>c0WHm z3IPa&06gpE2SB0`?GV@)Ne5)e&{!m3-A|&j$ihq|M&Ifo53)kT^zEwq@E;(Y> zVN$jRXT@2{o_K5czaH_(UPVXy@o3_+?|t7|KCjJ>MozTSrRni&uHORI2#+9ZZEbxV z@AOa#p|RL&Y`y7utbKTDM9)&-2E6i4cGKp2r;Uw`^|+f&vm+yxf@2&nud(mV|6Z^> z0Lm*v&YdgQuUp?1xdGp7T`(S><-B{U@Gd`s8`MORoT<=>5ogrcyjhBAXv8@zEHt-` zkNY>KuZB!tT;D*2h+hEV|3aytP|T==DXGS~6y}F+aqmgbkwX{^i}=+ykNb^{eeC7p+H!pg_ON#$Ua5pL3YT< z2w6vV*3a+%_V?!hym+4bdamcbulvpCx<5Ozaq=-LWs0+d5VArMP{>Rf3D&=@v>L(yPuPz$^Ak+HV zn)m(E`>a8%>9>q?qrmC-A4_hY$HMCphhJGmzNcRi43IvIXxY`v6HbCJrL-;X|x$|Fry3DbsqU#ruIBfNQ-!K0kO+7=W;9p zH~KTP7YD(Be!VU3K)AtxbkJqMvvVjw-nl#Og`)(UpT}zUXY^__fUmPTWE?eWxNN>R^ehy2 zW42?eU{{L=oIh1K1U{^9N7UkFdrkHa+f{U7JT?P;t=_VB=CPxUhE zf{shc*tWOd+@SYt8)-dq_^yWq1TxRB1&dlu4N?GRq&I4p`}!-_jB2+w*xo9Mu|4&Z z3*zR53i@=V^Qw7YVVZ^3Wyn=1Y2XXmPDn8BoB++ib>6~{MVw7mrei{c`~hkAD#h2| zx7l<-{FW+WteXJcWAyU*@Cf(zfk_4cZ?SrXk0p2D{*|%QuEV!YoEx|@^FWod3XY7D zZCv&1?q=&XAZ~`890itok-A*gCM!ruyu#)y;MS)a9{!E2+q6Gxqad?`G6r=U^(&QQX1Thzc0G56wz}ZLwbHvkDOfE zj)~Y{g1#z1OC;q&SyEHH2X@;QhDut?sQ})$sYdPZ_9M(fIWBW3J1{N6Uw(VzwO^L( zbTMo1Zgo5J!csk8q?2^HMEh7xk;lwrnVZ^#{KSbKN69RWL{`}azSpq3$y5*w2(tvZ z>R#$`y103#-n?LzOv_z(xn1SC{(X4NL$5G(ItHQ@m~b!hY%0!M4rht5=skHvf5)Kb z{XGLZ^_0x}TPP-ew3l3F6=FMnU@lAQcqO;9_ge#z=J@p#!^N>hyN-Gi3t+}apIuL6 zCPa^ZtSCmVI<@CNCBA%N1eQyvNL=|T8Lf}CrYpJhQl8rHZF!oF(9V1j6IryjSiv&n zv*41#&D;1~jtc*Jc#b%l&4V!l)2VsWsQ|hlE>PTy{pLT!FgU7Z;p5q5M*2M$Q^P~e ziQ(?n@;vyq;_=z{2hroOFD*QJlP^zL&_PIJWyan>=9CmSpRQI>L-3Af1im_&9NVg5wcRKXpt=t9j;0I za-E_$5?zRmU!4>)0`xR$8vN;uwb;!Xp8A&<<9$1NY+!^xHAYCP9fsI(E&2DfypOAK zZMwh9l;U363OugCfo;*g2=^C+hn(vAfdX5>?^=$E-Noh?7N(J?A#)qQ)1N4d!I}XX z*@1ehB`R<^M*i9I>cKiLwUlTxlFVus39MgbPbV^D+31Y7sDMIrt6Cyx$#`p~DHZaJ z{8@^*btYLKAF*LH%$+>Y?zhN<;2cI$XnYpZMW}XWLp=n4Q_+ysUCxt4F=cPRyjJQw zCyf3QK&?@QYm4kw=IQ=g%di|Cy?;*BcH_|i6#vRPy|2(a@1{tIN=iG$fAzW&R~D>` zemxuA;mIW(jJOgh7J}WIg58a-*?Br6E{nQw2#4%(sRBwaxJAr`J%elLqT+EAMG2Xh zwARm1F#Cd-Q!L=h+yLTohJ>w8A2GgC-{3Brgpj)O>$g_3a>8PZ7#xN0t;rGHG$6kv0P zhAT(^<$Nm1(bP3ATE+yJYUEMiOE(shDv-Tj3RTh zr_;j3S`yhxe6aoj%}2Xe_GXO=5zuRJ#R@4=hx?seV@Q{B^POdM2hYcunz0)Di5~E_ zusLn>cZZEIibmpk7}Wt}p67?oYI*JcyYT1`>x8&OsBLR-8Pn9%mbW6ubTjMGaPowA zi%b%8%l7m93~{nNsW^wK&?ik0>Di@g3XLg79^%4+(@~C*Mf_cprWo#d+KT&sqmFiA z;V#KvimeT9@{-%+Ey&%yv5HGa1et?Ye;f-FViS+NZyB63`>7^>Y(ACYy)h~(K9>~S zA{8%+q6QWi?q11!>-q+r-%v75sy*s`d~h7?4r4v-Kd~^l@-``L zmT8mG`Kav;T`${S59S0c-z(C?Vj|W z{=L6e>3wF&DL#CMZp-9Nj`K#yJVxLmJ{3n*L1|Kdhvp#=i~}+ev|b2gRHvu5Cq461S_q z2hBHKt32xi`nPtv-#)i){RKEimh8Zs z#Q&jc$lgKiHQj}&m1;hGs2vpC(dr~d)bdvk99J%Wah4_BE~+YPrj-Z@F`lt)mFF`r zXAMaJGL_h|VAxd`a=eW{YPDXRjGs@TG&ya8%?tHf_=co8S^6!}TR-81JIPG~h>vPd zv4a0%FvJtBgvYX|FA>h!$k}Dnv}ga$+6@Quw-C)IAuQyey1nFfkpB@y1Jv<%KpRpP zyxCpb4_|1>c-pO-*?!Mf?Rm%Yt)YbIbH7xnOwwcEj4@FFeOhmA`mp=Fd;+SCrQByTls4< zgN9vTE{;CsY{s(}`s$6g?e>OhLW!lE-G1rx8VBQdJrOO2DuIuI=7F<}@uJ0@J%K^f z&qEG<2T3WJjgoh8cAo0Lm&>&H#>8_T)+r2(q1{K%UNx696rC~$6z1eC8_W?te0yJs z!H6O6Bg0<2y2`GOBzH?cRP4S7UCStYp8%l1%b`0RCofn-S68+j52Gg52!_9w`KV=I zWr=D5Do|h_E&vb^TTxP_2@bAl91J*q>Z8~&Jk{|z(IHwzfJ%Hpl46q zqtm9KsN`SGQC2w=y3MJae+SRZXtu!f-BYTcQ}&T9AB~T|CubGJD*lH#fu`;iv?w1A z%~=Cb94O)qy2Jc$suQ=M}L#gxm^du7^U;W}I zb+ad8%V+Y|af6LKi$LZuzrj9Mrcq-vi$%UXt4(Hn`XQd3c}aD#yNgwb5JhIU;7U=@ zSWeIo6Gp|7tmrUdlH3e`^2EPhr{J-z4z~LSeN%-ejxlT^3b3{z8Q_p~tJnD;BjJi~ zUZ)#M2w2ILXPuS)4~b)=F{e=-7qkx+8Dz?qEM?p&4<;Vx-%AM4 z1Tkq4vYYCZaJ^@=+83%ds9t~Zcu{guvKeluWxOoTqLM!n*0QP7bE36aV-h7U^Wt5p zYg>6D)kO%knZz6dcD(~m?bm1iKHQWwJpRj`d;np_{P+e1-$CM|RY1<;4#>@YelYcH zygOzD+5LhA6ZJEEp1J^?ij(&a3jq1%UaqT*?y@Id6;0#(u9bOg$!@AWuh;e5ko*Q= zmdt0scV`vxR0cIB97|gYJ_^e|8yKyK2G3mbPLFT_)MzkD>Y=Q+4!T~8h^3u<96G5jOOaG9qS$g15QsD#*m*Wm!uRc?61uM&G+=rzz z2!#ue8iVM`fk}@{*2E0msST6^!{A7q#9N|~bgBbe{V#)c(MVehu4AeSc4i|Tk40;M zU^{al41#8d(x~y-+^2!@}8$zkM&zp zM2Xs7MmJjgiWBybX-)U?@1J~x^u~<6sB-NXRpCE=zm02uh4eqgiQ%96ooTfoWm0WI*6AdO76)nM)b!lt9W{HJ0P|<%PcCAON^`mMuIKgx`CItj|8h zp73zp{`|35gQqc6$-1%#X)vHYIe}>6;QRsoOuuhJrjo|j_*yAW<}Nkq-|@N2Mwrey z#M9AUam{JKj+=8pV(RuQk59eB=f}*4Y-euct)%1>o|%5O^0;#v1%&h~C32t;xF~&{ LJKAMhcF+F3SBk{eipHfE6QJPD7L&!ElO=F$tt-VLMnQ z(o`DK&57(b*)w#(Fl4cX(Fcmx1&*M|GBV*UDvWHA$XZo?zEYr}m2fVW=$ z9q!t2pYiA3==&*2}-c z=-$0i$*u{3cAKb!-MbeW0IXP_uOthjdj~b`B&h=cRm-5c0Fd_%?NSo` z>)+l?v^@vYlx{-oIhaNQfq?N>zCPJ_q+@Jb)VGq0^r(#fxd5PYLke17zKqHZDF!jx zFI=Vf3s;R9zvRNjXsqP zynV_vMrFJX3#u=I=_I5Msgt2&=TP&*V#5gq9b*IVI@Av<6G2!Da+jwI4XtLu%B|W` z1laW1rA?G(9x{Hy2!F6MXV*F!IHjHO9Bn!jup3|%0@h+l-z#SVk z&2_wskUa-4J8MUQZ7p+=lV6L1WseKTk9QHG)oi&Gm)Sc7H-AvG<&x>%Eo zBb8aT!3wfgK4G-uA>+quwp=2nqga%#NMwA<9n%koTeIQ81^_TQrZOJeTIOOK8jzD; zi)|3=c2IMt!HBG08-_(ZKO}!dR9?1fHm|1Z=t^0h?B8E0S3eby+E~ zGS)FK<8^5OUVkRC1%S96M6yVrn@a`yPCuhp!>pB0D9OT!qfhJAkPtQ1P}4NcMF=4n zz9T9Jvv&-mu_zUF-g!~k6GDhahO(lXeXPMlEQYcO{o|kc^sY@Y9-Z`9Io}ugpnqYt zps^@bia4)FGXenBK0oSFX`3LyEa8h7Q6>82dm+sNj25*+qY;ga%hrF`!p5tK@Lq4ZR#JU z)NVqnfq$gNBzTTC3EQGRD--EiLHb22>U3+q7XZ*a=*8B2FPh;Emkox?npS#*KBeEl zz(V9qR>s3N1I>e8DVeOT2(}s6njcD~olMp?Vgf+)sg>1>XcJn2rdwvl(gav%wCyAV z^jVAatf2lNaylsD3D40cy&68evRX+No#%z09Ck>RIE z;1_B>VL^tUBHB!`+9xQ&E<>$r*wk-GO+~HWkA~D#)a1^C)-`O%SRFdX_E~?d%$5LE zmI13ri2*PO(??duldwDqMQmmCYI5g=1%I;hCA>4L_4}c94P*O|{qsk)et(#b)-^1Y zM?|0@HC1XEL^`s|M-i7LxF!Txu)Cg}p-Cptv9hxzAX20o%c^gs!}}9uNBKdNAiG=R zf$c-VXG&{R)I3w#a5?x)>1Dr~*9}urHO+O|PsnS>0xII;vY!yGZO_b&@l0u1p??Nn z-*#c!YK(Y9 zEh?z7y`=2Msb4xSB%lHbb~B1Me1Co0^=eqOrB+E6d@nZW)o^6LnrfJRuco<1_7n2! zo<0@vF|wZ!ZOfL|Z=8F>mX^CsgSq!?LDu4phMev5?V&ebbhL9MK$Uq?R5tX+i^2BY zrZ%+;$;5Ud62f96QnZ?ZXxyqTLKp#>N|rI3UJ5?#A~=l;3Ghq6%48fyYJVpz$61Rv zhNVuFN)cI+FFVBa|A3Qo5n5XA#)-3CVgh#QXWJrKG;IJ-eh?+F>A2oFakfjZhIiy` zQj&$^M>_Rtc!2aNA+S~}&|C*dA31!YO+|bhAbmuuIbM8YRKsmcLz}U*DhuDJ%)qv# zT}GSN6Y0=)_l^@nUu>Vkbbk(%R%ICu-f0~RV@m_@I=p^qRhH?|$}Wrm024baIO(wC zR&5GXF`}vC(Fn#ulO zb*qSv&nB9Q*89(^Zj5SJ@Z>>5Sf0HY9V2Hg`&K%o-d38AK|Ji9obVz{{d009?lni*~$2P`rZknenPa3#}$9*l^3)xor_b?K4WUPFP&@r@qJcC?RbAyV}RsI zm|anek+ue8SJawhPH0~`*U+Q(nLiPH@YxBhkyTsPo`;aYNPnC2%WqUiG^BxfjAZ@U4NduYaEMZBgG&-_Ev2@CvO_jyt-0O$vo26(}z)MtNzmX-p*R=fNrj`W zWd`ivb{9CxXfPRD9-+JAAX_I;o$}IRQvg1gj4vRHSIuNoYn2sS0x|}GTxw!|3hu3FgC_ji2oMsYF?DoH)SHtYIhm~Yus6*ZqX z{Qlo*8u{%jWcvAIF*!==uY)(^L3mOZMP!%!@PBe>Q>%X()9j-SP9Z!Alltq7zb60r zFgcYa_16Iaykjq!WW1dnEQ^sh<3Yo@H{(I$k(Ggy(`>LhPMNW-1tMcl>LMcuJ8so> zf^`PQUeX`wjDoWT0K~B7@&tl`fzXU(((4_22{ZTC$4-C^4Cw3Yk#NlK7bi&H9eXn# zG=FwDyXkb&%GgIcf0hKUClX{2jCi2Qe_pSK z)oI^Tl7+?#|E^cV&q=Qm0yk;Jn(K4YO9s1MQ4t@XlU|}7?A&-`yo-j%s*-l2UHL#Z zHb0bUl1C$phyXPPfY#SWQ29W%@feNFihqRTNhmV3lF^`cdOU11(E6JG11nBx6a}QV zPO$$$r3bQ&lwvt5x>tFR^oY6MF(dZ0$ znaI`($`7Ihnave1?E1|qy&9IU$yAbsjyF%~)$q)RYMv=w^0%7iI`bhR-G>L9wSVJ) zGanM7)ps4b@v5PxI2QnLe?=wo3(AEu+NZMqwpLT$%FdC1r#Kh)S5yK3`rBF!=e&&7 zV*@+c@gyt{A`#pCg7UDSyq=Yv6@&!XblN%NXGQI-(k+6p7~Nk{88%Z2PjM~=`#KCg zkzIdVtDzGW0l+?Oc6zdQ6jIfY-GAlmtXUa*RXEw%5?~c+o8Nq*%_wWfL-|3JAiG)O zfx*5Gy&A4AD_4?*f!!T?H9UA&O*OpxTbkxNc$kn{MZc?c@Gv1-PMez9Q`r7nK2SD` zfwEal`06cXc5U8P1j~bHpllY4+>HQ$^F7sA7a-wCVcgJHT+tUS4kE=@Od>C z0PK2dN!Z|Re_{V+X#`-FZ9=1g10 z$Ou@x0qR7u(F~ktz7B9AElf3Wvm07L$7+(}vj0{^D;zbMN=F$iH-M zwCo200aEqQ5>x+YWiTH9^wVVHZ*paUp;NzLiK%bMzL%lDi{|$7Wq$^teI@Ni|76E$ zta~U2zz^|HCGA-EP!5(P>Uy3Z-wpuyDmDBt5tt7I2K0Z=ZyxML(_&r6bM_>f?py#5 zfTlYa1Z5Y|bmxN5LA0=7fhjW!3lsg><)}y9|Kmzb=AD(~$o8aZhd)HZODQ1ZZ#o1_=N- z(z(MlHs|UZqy+SK-li(dd~z?^r@n>?0OPYCVED>$hQKO#a(}B#L1|zr^W;|P1ZF-- z#PpeD06^EzmtkYD&5zA|l8EW_&}YZ*EGxpHGs&m`aOG+MmjctK*^bYCfHmp)Mw@~z z0T3)kqi^UxqOjr~4_4e`?k9aEfaE1X$4hT943Bi~Ffsw`!0T_nZj7y6UI+w9@t~Pn zhE*7!{Q%lG@_(vK$ME76n{jq@7m~iB8)vR{V>&$xXS;XmU69oYkq)O)(kfJoH zDoV3brAU`*K?FpK5SqM$?f&If8UWpsjjr{GQq1j4PS zt8EJ2p%4hm0S;PGB7grDsfhenayAp`b00U|V*gH~HV6)Y=FsLI*1!jpgL>jtk0S=D3zV5(jUn4W3 zuQO4R1XELmDtqHW11aKvp;m^cpjtF{ z0E(5BhZ9k9Xedrm7A22VM9axQ(MS{yfy5zD7&sDzN6O-_Qs zKB>SQ7z|fD0^#N5CF_NerMcT9P>PC*2qYSTM#Dh_obE$q5WL}3y6A5U+5nyC?&!*J zq*0+;772DV4~7a1WcpKri|Ze=RQj(lfdNB!6I>A}S>%?aA3zfE56;!Yo$|vuiHHCw zfD1rn&_OKf57yOz#-Pz1X#a=lKi&W201U06;U6FWl8cMW9~X4SaZgajuYmkZG~LX{ z6+oB*bee}d5jgG%(iGiN&uSK7WVG!;v^Mlp-F5!pq_Q2?{P4 z5`jVZk6;oJPo}xM5J0((E(Ci3;YziKLH`61uSKKK+(E}6JIoIjhK6`ODxE=~5&=DJ z6&R>k*3prK$0!g81hO3#PQU?*a57pB3s=Npk#H;;kXJw|0%#II`aNEoM)cU4*Wcqw z|IhKp?v7wy5h(w)9dP~sL*Z{L8lWONIC1n}*`Z0W|J9;6s{YJG5{(E_{l`!RI}DCY zCdtD|0Dyzb5#;3H3RpQZ9EC-q3Gx`SJQ7V(Mru=ptn@ecw2O8bvg!RtD1#lq(o>(hYSAO6rE*?}hzx3XRZM%-HNTXXyK zwc~%iHhzxxasWW1|0WTB!ss+I!;9b!XxM}6=s%{C$@ry7PH zHqp=v=Coh=Am!YnqZ__M@UO7jh1#VOI$p7q66q@<*X%uTjo}uRcSIPdlOfy%yjyPexU4KFGnR-@u76nE%inESY@FdQDpZi$zaJJ2Vb_j=Y-AH}vGqUDCGP@D*W%=UB zlgCLoj38vX*87q=%i)Jp)Yh`i@$H=#nQG?GUVgtO5VrKJwlXjW9h3Ai#oyu>h-j`G$E5*t%i~d9!wPLNT zkqax3J<4`W8ynx&hcibMA~z#^VmYc&FB5n*=f+ z=#r@g?Hljztx~P-OWEk{Kf7$`a2R4$W0w`dbQrSS_LIF(_r)?;bE z8mGO(b!GfzkMvkVf-FS+%ai<9kJR+?20J_V9bK-)P!{V~&uZufd9p(`W_Jk*FW{vX zMrWpV`(&S=S(Z4Mb|0^LfF@d67L43RkpenjWq7@l7ORU)HLQ}!l&uF%l7=$G(scIP zon@6y+o$b0z3-iFP=?=|@ZS3_3CBim2|Latl`2qwRU|*eM8YH$h%4?42z#i4vo%bs}@q(!hZ2~F1Hn2YAYzVl406ePtho{Apr8G3QQp3v&TEZ} zt7|)Ym$y&jh3q(Q$z*(YV^9?iP>-`XBV2qdnnis@lYio<|9zb&vmm)u3$bI3!L#k& zt~h1CO??}aYq_h0v+I|}o|_P8^5b`*UdGOvhj|NQKFA;uuE`(65=|4_soH>gs12uo^=96LL9v=&3T^2kf3&yV45_m61yM7R*5w_B5tOVAM4_XLb7h}T5LOM;?2H1!eO>>~6R8@3^|0r=v&ctx|iOa=tJz{x2cghLM z%#)K3F0`(Na@rU!Twmi-S1J&I~=W#zp4g0j2fO@ztIJW+OYln&fg= zWIXKz@dj#-0bA1omQP7qAjb3d^as#fze8DR zHwWOyH7~EVp-IBVj+ju+@asDr_r;ar0(|=VD%1)a}bx z9GrW<&-XmUQ7GrB_@r+*=V_(BaoLEp{g zq|izE92UqxRYvEB8IG=l6AIN!Gr5P>&Y8tiGFTv1X7Pu}Eln1WVs1F!U$0KooSu&zhx21_t+yNRh!smS>q zea(Q0d1(h3=Fh&!WO{AR@S zpcAzn@MQ$O!hLg`%PkSMYO@{vJ;~4KfIbSCVskoeV(PH7Zb$4SSJdmv85w1#C-yJr48ksJ=top@|X!^zfZKIf!(m7VzjZf2(=cG^(K->Pgd9Xv&N09=27R=CMzyoA(k5nx8}s zrVI4XsmdkHha58^i^YZveYd=QN1|vOKUHUSf;(_7H2Jkqz-)E0PQV;{60<)%51-s| zZpVdrL9>e!kLIsk@KP(k_fRduX)&5TmuFa@J)XUM3UKNvOn|3L>_nWA&iEB9|=$MnN zG4#LzjvKm1pVUP6y7>eCi)?*$K{g{!;gKsV<7|`FM34qCv@CWvhxK-fYu0%UrF-`@ zAM8FwRDDj7=pKGZNHZ0_SU&YSrp^pzHOq|?SG_C7b?)`%)f%B;G3%2Y z237@7cj{Kv--8kU`r4?EDN@ELq zy&yqW^{z`IW-6(X)0sDodLlo6itfHICUeU!eUkJ^hO>RKG>hzEk#?s4{)ZS?9eYk% z*h$+7XY>7%tQ1=b>++h;sJX9Ys@1yZrK&hYZE=G$jHnH0cXn2(?n_N@$E=!~7^^*w zG4K1fFgA+7t0dn2D3BI_DVPn-I+E`bL9V~xkZivwsyPs~crPrfBZ{Nr^Wg4#fg`8b zyF?qgdE)cy_NHQcP`Gf%^jGAo`#%!MZ1QLq}j|hEjc?J?kxOK~(4m6Qu z?uLZfq?;Hv=t}Gf%DcR0vHWv2f$9?y^Wj0D*S#8{C1H~?A=wLe9n)(>ue|A7FR}<+ z;ckHsM<{OhKuNTFUEzV3_4_Wy9!za;6T$c1=Ohl@7RX&~ru|jB9(ubhw!dgiJw=7LF`{Y6(M@bVgq7{QGXxFGkbU41L_C9QRD z#|w3&e4g+X_*w6T6zD@Q=jqoGsdE;h6$}O}O~{JZRw=7gbw%^mK4Fn!;-FDBMIbX@ zJ+(^{m2_aLsMqT$Z9fw{;N~K0~NI5zWp( zZt{Pd84B`bF51jjV}=?6=D#GCT#r6f;tlz1_E?0y4RwJ_A>jZSzj5Er!Kd({SQl|~ zCc;9yK{qUzi#4TAmF1oP*A%g0Y_G%ShX|kd8B1|V=`oUx%gF=A%53kL&!fg~)Jh-p z2{qiX<}#R3hpJ2QJ$`>uC;dBrYw%UASrQ|DQE;YX+eAtE=wXQ3Gd2DSTgQ5pufsWm z=KPZjFY-Q11m4|~r^W?wT~in6x#*FS_T8l5f}D^N3wTw^TRSi%(7GG0E$Q4+TbEpV zv=NGC0-yBx1>%8&^K^4b-NUI6h~J7DX78uh$lPV=&1->rYwVEG>f$AIxZ!i?^H* z4=M{ZcrjWLyXz9g?5(BK&Xe0tTYL2Aq}9dmIf_IH?twV0O@tidYFbx>BE)CDcr{FZy z9(j{zjoPPK2Hn0sY0st7KTiC$zk<0=W~zz33uLlg?lrO#DRuK5xAu16iMUA5*mm#S zYSk12eP@bWIKEciC0S#9HPCnmWPRntOEixxfvK2TR}caJ^%m&?d{N5Zk7W8 zkT@K|-SO=}A9E8Z8f$9_)DB90;~t;{3ug-eXvpUOeU^uNr-O0ASvO3vgQ+>&+uJCN zIg^t_ZXK+sA zRLsUYi(XFgbahzCDwrII?4sx z0m(AT0pfDOy0CyFVxG#Ffz$2hbqIQH1i!oO2uuJ4bHhyGI6})48#RFKSSHz#P_ig> zlk?gNsAvpH+cw!=!`TRbQJa|WspE;(apv0C^(ixzn}TAnE^dEq`M(FCmsQ!!t++4J z&N~_agkJnzpq5)<-vI!oW{` zC2_kcs?=N#=ddv$=)&y@7F3oH?W&~f8x*(YK%rq+Vc@KeY4|Waet#CLjMW_8&V5q1{@p1q{~O=Nf!*xs*)4!L0chC3W*+8C#LpjrAuba zk;FBsDj=Vh%AN!vFB6~4Q$dvqkU5uB)JrU5&#OKp658Al5pDRDXyW-&E`TWX!@*ri=ArPZ?MCf%ZU0Clm0Jcf}&XDOWmchCo3zij593B@_Gc zsg-_E`Xb}>&urTOV1NV&V5YO*4d~=RVlz_p?&k!b4&$=Mag^J|`mtCpi>wuLNNG(U zKin8{puov-ovEC2r}{9{uy#*~a~4`Ob=YY+P@te}USPdcAlR<;?4LqP<@ahX<2{bz z=8KAT_@5rp3U-BrlAGcr3d(=QyBzYyYd57RkZRmHysfOqhqnaxZU8}Ns)6vV-Pbi! zWNE_+tvpDqy@HyNhUkPOmo|)}=n{&I5HdJLLKg*T9J)Q6t^n&i;87e%_3HwPgrxgL zL%k5TJR1eWR=i};8)BKn41(NP*+rwRM*35-p&!NCea9&1MW6kw*=MxUd%KSxMNe6{ zL$?nXr%$sNTqF#HUwAc6cz_BF@g;$6qDzn51^-Y$PSiZ}h_uM>2!!I{oiG~_jyyM#z5N~XZNi5Mg2xvtG@4XnO`q5bR}Z8vJ_tX+fPUmio*145#tWFY`G50n#fjyb-Ls5pNHP5N=NcY*90}e@E7m9K@!l? z42P$tXr`lK}dR%4CWP)P9O*hCA zq~VRfV{G1JTLH+HgSL>!*SajPhi9lQ;kO`$CZR(3hJG1y+rFRU-X}xUGIx78%F?*8 z8uW_0C~Nj6t@Lxt_=w=&{y!_k|2+PjK}!Y|Iy;N$^58w9LuULi6bI+d;h_bKKTOCy zMo$H#>QA)}^DOx>oWu3kCgi`M@Zn~S8eHq{p%=Zm0_j|0t4XQWO7cQJkr~pGxZ4EU zUh5r34c+cf{`HIO`V1wk2=E_!J8?%Xo1sTj1NxeyzEqdMs56T#)c8uk0Gyp@jZlFs zf#)WZ+0%wFNwMjw!aKdRSat*}M78gJT~RCd4VD9mzrh;nWx>hzUf@HAFek~_ulXQ# z6Iw4F!W?W18<;x)6dpw|^%YL8!08P~i)DcJ5TwlTvbm#WRS8eV$0qKGYIC-Jw(IP6 z@l|RSpIq9jp;Hp@8#h430S~0X^?0A}JaTQ`kearq-(XlekGfyxT>?K~h-C3hzZTP) zVId{#Ge-|Amik%|i?+B!WTitzJzlGZG-Xkq$Vr6dP=75T4Mg(u!KR@G!2x+f%#F({ z)Sx`5xm~sB~ajiC>+)8zt}9Sbl+ujH;(8W(1Fmux6V_F4nF%3(FW2 zw^!K+<-X=@ce8>-oQ9^RS^ST$DnIQWNaWr2VdWjbu-N0UpGAB+_|fsEv{DS-|yciKW*PX>YIM^h0BLa z>$8HD^?5dF-y=+0_9S7M{Acx-#N-q|F@+wy!23`C?C###**X6_;?Lgha?!-Z1bu+6 zF@8djx6XarG!-;>{!7OzxnMU|$oL{Jyb UjQiS2?q3SnTf3lZE%7P;0bJ6D7ytkO diff --git a/public/images/types_ru.png b/public/images/types_ru.png index 571e162e8dc9f3da298566982b2634402f9ff570..1e0615f721f27d8cccceed1245195bea8eab43ac 100644 GIT binary patch delta 4800 zcma);c|6qL_s8F3Uz&`uRmgMJ2#}H_+dCNUqwTVpQDSTpQDzeA4Y{AA%&2IOa0&Aq&R9rK?MalSClkT8jf&v z#K4`ANEF;j8tDp`mv?eR$f2At&Q9`)G@^p8PEHCKR~c!zysMKdTn2$ez@6lrUEtCv zXIU9%gd7HmP^dAYkp(5O( zO9E@`;$6d>4i%EamXmZF2)8#p{Nhh~7?1v)f>fsT2u7$!GP&FXo628=aEY4;Q-7j~ z{702@xu)lxlb=vD+G%2z@6Bt<+FJVBlaK*ai7nQ^gj!c-ffoI{mK&YgE28v9pi5t(m`DxXaK3cGsvcGWg7=qL)e|;n}4^! zHyqhVy6-GL)Ne^k3m54^HQTTCu8}6*q^tdDn>4Lo=umiuL_7`YM{jpVy-4% zZA;ic$`%b~S%3#Xhf@;URU_r>NvUg!Us+$5kMLBeT_K1$>O1n?JGl#l6^wfn{VH73 zV|7dAVatS{(@8y+-7pIu`^K$5*Zt6( z{mZ{`eXMI04z5jhblE|H{U8YS$tB4ihgH=9o-Nwj;^Maj5@Jq!vl6|FMr=-#{Zb~M z`6|Dz+ZkS89op3~(t)r{s@o`r@c`6@YM_3`-eyu- z?OY|yxjOlVpM7HyCE9x2Ci!nKDkt1U!7R^Crii$_;@*;VP;nBZw1j>XJw|=lj}q9* zy5OHtQ(YtaZ1QqB^Db%pO<^gBmt;3iMm7Z9EO{ybBdsFv1B9q$!;+;94h^2IOd@YM z#cpt93;+&$+zEAkS=L*t@e|{6RD#dd{g6oA8HK3nlDD^*CKGmaL`5UK$h5Wk)Z3bZ zOij-G(aDtT0g#jXMEsBQOW|;TezAX}p=v8Ei71iL%*nz8I>B8}xiT0~)#kn;-uDVUK% z>XK=v>U(4Ff9U|vb*6RxMD8T8vJ845k+;os~ z-`}z?mBEg|OjqUnX~8){to$Nu|JblPn5hqI_6kS15~Dm@F(GK2n{!xPwSyGdJg zOG@;V@k8aRmTKN)@<1%S!zCC0jOn7&FR4YIR23d~4i!_^ppsZ)TuM z4_syv6~P@E=C76JM+0*eIGX@PH7qy~>*-CIPGm@@v50yerN=aTu4KxcSG-@#do%SU za9Jrn5;V>A3e;%D>LBgUQ{|4mr-xT&>LHdNN9K>JkR(bgpGWf|%LhslG)jdpx^B+b z*t+h*830Hc;mgbfBtHkXJAK)^m14b;=yPjZ4|9hl*;d73LB*lSddH6lAK_ss_z;_7 ztKz(zxsW(h@u?M}QVRuaD^Pye?a?P4sWy)X+@L%YT_P-C)hrR) z$WoRkhI`i*sepiDM1I(f>lm?^X;5XI45I~Fs^6gpKdw#DOZV#2(Kyv`Y0!I)91mbw z5V2J;>L(8SQ&Ue9ht+Hn_TDWL*>Z4azO?J7NvxMoO=6`>hFkW>SMl?{_w|2by;nxS z?lVraSc~%AHM(Su3MdrT0==y|=TMTw74o|7p@z+?gs~gix-6YlT(w@s-Aho~9Kx?E zEC_}tFf@Yw+i8JK4c?ltuIo$zwN?E=6I3yK)k|oCj7n;)o_&n+PX7`PvTjj9iB>DJ zgi~&c8*F_v*@kCK%qZsJ>`g7pM90SHzlT06XgUmAyXfA(prVVC|8A? za2kHxFE!Z?Sq`CU_vZ_uE|lCHK7d8=M2xAqQhQ60Z)|#yHXE}M#tG~^E|8Pm-hC&S&2#ONWMx%K&3fV#a3p2&|8)xO% zqHZHp-*%jW%)RU~MYL*DIrxs7wlHQ)MyB#;Y$Wlu5YrmBE>P}tyfM1>}C;w$$IDv_Hr2$(`JII?jG?;^-tPbvN*f3}F3dcT# ztJAcw%ZH1F?O*g&9GIRnd$oq2WC0GC6G;1pA;Xzx?~9F_P|1;EW(P-@dgKVw6a5j%|16 zygqFIHvDem(c97>Vj7)rJaW2DmI220NtD(|pgzKvX*GVRWKAmZ>F0>ozTOTQ)PBOA zM9zg#85_h&M61@AJAzQ}6A zABI}r7JcBcJ~T5MHk*MD`ZuKiM5bimnjxC=bu4A=sD}H+09Q9Z068wjS>OJN%I_%} znsxZ*u7qM`Jso{6pF{rMwcGj?T)<#?t(+WT(P1R;V*nidin|gnhQ{UHj!~fUT=i)N zVA3MK?CSGd;z8*FpyH878Z;1+S(bH9+S_(x9EzWZcF>WN@g07q7Pmy~qVY4=5?-Bp z(P6w`9Bke{vcE;S6nc2Nr)HbV|E*%P(RwHJw6JgeV|?ZSn3Td%+B;8Fz`d+Z4tvQ4H7?h zQUg@zl_m%jf6dAwyTqnprH=HEyV6DT6f!Z1r`lir8MNxJ3x{Gi3{N3)__WC7Y3Pa7 zFF~ahz*Kq+Z`X6>YAp_V28>QMIwdYNuz7j$c1%Pw8+^Cn-c-t_L_fA(vEeRIrtMN{ z(T(qc8L)U=tbm{TuvxEx(0lZJP$+T!ykjLx-h;%)j*85T?@m z3`wqT65U&ea*Gp-_Rli`s)jeUk4xF(O@yH3{M*g%4O2xL;ZzLJ^oT~j9oSZwh1s$J zz#CN0<6&iEsG<1N^NN$ zKlr^Q)0RSIX)|;=*XM&BYO0YLsdo=wEY@9{^;tqi9vPC{2@`}-OW%d)k6(1_oEI*( zvbFEfSciLaZOnu_{CH!r_UgMh5cYX6);WqzHI5MD=ItjX4{#)ncg7Yx^Y3oG(Bi-MExcH?!ZSc(yUl!LxzjyQDQ0)h<8T{pw0dQUZJB`Jasj2(5e3BmOq*o{_vF!~l`HDGx zlflJ;F6*hGgq>;{k?!^?(#A%QL;m^yBWQTe+w*M2@{sh4L9H;h)!LZJ9aGkc{L4gsRwX&~eP*E3%mLGF z7(P}OK$ZSde3iYj(uRKW)>rt&)l77{TVDik%(+N7=l{fJm%h<%mfKi&PiVk%UsaW3 z!gd7dpWP|F##@rOP(~2ZheKoy6RI(EfuMs@}h(I^wcBLeWsD7(y?<%(GlclE!LSyWg>{A zRrkvsnjPxWOn$30$ppK|kXwTffp>t@+YK8qN35I<9IC;gJuc2^ba8Y1*O-QesvECZ zF|U+Y!(e;U5P;A!a}O%D;_+K8_4kB*i%-Y{*dh7E{&neH9AUumCZiuaA8{?Y8QBPRymp$&J{8plXi&MuGyxg zFaQCM=>Bailf5bt3|j54xhi|sH|ZmB$PK6ua80v_M4#=-Cev;5zUH%R+RF+ZJMKU= zF3WW6MDGVTD!X2;F0MwwP2*~HkQ9bEf-65x(Dp!D8`1ir@=SfwA!9bV-W~9=8 zP0%wlA);r9>N(|J_i*s1<*uoKrQMetO>DQiQv<9GuYkoC&EP)E=5AsSejJ5|3DE&Y z{@8mfqA(@n=1M(-ukESfn4UGUcJ*Eu!EG+?F-h%HOWDy*Ppw&xPR?h`Z_S}e5Ej4_ z%)T_~q1OEkAH@!|4gL~5dATz;Mt2eTp?pAtxa~s?jMhqMlIubr?a@!CS*V^32c3QR zO)}or*hDzf&`IXOlqAC*3fER9u~JF->KALCr#=|>(lH{tW-8^pa34>$Y`THXqftTC zuG$>!39r7pZ*cSD&s?qxgtZ{kbx5LV`17%uc#1v#-l{mG`fL2<^KR-G@- z3}i(4z!>QV_&(7$s1A35+FHt1BgaT>e+v)?0(TjKHiw{}RVQnG_M?3wX}KSY;z<)a z%D8K^X~n9WL^;$t^5Ip$0fvaNOnTEo+C4a->lV5ESnu;CqyiV{ZW@uR&28b&MF0-f zvk#5(!GC*v7D)xr##JCabsC0+6cum2$9C<`z74Mm3fvVY?dZ;qlI>4!j+XCjSofY$ XZ=e_v*!$`#;d+ULWIgxE>Ww=Ny#dk$;he5 zB30y(5;C$XGBPj$KNoiu0|ze*7UPA{#&|iX!Q^D*6s2VTZ*oE`wZSzxguSv15+N&v z!`fq{FtQ2?Qb<`lB`H}X5{pCF+uLF76yj;b6H;lfRT$DJLJ~ADNLC!t>QL(;6_FSP zMP&skgt7xp3WHOylfuZ$Vx$x?a`JL^c37-}y%J2o(+lO}rQ+-8;QIg4Xk@TUSUz{4 z_g|>|M?ixFe?~p2|H{zB0jGjZcu0dx5V;TmZov;`0f3D|Ut816FQe-LXk562$)+KbhNXSR423Y1pf851D(k*rf_C z1*k6tsxKX>?eVTY+f!3$mFW#4Q&XQeQeH?;o20n;TOC0cPlN0DWhw{YYWdi0ng`+A zcSarq&Bx_OGQK0_@9=_L>y$tzyp9D}zqbXKX5Jm*%AEz;$G)u`h0>syXV7fFkf%~-rs7R~PQ#2Q z$JOwiba>Ifo9Q{`)mg?IG}+BX>4#UB<=y>r?c4X4H|*_E*;O|kuxAM+-J|1vt`3*8 z$UU1G^^d)-d_c8-cDa{Xbx-Og?N9iXg$ENdWUJ^9oJ9>wLR>X=u4H6!hCLpH--_Cy8TVvd^CivnQIsHwOu`x z#b$>WhFMLy6J4kfm3!w^IWRW^;Njt@gbF5d12Mv{%9Fu1=Gu2Am@cOkoNYyx-4%=i zVnjzd*I|!SI`*q7Pz07fBV2MR$1G|TrIO4B=PX5Lgz|jFFOghlVhcL84zxIJd-lq7 zC{)IooL3D@*u%PJ-pov_J2s^$%-`+z;w3B5jVcPAa`d^;U^8PsL1T+*YQCE{Yt+$| zoEe|3QzCAOLknRLU@SJwB?I*K$Kbm@dbro%(EhvRfCn;-uJsFE4{U}P{V3*X+5IAt z#vvQCQiRTLrN>-?hyD1YvQigUqtIc6eT+8S^v#A7i(zkK^h>~YUTpnhv*C50P_vp1 zR-MZAi=~QnKiM_Ae+aWnhzDpsz5>`9R64du2u%l!K%@P9#iNR9z?!r=j6*j|B3_5- zfx6t<>l-`pho=9&^0Oi6SoA}+DV4v(mwS9|oAarnKhyAuzKy`mG1J%b!ZU8mjYPF% zX#hwP>7fBQ9SrDpITw^a?!7F$qi8|78{|A6SC^>5J5{5vCamc7F7nF(!=8VrDB0R& zg!XD7zJ@KA_h5HRHS0wO10OFwHz><(Af5kHIE*jb`cpOM)fYg`tBqIsnhNU%!3<^L z*4GSEsGf3%B{A`%ZdI4)y<0`ukx`ZjYOVYR*45W>jB!vZmx-Z7B!!skYB+e-l@l*o zf2jHCGqiEkB)3H!&}bjKdpShic#A12v>oH96va6O=CEF_YIxAQ>w_Uh-msqN>K;6h zbLRNEfiA1AVXXQp0OWkB;imLO5Yl3M6xT;E$*t3S5|=;`QH|nvt7Px00lE<8uTQ|D zYQ!b4H&OV3h6dt+jCkyhPJ+1Zc|Ebj2>}xI^bb#l;goc&;HI}FU*bV^;I)3ZwoTMG zN6?-WIivbEJ-&Zj>{-#Lo_f>6K6AxvXpp_Q!b$!I8Rje=iss&qvvrIqQ5pzx9UgxA z%Mst;RQFw?_?^#v`pxQ=mygp+O7lklzW9ls`sOeNwRRiL&J*(=J_T<6XssHIQZrnW z+8G$!NIHC6>5DHL{b-Sw+h8BnCH#C1X9il7_6>D=>RoDRm&|!PIX-in@1yrctqhA9 zvF5vKq)2nUPOVC6|s0I;(YC29EV z1a>vH21P{SFU$WVa|=`K6*l$}3!}nv7ytAn8;w2z5o|tN*_HCR^~a=EQ((+Om>r*j zazQ!mPB8hmM{0=CtgTKNWkK7R%PCrn;EZdp!db86J#)f--YB|Fv%=_Y~+<=b%= zRswdTOM-MAUdHGm8Z)6iXaFho>{=_1+ID1Q?%shVk`LbUB%LS()+Wwj1ofoaUeh7O z@sINsP*Mc)E1lNxq@}W4?u|UIW))(x)MJPo>)U1)El8ex^O|x`UA|mf4L@Mi$VwYD z)|=mX;0Ar#IpIF?*G$M*dHwikv1+D}f7J+P+v4J^c=C(I_tK{`uk7<@wR=Z0mDOOD zuQNHE_AVbC5Lyo6a<<9nzdtgwQARKLd{#a*) z?hj|FAop9s3hiAL)!ARdjP1O(sx!;Q42_+Jwv*qJEk(hH5mZMubp@RVjYA((>o6lL z?{DSx9#5KkDqVm3+p|@*Q0v#-2VooApzay@=)rrAxT+$aiI}PmEAyD`_PjIVU;7lS z<3geR(eBAeA$bZ;L;(T^=#7v49X%?2@A@p9E6VB}VXzY#S|nz9dzMUDOJw+r(H^2L z3(rJ#f<3Rilm_k4f3^R_eYv{Gu%65B3DF2NnU)}Q`|?eUTWWX@@^M4vub-6d+#8WP z#>VP|egY7ka#v#!Qo(WEc2v-dBj=v({+V%`>alshIJUP&<~!Xz$v*W%MH1l8{L>1~ z{=}lAhsFj1UH#lJ!MRnd{)XhG@RES6iW@x=U_(>0?Q(IZnj!C`w6i_=Fn-pYA(0CL zr0rH+HM61_M2waW2FJ%!wQI&nO}bAorSDc@^f$ySW%%(*Wao+jh=7?D_6dsY51pDc z*LYXi@i&8K!@D_}of1RYjqFawZs-*>$B6v05ix)Bs(CDedwJf=`I9Mw<-qkw9WBs@ z?FBb!Y#4#JqkE*}K9(uysY!uB7KbaotB2NI1P9;tA>DQyWiX5UQS1^2B-F3K_S?ga9C?Aker+KFliEX~qLi_L z7GgV)$Ijrk!01-e3u`m%It8S2V%^Q{lUX_M+2S74qNzgD!BBwNK0WZ;(N&S|l>C}~ zDUy)8v-II$gZlZhrA(t|MG2pwWn|H`SGrn8Ir~i3M73|y&7Bk)BYJ=pTpe+Vgrz6D zS7&B3OnGbcr&N{Y?>URKL?qh{#ufjnJ)iUHMSzdJ^V&igw~UE)6?dsMDZ>8_S~yNb>bzcP(l_8a>we_G_0qq$qQ^0+kev8wDb_vWMh@BzlwY(1mV z;>oz&8CxZrAlc0+C}p4@hW@ryUKZq))x2n}V5Jn{Cx@5-mvbbC-797%{P0+>NsR>y z{IM*YkLljOb&kk{#oPjS5A&IB;acatE&j|&KT6R9zmN7T^jgF^9)>N;>^AtDg@Lc7 zJC!Qa8gyN|oLh_X?tiHFGZ#{ULh(EG&tJ9Wez8{FRc4v-)-*Sk=K54rY14oCsM~`w z?_Qk$16;J=${M0|EwZeGibzu(Hy5D7MQ*i4NOui${#f5Pi?IL%vZ<7^WN2M0CRr3@ z{Y%BBXTC76=l+ynute09N037JBPd!uUEn48FWxxQn$5whcv0Swllae2KIBK7QvH#% z<6+#KOwq-Yhb30cy&gO<)@#Wl47%tGmY+apvLbVUB!aIo;Oln03edce#FS!VB7f{! zV<0w;f8yZx51T*)&r5u{i{-w~vl_U2WJQD zqZk0=h^e~rK=zw^en=tUp%Kx!%hQdRXHXhTTGO>6Snt=9Z!#0nJXAJOp^{iomo#S; z5c3&o__;~vhesfJO3C?4fs^v4Va42ZC;aJ|#psZF-E>eXd~^9E`(#Ff?TB#Fr0$$X zNA6JV`VM9|bMKn2CqmHtVjlgcD=+hh>8=;8Jh$PZ86 z`6Zd-E^JJg1g&w>)L}>9|DCiQrr3=i=NY|Q6%@qU&q_mw%*Fo z@E`Keh>9{XGCs0j;xYaG=q#0n2IHy6%-{s1h4)nV*8G&bdXJ`@Gzjy2av7NRdL%E$ zoqV;f^P0gv={|UeM}g!tvKZD@8~Og-nsrnrAZ@S@#oz7(Zv4EKv z?fh_BMj`lAyfc!nw0y2f$LC|?lu0Wo%D}6Ger8ZWA-3%8RT1wS2n>e}sJ`hk7GTPa zx!B3z^UQbTnvIXbCP}>Xhaq#-@nN^q6jHv8QB}NF*7DxmWb?2lrWnmvoT{|>p_H;Cuz}0B9Ro?DLcs) z$u6YOgX~3$Z}e2>dEf8dzW01{&YbIi|L@;_|6SKLXX30Zj0Hh^K>z><9yKwvVXra( zz#+lM!XQPglm8lI#UNTIXQ z0HCECNXHSpNlY0$$-|4P4W6s00?T+2wZX?V%wgtq1CpngNw6=;HrT?B5bRCRB!YEy zWV8aYYzGt)6DJc$AyXOHKyC1MzgV`srG|oKzC)Pa+Tg=m0c9M`tz-;nz9bn9H57yZ zS4YU8HPzrKm?lE~pbP>AM?+y~C>#la!LcwD7O5%o;{#?#^Ch}tZ48h6h{b-=275A@ zbSxAa5D=gifK;RTdO+ctnwn4;0*XLD*a!$Ch|0tTLZ}S6pB@ZJ41%v0o#{oR%4~VW z;c0$MZ7@62KTDv{f4QYHezb|*FlZo-4uz}1wo3XABocn%=zhNB@4<-#D2Ys>kf=-s z8w>x1rF+ttG=?YbpSb?@`fmcTTWfCqE5<*}g+loig26QMXP5D#A^#A~unVG-pf)51 z&Ci!WGV*7qDYsP(9c$oA!ZB&Sb~GCK=b)^9rYvJ%Aft5Li%O&gFqHoSCmG_HByBJP zhR}e(G$2Tr9Rh|`*T5n)RAC4#4E86~oJRC=5Be(<1%aWrpc?4Ehq4Jn#4&OI7EB~y z-D$oQ9J^dE3eJNBrBgk?GQXOLHK37czU;v4?2zBHFgM2@r81Z}DuHy=P#esySk23e zh{Yh_1Por&9Rf$AF%TF+9S+eX64fCnJO++|!;lCP9{^>4@o{b#8&aQ^=~aU79=foW)JLe$x32Ly#whe7aoA`+sGCgE}JBqWSLB>gP> zuZjO89|DPmA%F7p|DO1dmJ&R1R1XsSkc5K&`%?Zh7XI%U{4C{f_R;@WDbTHx@lS3+ z|DJZ=-~HtjWe@SYihVS1iNEdy_QS7Rg+yg{$(Ma+1ZqVc1_0qBM-30#1>R~q6=7xH z%i0{8oL0Hlk1t{EluYqxa&;p|MLBU%a1;vHHO%8PCFe8u4~>$$XHffPCAW8MZ+AmC z8|DmO+I#X?hW=H1eA<9gpCg@z8+pL!Zk&>NeY1n7d8=HU_IlHM=XUM6TOl1`TE1Uy z;NFyttZPoLYmUr)POS^m6$CKM_lPq*TB|D+f}3|#uN|FU*nB-EeJ;;gp|RXmCyVq+ zG+eBUQR4?hHq`LaH(y*$C69~GJJa`z^GXB2t)b772ZeOpw9EVL3|EtgoV7J}lyMO~ z53xjZ?mT6;B{SCj0X#TTKgrfUWml5-=(}elqsukFS@+|?Zt15Fuj{{C63KHMXRBbq z=*HC>C(`L(+$wNDwUbS@QMEoHeoWU)rTI|PXOp9vd%~0i6=VYH)K4A~$C#9wuw<=`=Da z6^-{j_+-JtNDCVx&r!6_!=YE!=Xl{hG=)bPocy-S?X1|u#BHH6(DUZ0CxL}f-PBi( zwNKr&t~q^-@e=;VGQ`}AoSktetlNZG#H>e=If>j210C6~(q~_+ z6RwT*44XeM58&vUOWEJav(u+_z%wFM@$?eC5}LN3BW*U(q(Rl{+PIhg?yO_PZikh@!l>77IUC(>UAZJZ6F^_{XT zcy#~RknXpIrt*C+TD(SeNpRjZ0xM#J-g_v(^o)gI0op(_Vi~e<3?9Gg%u8P z7tZ5Z;@QA6-;;=0+`AyKWD%{g4<7BQA6|ULMB97vA_*0)H6XrF2sM0`T$0*3QThI% zN9!<5bGC_{E#nU_2KsVVXq?Nefweq<&;_X+XWpbk8nAQ z3!@d@w|$d1`k_Agr9E#iUW)aw^#&i@oI`L;dAg#wxtxZLq@KvCqR{_(b9SmD9LW}^DWyv6}_QT*itA1}&_$GnJaOAred#EV4?cbU%!M^&u_9?Dm1NzC`F0Z6~-<28F zEZnfc#^3yO>BDx}vDQE-#EoJB=aE`jn>qI@McM*sV8(D2Ob?_#dqH?1kSE)BTVasho!9acCY9FGEhJ4Cl}1cuU}Bp*R?GN zL!h@xcu0d^1HpYg968~0(MyinA$cp$2NuOVjBRT^iDk;;gUPxRYa1eCNSLY#XV1>m11AYd{t&{Iv0eiHO9{zY8VV4$^w z;Nt_PqXMx5Yc~!p+~;%3$mKi6)n{gXBFZAZK5%TJGJ86Vfo{F+QcYSl_;?OCRdQNqQgWu3!grJ|B6EFdyR53tM_@!D^D(kP-Tv14h< z$Br}Mi-jFGClGmlBm9Z>A4vn*yGp_>z|Ls7;wPplMGJ4W1V#;J>hB5-E?Kobwv5CHnD}qylv9cVQ2OI)0LX^ z7%dLC6m9W-BE^!Em=2Q4)~}JX_Z)IMGm-pIUlKSu$ZtJ-{hhn+nX|?@U~;F-ADP$v z`bv?DEMavpHTFwSLL0>VuzGBNTecFnN_vK}n}G?hNPp#d8}aJhd-o@Z)i50IRGRa2 z2nau&Nq-nDrE}H$s!?Hsf<8MiySb3;mj(OKRgR>OF{g}|XJWEPklQ1Ah4oWuuNXYm}54{xeD^~k3^n9uvxXr%0aLFQ{OK%)%v+jwr84bQu%?j@e=5T z;%mVZz+=qJg2V_3X`1@vg~r+Ou>&1WB+%6AY`n0dQSk!3tLh|J^lg^n8b^?%G}cL1 zDijpO3HV%hOffylqiE53JJ(%~=U`&23bYXl%6@*YOWeMaFZUGt!kP}Ma7kH8EtzKT zV^Z^Ji1k!>*rPH@DS5~Vu7k(|!A5hWmotkD zsJqB>ntd}-8jsX%cIpXgF6s(mzmDHMvlp-a?RewJ$LSc+eoH9>pFj3>`#q&-7mQLG zvKdyZ9Z0;QUew!eO&dwE95CMbGdGww2Ps_w?Dh4(2R?a>>+zZg1uP{d@=Emaw&nqg zVjqE>XC8f-QM@Z@>=b!=l{86u`E2LfUY9*DCo0FTaxl;bo#*`^)}B*O@gHNdG@eoe zBdTFvv)1{8K9#3hj@ho-#GpJb&~3T5PJxXOPR zuqPD<7(JuvX(nZHTB?qnmDY?%NJ$-GskbR8C~!_C*z`J-g2uQT3?ofJ0PP8{)-&o9 zO0K8ubW)O9-SAPu2wYZ09fdt;*Oi&XsguF9>UlP5d@%^59la54Ldw6cw5)UJ%ZGJt zqhOt|k@dx?tU2Avg*b+Zcu^MnLu&yK285 zOeZKlvmH57qgvQw>e%~|DEIE&BO@Q2)Omp=@}yyLd5i*E;Ww+A7`Y1sDmX>yzG3Pr z)njV%y8Mjq7Dg!0LxsR$T^VPjQCM&v1G4%6+>ds+>M@wmCqy>2i!M83E?|D=c7Wa> zH+MhkP{gsp&OXN}*VtPiuk_BoK148WH7#{6%Nx_pZm_|jl_5!skb2lxgG2oNPd1sY zJP^vMNclKPYtdEo@sBSp^h5KRLV%eFT7~g>Mi;W$HRG_pTSD#VP;Bq|=_nM|7CX%r ztj_Pn={nhgxgH}&dFk6!6Xu#g{-S#POowy^;_)PjB9-@aMLvZ47(#Jl6`&GP~tT7C)M4-J)k z1YdAIX>5p#Sk+mr#PUX3y}fyz9^;DHwwkWaC&z{`esK z=D~J<7SWIM&Nc4gX}2(#t@qKdXI)nEAf0jQahnNxHrputI$E1E4B9UT$L<0mGt%rZ zOtQ5#$hKN#vaV%Eb-QyTE_!h?S~)GMw8u0>Lf8&d8B6)L8pu7 zuh3K{B9r5Ys!S*J95IT;%)lesC0+!F4z<8Slyfr9Mqg|*E{yG2E8d8opgQU-$wE@B zS2A5wD#jpP8AjCyfcgOO0;E`jF>TzI7y>zjV1uG92quJ z1F0h6>#)+P%OK#s_E2<}SoZVh0h6Jd=N3m`^sv-RLeGWXs;BK18WrK_+pJ7xw8sC<|JPCa&jqec&}OT+>kHL5aeR>#Q%BSQizHB zI|sbOVGZ_jVX#b*;|VCoCqI85#YnES&r!<0v`Id){nkBM2P^TH4(fRRIQgXbOZNZ? z)gzbNRJ9&%Bfs%FU|7t*3no0@kaP2Hjoy+t16`?#&X5O1kcDnkeX5wu4iZ+J++ZzT z%M4q2vb1>RcvHCE*c07h9e3&CO}>$C_i9YIzx3L-lWny=1vKg8FDBc~-T<$6(KPCI z-+(qrUf*+=wfK!wqTkPnczI;)VBa9`qm}}fae>fDwOXljb-2ZgEfa>J zS;ue|kx_NHf{z@RQW7UzG5ZvzKc*a`>PT7U=_Z-vWsy#PE#l`GgnKWc#ftH7KubXz zjB`-@oAXZBKX)B`5O$8H0*nrpjc@^6&*tNph}+-RP*Hs{Mr{}^3!%_PB zx&1)T2r{hulg`#cmOpj^IVkdbq5Dj)(oiA0<=Gl_D#k!=Ros%cpj+@(r?=d_<-zHNEwC_i0E;+Oo z@Eclc)lxoOKjDR~PcSQ0C-F{f>sxSt7a-fq8s-I*TqNr6bqmgCaie9}PJoxgb zTyLQ^txehQ7^OkrxB1On%(hJ-w^e5?z>VlxP#8w*Q2_M5R^q&?KfYHMpS*igu5_F zoPc=8sf40FH-0>PwV8LS5_iON{>qNgDzx{NHaJVZsG&m=RkxY>sE_BSwDZO0h>fcJ zaqEp!5o>Z(b53!2NMlAPBYsk?Sc;aSz6Y3pdSGcVC82UtOKynQ^ZGfDc->m*h&zh4 zM{GF&BsB5qZ~o_r_Eij1t-K&d)o=2YRBGJfO1+}HLY!B-Jh?(wxv z`~el%c*)9x+MgF*XFG(n^F~zProX~&)MgPXd)^#R*yKHc$T@SLhPx`6G=otRCt`tU4Kwj*PZ`dd|iK}*^=FMf@w@19aKQ6Lh@$$l@@Ai16bm~240OR z?a2C^^+$F$8LSE+;i?d48<5y!qG2 z4nAzXdvPj3#3+Rc2s=~dZYcmds_}3t@GJpaU+P4an~(q~L^W0A+@L%MjyL}rC-?0e z7dIgROx9e`z7WMkldg%z$$k4^0KkJwbF^gP3T1x;6jatKZhE$V39NDGA zKhBV9V(dK}uXhr9DHLQ4Sg-aUk64^cX4@)IS_wnJH~>IH^DsieI22@w1LgG!*WTsX zxAH=y(izoLbq*+IprLsf0F*j5%b=zUC}toOj6*0Ghlb{1&-&6bsA5t|5Dh#}!-A{< zOJ+yMmPN*?1mige_>|I4S>BEFg8qdlnnh!x(f;ERb2lto8mlD>jr&Fo9{?;`{Dcnw zXxvu|W7kir$?L~^wFIyrJAf%)`NADFTD$|ea_K$!X}Ec3na=7 zQ`4W)l7-VJo-=ntLiFs0hG7^!LI^=mnhyUUgb;&tr|R7eNp~tC-Kn%1zQUJN2vB znPVNh1R>I^u9+EeZpth#k^ycv+|WLV8<9v^2Rz*LqP3fl60o4<5Csx-3EZHnC$jS_ z0gZM;yBbF)+zqH_(Gh^iP?cjRA8qrxGFkgV6jNJsS)23Pn#<;HShF}zOBQMl4M%pv zBUkn8hTr%*!$*!>C8OFfwOH`8wG9Q4RnAwx(COaw@VHqyf}*)c`O@L_4IM8$@>F3V<&G z%ee)jQYNJU0Phm0*m;#^T(@c|rf-=Jz#O4yhO0oB0k`zCGOs7nk@ZKRe<2!g>Lvme zC$^fq;mY(4TC(usi7n=Cc;(P@S^~Ix@l%HH%Ax1TZ-2U1hksl-^c*qv6lL6g-zhC_ zI%;b_HhV7ioO{dGE(+XZ>9K8I7TI|QK$S&7P@XyfVDZJ0epovmu1hf=t%xrQ+#?*B zOjkJonmV`60W%ZD5xru@P8HI<;IY|rrD@~!#$!h@t`d0ZsMsk5z=F!iT$a8e^$=WiRhxUairmgoqZO+?z-!pf^;#v1=$->^Di;-)?o@e!38)iIX_(;#Q zB>bOxp0FT2&l2OKBE2^#T-O1ksmIpdI=zv$?H!w~cNY9HHyd6S^=G>q5$eBpr^G+!(i>x%u3k*w-J^-PwHw=8+FnqlS3HfPjhYtVfJxGYLV#Tj+ziY$# z`b7YM%x|qg>YQb$uU{mUSv{F81*<0wfI0wx?%%u=kr(l5LcKBlF)s64O*Wo~bR|rk zvrHJVNKaRa@5p*a;+Rvy+S0;~k4Q9@-b87?p`KSvdW6 zlertFJ@lBCEcCs$$J`AMkq#{ZEH?5D-yzaLj-B42!#@s@4q{aH72STj;l^ZhVwOIU zius`=tIcvEy=XXT02`B=Q2InF0H9^tX;a-3#W2k{rr>^llt=>c$B( zK(R5oX>8=%%;*7o8YxFXM+^X^NVS~uoKgU+A#GEqqO$O#WYg_d0%-uzq?=-6aufbN zWZyAG91BMPwv-8K<_3|iSAW8F;Ejf{NB-xBlB_l>s~o4<5pk{oe3ppeNAy|ZjfMep zH+&?Nq$Lac8wSkX@WY`xEdksyYn|cyaHx(P{I3=r{_){Z9WmOT&eHpYMcdO^r0wY} z>*2OLmXqo6vWye;2UdwSj+PTS^RjJwI?MjNVd-~fp)4>5rQexl<9LWTRq(d9 zr8xhY!^nRIC=1L%{xd*h@7n<22ucNIfjO4F8*CXU3(RqJLOW`CbttL;RN*KAk6TGM zKOhl&RFO@$TZg=!n=K!_NY6(VS(0y4S)|8zD6^FXr+p!cnXJjBf|gf@%-ygwp?Ak71j?(dfT(Qo%<*WT6aYICUgn*uj5_K7+`u~Q-C=$%q?-N-3BW4H zWR@TrL|X!-(~v(40DHRGHmf(GX8{X-@`)y+Dzq;|G5yc$d-}fr`N-4vDa-Xdec!)3 z^7Q=&*WS?*z~}G$isAd<+B@W6yPnStKe+Y|G4}4+uK!y!1;yoPjeL5_%c8cMzME~U z;1gQwx}dS8&8ARLT#mBRB9xUD*^Y_0#+Eh|6qkG2N;BYwl9GH+jyhAZtt>090jL-NYN=J#vu#GK0b3H)z`Vegg_h9AJD_q&NxqHe z+f*J8$3p)?8aX@WLp4%_?09OztWOLoQf>t_~YAhdx>K& zSnmHPN0wO5O@1h8oo8P>Y)kQL19br4=*SHZC9SjUb|<+zWp#xCu#Q!tr+U=xwShW3 zl(Y^Z)OlD*0*J;jMW{0c$WyoBv*3DZ#wycMhC0ok{iLJao#ZT?$c};^pcR0q?)=1( zO*B#I-0WCc`$816d+=x4obMj|nYkMV;#X?PLhpqaBhMTD@)bSL8-Dj64TJph6*Btb zDINY1yYc}TBe_4+e{Bc=(>trJ*Ve)F;CaeXndd}0BAM^swh+}MJ#M}ayo{Qp$E|l8 z$BkfmXSJt}Os~S9;Jh-b@FzHoEBpzTu?#9YB0YNs23|JbX{3=X001Hpdv(tD<)tSq z0jtEP4P0qh&@|U@b~3#Re}Z!}VxybhS&fRkTIumhyA#@4~}=5Dwl{!uMi*mB{&&E4=OY10zG6r;%S-6Ur$6TonV)FX0ndqSWzfG6sZ6fP!mgk9NB+_2 zn<;w!(doDS=5BcQT|IY76TfU2zO(NV(sJymxAr99?7M^*wM|EEzi-i06lDSc^2$TV z$uG0olVJbmhF@$N)QXw;E1b3D^neg^4JEBLH?ryN!sQ^?BtXR1{?@aeRQn zb%3oQHK1HhepysC=Ajh8XNj0w8O^PttE17}4Re>5Y01LT-HqmMc=(u}-Eh&@4a0Z% z7$H@f{#WbpF+z-t9ePGj;aCTbF28|_KpCk^I3(}NGPebajLTFaRAW15XB6&*LrJB z5{BAq&E4?c!Wu1E_+xvG`ES3wo=tQdoclNKKbH2TPhEbO`0{j1{Pa(Ej`5@RM*wED z1ICdv&E$XIY!~*Ef9I&wM^aew1*p@pmKZ+I{3hX0yhC-`_ex;oH|CNIpZhN<$^vl& zA#yHoZ121=*ZP8q|C{#`wjB7{I)jB`Pxc5k+{VC^y2J+^GIBhfW(&*Ecx!3832H<7k(80;Q!pm z(!qTC=Q|KSnhpS%(ba}EesgYZA3TdS{zPmCux9=}5$-*#nSamNLX`aYY%7rD$H)Gm z_3t11G63MpU$0}u558t|I5Nc#V9-o`A%GA57Wg8g@cF;sKT_`o09=b*XukWthV_B9 zmh4N*3*v02`Kd|TFM>!9t_1`qz}W+)PU4aTTYxwni}2A*%&0aUV%7y*n6@4B0BnE5 z`CG`1XPN;3WebB?A6N@tdT=e$@`3<>zRqT(j40&02#^Im=ZabJvZBWzhlZ& z0Km7C&0l#kivIPKa37#)9-S@z4T9KnryU$E1pvff82bk8G|8$yb6PB7F8uz0!?Blh z8Q8gTA%5&zwBc!D{a^jzcjj)G`TYu87Cwr*-_zOj;fvB^A6=ed$#dTSJ^qC2R{L|G zjOlguziRwHWRS#5j^)tn0000EWmrjOO-%qQ00008000000002eQ%+kZw#*-~T+(~zYwGnO=iY(sXk6eWx?m@K22!PqHDlBHBcc7-H_7RD0U zqXmU*jWzqe#{1~szw^G|dB5*H-#h2bd4BhO|E~4Eu4|q%H%*PtuXdKa97VAiKz{^tI zJwY}AsHjmru{c*e1?qrzCU~gAX3MK!P=cc>?7X58!pKt#??TY^BjL^cj4g0}t~it< zOzkvOg^C6hxZ^2UDAnD~gN&xC!hY#RgLrov4uk%pP+V1E8oLIeRz{{!Eg}gIRg^s` zgF`CFL6uOl$dd?^oPsn|4uMpHBb4Asc^L!}jW~&xM?rsoV4yXUqZ8T;qx0JqxKf3= zP$-^gINZm_N7hGPmPm4jBT*<693cmnlam1%GGt#53YIG4K^Fa60|rmVkqDj?0?`Ay zs}bu!^rEQ3Ku`Y)!QJzZS`YH?G=T|&Q?Z_Kq%2}Lq+diw+#jB&7s>6HaYq~+?}m5B zdr-(A7x{2}{988J!q*cI zH^Y;OUL+h|+Z%LKbT=ALv=#}Er4UILM55c@7iIdl%TO&XsN{Kqha=I4EcH)xJO)d_ ztHR_Ea*8qtMH%EN3#1}iULLKecmg4ZMj-y88W9}{PQL$4Jt>1wvOuEH3J5e({$HqI z!8l?m*#Ag&#G##tBzG(rF2Nn^jE8%AIK!ZSl8DwKx)DjBVbDAIUtSm)q4hk-6s!jh zuZK~Eff36R2##o!0}dsJMBro;u}Vl8l%s-!j05fzLIx|3lgBzbD52yX75|pU5OH3+ z_xiWI)GwL-GV^t63F0H`Tp()bNrRx$c+H~RRd@&Znw5oVYuB( z#XG`&ZxjCO4E&|m#{~~6{XeezFB+NXMDf9r@EXoww*Esg!9myIyFK_<6!8BU;IFfP z>es*N;1Jk_f2I$(_%pxo9^lX-fzv6FKHdTV2U_$n8Wz;#kxRj*mJPHWY1Qf26?~+Z zTxgjj`eRO@sdiE5*OE`84Y7=|&g?X#jF4IBl{?-J(#XpYdd!7jF{7k&i;Why58^Rg zbobIYmlP%ms9C^;LxPM1hUi0B+t%?``4>Owg*R0Fz1#a#SAQfLtI4MRLTn`AI zvYr_dmW6}_^7=_?s(;M+Sl8o|eq+0cK;+x{3Zgqq7j4{C>-`kx}L}7Xc;!wrMy}S>AQOnrVLUo3aR6CuF-UkOD zY{khxR@ODjnWLw-4)q8KFf1R#RNv0K{sE5B6Ccu70Jqcu`wR0gwQg!&tmV=K(ypBg zhuO>Jil5v2ENfQDo%NcCP|2M;8?`tFf4e&a*Nd%1P1lAas$o$~@xwk+?Mz&mfkFpv zHap#(E4c=Z1prwSx?bptZ8ff?A3{)O*GUnfLX2V zq{TJ2%}d9AP8M~UG6n?da5QG(Oq! z4;3A;Th~p9EY5hMC-sm5Bm4k~Hi#1#pxqr@$m2IXJRI0Jxvn1#UL=nv_2h-$^|91# z|NOWmo^8ubeS+4i-pncmK&Mkv%Ndg{c1bcd)ww)l&}oip#CX&Tw6DLHaaEyfyX>>q zN$|;wmD4=EKmVEjbqin?DSzD9DMj>)5o~`n4}`#Oam4P#vJNJHv3UN1B9vocF@iT; zV2pR)v;h*XZ~0RtPFk*5z_d~kr<16WlJkA-!pM=z_d#1}+|?bCDG@LXLy++1{53fy z<9jEKWEPnju&F1-C%1;KN_{l1NHmvocx?u+z@|; zITxtP7E3XYLxdLH&;ID8&X&ueA#LSsBixn!Z{RNnGF`PW}+uqNRO$c%uX&Y^&ucpE(F_$bj1OmTBZmwQ2cwO`XFrB zyatnS7?A8GzsodPEAM=$JV%DhK@%fRj=NDBX zh3%r>Ud=8*<(r(eJJJrl6s{cuUtL0)-LtZ_!dc|nnS>bT>PHyH4+O|Sf`(!HL`a93 zcpM3cgVhxyt|22ST(`K+X3NF`)hl1&OnGxpH}*_3RUq#Mw4~7lixEmX=TU(p$C?W0 zg2zLy+j1YhHsFN`JG-CC6*PB3pDU{{fM4-_CSNG%g0aYpcQI$XiBSc`KDBQb@ab+k zx1Sf3_|$e?1DhDNddQ(vIE!NbsOFt=sEewuloVVoZ6wgh2=cgJp4YtIe-`NK;9hI9 ze7`5)9K=NOU?Ewezdt&6we?+x|K)qb;9};4?5Nyu(;7}#F7C}7Pqi-oc?Qe8RSB0a zAm1Dn(ZE)=S_3<B{;_9O9r-0+03a3g2a3j2fG#QUJ{y*msN9kR&AE-tBsTPdlUre(-TF%N7Bh*ub9D#@D+~8p>j= z$+YEvCS=vt2J-kzSn%{DFnBveO84W|&5osm&GcZU9d;lJUh4+_a%Zd`(&~;{ao9>aRm6(;NzV7a8pQMRZY@xCkWo3UG%#_v1aE(!#86a}5|A4E!S-(No zXhX{eMt6Y!p3`k_@mua(73cHc$K3UOuO$Gva)&xK7T4x0j2uXwnVPC8_(>}`efqfE z_~EqYz`4B{Nbjx2+6l8Oh9$D`+Bwa*{ABPRVOb1RxbGVOy=RAoxQ63e&ssE`u+A|$ zx-iu<;5r)~;~2dkFW=+!<0?(C?D~>VErCDG%Dpe%qvA;0=T#7hC&tbu+=(<7q{S@d zZQ!tUeC?Cj7s3~#+P-wH-${%__OQ#R+J@yf99G82k-Tkr#Xc46tW`zjES3&AM_b#C ze%^=)5eyEyCh>qPF;%(hh|@LYKXKEYimNh{1=NdY3Jc3CENN;Nc_oy{1Y_ z=dI?gm~fC|ao2@2l?9h}oD*5hs^~9M7`U~L?^!fmtvD{UVApa>BuWYZRyr!DvVcO- zn)c2jQ677l+1)e&Xc#&c{52`jC1Q_wXKa_=gxdH8IUK5hp#5{ zrrR)id7Y(a;9@N+__(SRIy$v)^a~!I*vV|gIDDzhp$T$;>GrSXV%=B7Z2OqfJ5yGz zlXCfC_=AOO??tGIW($*v&vg3LIu&F_6DDwDIFI_6(kcGuS3z7JJuuNTH6b5*g%Q}1 z1YZ&uD>&wa2bNgG-Y`eO-+gXq^HI4^d#IzO%z#e0_2FiiH+#-PTaSk8(V?fntC}wm zoodmPd+Mb6)=crqb9LrMpO28tm3zx~di&YQ^{uRU`OB(1%^w!SpU-lP`PR<1i-%QK zCUI1f-cA@2F5Etb8%u-HqZ63dii$>?+Mcu~?Y||#D-kisp2K2vgfC#EZtfl;n^3Ct z=J8VwgZj2By4DxDUxL`Rd}n59agj14t#$IOQyov)QPPzy0c*SSk6Ji_TQe}sPO|A0 zSmBJC$SCE)vdvlHTDb`0i0C5R0LvVvf`!27fHCfLZZbwVoGluUW6Stp<;ipOgk;Jq z7@Z>*X?-OxGWHo8zm0!r!Gk<<@x!4ee6&?YsNyBB3oUx*Bw#mn+oF9p@1OES?4a=3 zEt;~CV+}V`ZVmJB`-G?{kNUTru6{ZOM`YY6COcH+v2^pDOaTD+ck$YOcC~DOO|Ch9 zGtGRH31Po1Z=cqh^*ABPawS%^wX;rd83XiJOSF-E^g2f-J*+kud%?Go0c5_OW8aCn`96#{9njF7a<6Y~sqV%fKVvJ%>Ns7Ln z`g1PqaUEL^m;KP;o4ZorDHWE^n#qDVc$$GtLo`h$#J9O|&D(VLHlqsndaeS)e5~L; zdzi&^lGz@%*(qYRU}@FLQlA1uj(DvgJWKTBv(UzOg^LYu9!!9E#p4uth7+8>{XLbP z{p_3~?9NJCZ7*4hI7G53iQVVpUVpIR$l|pWr4@dn&J1OOFRxm{@cWO-Av=lBULP5E z&+if@mIx_5D3AGkD=&0G{|h*I0!aY$wc-X-qkmUV(` z5o~wHyN0$kR(*~JS8_p(gp?=jjFt4Kxxb-vh7+hp+j@GFw@%nJoWE@QQjmsI!QBl; z?9H;DG<@jVv~#z=nD6CekfqL?*s4bEMX}Y2^Cp!-YW!+u#{s^cs0#( zJEFp+yUwD!dQh2xFWs>5%YyR|<+JVBa*;I-0wmf|Zp`2B+*3f;Mw!@aP0j*rXR!FT zLsZIlx930?pGwkqlTJ6C0_HC5OU|(prI`iD|sSkT*SPTswu>mfVv$5!X z!2Kvb;D?fv=w$Co!}4aoTBHHZ{gwLy8=&6P$JDr9&?}hoYOxu`d1bXUIBkSJwBwxQ z|Mcf|k7k(T@S`)~sUojBkXs11uD6|y>l>Ha=G`Y&BwS^e%Z9#Buh42ficU8$`=9<% zvva8`veNaMu0ffQlS4cM3C5YR{JbHq)KhU|`{29mXq+)4puAs2cG*wO{Oe-es&6gG zu046j-2Rc;B6Gc=U*m*fqCF?o%l350R(k)K3YYz>%`@!`iwkkyV~$CKeH>#)L?HYe z6+2}#!O?q`uL{!7o8#0<{0yo#2+dCeQ+;|w;jcv(yLUu9cIG>R7EESj^Px`o<8{0L O*y?E;V+u6wul@te(hKPx__DMuRRCr$HU43v>#TDNjwG(Wa1{rJ8Hhd`vD9ERfXaypQjTj(Mz<_{-0^(>} z#iD}pA);2XYSju2qfiJRB?wWls6=QHtU#bpzbl~B2FKB4rnWFsJGcJs&3T-+*}Zr7 zzW4Ily8pcUcF&&Q@BGd=yOS3lkwOTu>BA2^yf*h3)~2nL0Dqz8+2+Rf@xEU?LdfaQ zJd%~@DPk)EkBpt}+h02ND$!^7aR1qME=t-+0ssOq#?Cdr^PlN8YL3W;k+3EJXGDew zM9rmzz}}4;{Qx)^MHDrc76JsuQ^D(i(i1k9QVM7@lu#qGol6S=Qh<}Nol6S=UJG0m zs*kX_v=E5u=eX;#&+vhsO|`}a2-^o$Wgy|etQ^OCH*S|cfdk&$tPtoS=SvCj_iI*U zLg7fC=N3;8vT9rV*wL329~P5Fv=XH^wTc8Y88{OFsAyQ&eJ z0DyQ3;-(`Ofv;1})D?3-vIR$)kv(`XZ`*BjPt>~lxMI3hOP;1(@qntQA z*RwT_ecTx~qL%#22qe<&+{WMXjSazVxZ=s|+=dfo%$E}2f7-dnhH%<#F1oa@khhkX zSj+#=s?CukW{$f$(kAH~a3O$=yY{{3@89+IyP~Lbr^G9FTGYFvH3=sL;1wz$O1yFc zAmkc7{5caK5_shV5H*pr4&Q}Sh%3Ky4d@Dw z>VOEj$nQjeqDLA?dPJ_icP&Jx6ab8aCiQ;jPW~F571lLScz#FE)>thzUX*csV84GE zfrLx%rptEsJMRUzVQr7a07~m*Ql>h)!A>l*-kwy(tCCZN0od|HJBzx`d2gRHz@oOk;1me~~S_nWHSLstq zLApA?zl`E^0nfi0y=y9*32<=8jw0mpXN&-~Ac1XpW3k`=7`#1QYeDwxKhq;eVZ94L zI&@S!S~IPi{9>Pf8G(fD!I4h8{jWU~+=iZ)o!d}i_~5&Hr86*lUxl?odb@hYm;Xx# z^M~Y%x%2YvK^03k0)@4qKyk=8695Q}xm)Xe1u*E@RSjnXVG?{b6iAAa7h%kT{2xof zm)Ga~d3D+%^a~N~=%|F70%`^7z?z$Yt}9%+LC(7}&vWfv0_Ex8L~Q+GzkeBlMC`LP zg^lxGJl=R4)_j%~09F?yW? z%wedG$#%)^`;39;$XF5JPT>TDqT;{=FzdAkd>iaj?NxSG1XM6CsTw%|bt;%rp(Q{v z6I z;c)_74P6JIUHwY{B+`BnSU6nz+Wo<8`0ygN<(8 zS^-m}1WX`2HLCNJ72pvg8X8;yPK1+Aivr|?OUjFH!=AtSoKzJk@|07cMiEwkTz(J( zDO-E%E}+N=j3VUe(RGMArz+-5fE@55qjYevlu|&BIEbyJy1ikimK<5o)-$yeeaQA|&5Rip;OX=-i;GKLhJKwO#xow1yC(2Uz zq*MrzQ&lVO%Fh?Gi{gD+`u{~BjvBEP;6U_zPhIfonQJuwYG(rcI7k&7 zzEUOt$sxb1^eHQ#OGO+Ku@o>>JIzycHfMtq0h^qYjcsAdIeB@W4O2M0cURDAQTDdy z0sQ&5AxLE4vWs-N@Oa}VrN@tE=l6zr?WB|de}3P|NVb_U7}`5W$cm~HP2`_kmSe;l zHsvm{sp1}i*V2;N2|MheIo*EMK5-7{2w<^#%e_e?IH(RxC8qA@WVvIl0=n=CR2Myg z>DHoB=R|;<>Ey>6sbB)trNM~+9AF^QWgCLR)hWZ}CskmpOM^23asm)M(<`qFu7GJi zvItcfqa0pDr^C*_4M8I6z{tdU^|FP*&xU)pX6H8SJ48we@Tb;o$wYvWKBtc^ z5pwe6%~tYPRFsI`myH)aJC!$ajJ2>aWr{gKBLO(2jRZghh|Ipy_r`f9iAdJ4TAU-Y(Qg}{}v zL`s0~*k2JVzJ)!j#GL&j1--gD8PIczkh5#ctfL2z?Eo>G;Y0uq|Gea_gx`%%y(xxI z?<(k31-x)tmq-rI1mH1D3ERjMC!tFUXcGt^a&*CoMnsSbVDeivk^)Dlkh&-|~V6bV)TfuGkk7JKz zg}~PfZk7_@TYt4S1Mx+>9CE`LAvfH4i81;S_wE$W&b~Cs6XwQtZ!5-JQ{d0zz3t%p z5+`940op`lh`^eIA0)`FeePHi2drVAZV>U52yn+x$VLTK$xs4O4iO!XQhg@pqynw0 z(ZkMI1CUh*LUY_HuM3_co<3dvLkUoUsZIdQBScgxfv|P8E+V@C8z|3QdH_Qu9Rbx& z?Ho?RNPy}aJfKtGLK&IIU5Ev04B}jU_>WVb1C{t2EC4er-coEuwx{ydo z0g_e+h)%IN7n}*G`Bdw|-~{NIS`mt%nDL$;QmTS- z%&-zK?4Ss1Md}s;F;hbUN%KjMr}368vmf|3r5F6-}!frIY|) zv3_stz;3STBOWV`w|~N@K0-b^Xphrv2*9XuaffMQe7he-nfdF^1XdpXb&})P4=)h_ zR7b`L04Piam*`_Dz$1kkTmjptxew;0WN=7liNg2`(qK{YT#Kt3v8qJ+pur@sh-OQHF7G(I6jb4|1tszt8+8W zMthDdZT!CB>h)P6Ag>)IrMG&$*RXV7BDUO`MWUhjPODg9dq;=SVphBJ1b^0T)~)AG z?JZ>epgWSHC&T*=uWRCmz_6e?pm32pgFRkz3O{i ztHOrc`?n#Hi0bWhuit;uJpt@&rxZ>$U3E$7WbkHZO4v=>yV2aEX z5mzco6|8F&u#uFU^STJz=bQo?NkM{SC2&=Go(|^}0KgQL8aaRoi~~ibB(exW!W0#C zL|AdgBEaQziAo0+ivXfV4N!!-YStB%DP5)lGq%>2wZ&4a51#}$37X|a3L6r00#vbK3;!rV}hWKtWE?ddU6af z3`9(886#kl8wzkxo}#oWK*h&ZAYf8LC6iVH)B!rD95`bsAYio%s0g_l)Fo+GfK=dZ z$ibekd^D*ZGDZN7O~sd$6yQ+e>fo8?qEwKB#!vJHmQ0MYORPg??wr3@*P@(m-?BWo z4M8HxI1}rytXv-4hTC>z=Qg~2fRqy8bAmtE7i;XAEJV?=SX;%yjzY-7iqxV9fr4T4 zMM3G@SS->`z?F~_0XXLakssSMH=%$lID9z~;NU75ti?%1N(8t9TnU$Ah(&;;22fT~ zz;kVSwpGDd0kzPg-bWE}BA^3Pi|<%<&>6Nx=|lj|`GG@VYV~8@Ctz_n$?ulV^K1-F zQo4YtKCsunj6fnPSD9GKoNsXC*Bgj38Z3>i-?;>EB$CwCD4R)xrB??VI?@nJ`+&lI4TE!qfHt z$LO4hPe6Y@Ns61;ANOq$eDIz$>PI(zMO;GwhCTsx6A=P|_Fd2Mms_9qg&5E;cwSub zrT{pGO5n1#r;DK-&+ylN_VNj_s9jfaLw?if2~$ge7B^3*F{VI`9)>yvnh+>BlH@az zK$rqNda*xOv{+OxpuknFTKgb(9{Bi_0*$~&avl#Skau#9Z*V+kp|Db5fADurd<7QO zH&JX+k8{KgomvYOIFW)MF4-1(1}vLeEG93l5qQ0BL`DG_}y>Ys=P1o*h3%N63Kw_Zw`8`-T?6rXol zVy>`d5S%m-c&|42Ey%&`!C&=L2hu_V1kNt#Bl6E3;M;>ZUfZ^AD>{9vlTQYu`nBsP z@^g}v))F9aaY0bcb+2v_pB&hkP(W2dRe%Vns(=6=AR~JYneVmlIy5n#+c`LXRi|1R zgbH9_g`8`CNi;Bb_*FBj~08wLb1-PHuj}8X6VbP>nNp)}{PyelHzPZKm zQ{TQ-$(^V>GcncalQ+eu;-884XZ-7w^$Uldlm7w4w5DC-tmUWx0000 Date: Thu, 12 Jun 2025 08:24:27 +0900 Subject: [PATCH 32/51] [UI/UX] Default cursor to no when stop trying to teach move https://github.com/pagefaultgames/pokerogue/pull/5924 * [UI/UX] "Stop trying to teach move" Defaulting to "No" * [Test] setCursor to 0, 'Yes' to end learning move * Move confirmUIMode to its own file in enums, add docs --------- Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> --- src/enums/confirm-ui-mode.ts | 13 +++++++++++++ src/phases/learn-move-phase.ts | 5 +++++ src/ui/confirm-ui-handler.ts | 14 +++++++++++++- test/phases/learn-move-phase.test.ts | 4 ++++ 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/enums/confirm-ui-mode.ts diff --git a/src/enums/confirm-ui-mode.ts b/src/enums/confirm-ui-mode.ts new file mode 100644 index 00000000000..46bc42374cd --- /dev/null +++ b/src/enums/confirm-ui-mode.ts @@ -0,0 +1,13 @@ +// biome-ignore lint/correctness/noUnusedImports: Used in tsdoc +import type ConfirmUiHandler from "#app/ui/confirm-ui-handler"; + +/** + * Used by {@linkcode ConfirmUiHandler} to determine whether the cursor should start on Yes or No + */ +export const ConfirmUiMode = Object.freeze({ + /** Start cursor on Yes */ + DEFAULT_YES: 1, + /** Start cursor on No */ + DEFAULT_NO: 2 +}); +export type ConfirmUiMode = typeof ConfirmUiMode[keyof typeof ConfirmUiMode]; \ No newline at end of file diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts index e24efa63b5a..e197f876d76 100644 --- a/src/phases/learn-move-phase.ts +++ b/src/phases/learn-move-phase.ts @@ -12,6 +12,7 @@ import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase"; import type Pokemon from "#app/field/pokemon"; +import { ConfirmUiMode } from "#enums/confirm-ui-mode"; import { LearnMoveType } from "#enums/learn-move-type"; export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { @@ -163,6 +164,10 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { globalScene.ui.setMode(this.messageMode); this.replaceMoveCheck(move, pokemon); }, + false, + 0, + 0, + ConfirmUiMode.DEFAULT_NO, ); } diff --git a/src/ui/confirm-ui-handler.ts b/src/ui/confirm-ui-handler.ts index 7b5ca3d7e63..37fd50ca671 100644 --- a/src/ui/confirm-ui-handler.ts +++ b/src/ui/confirm-ui-handler.ts @@ -4,8 +4,11 @@ import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; import { Button } from "#enums/buttons"; import { globalScene } from "#app/global-scene"; +import { ConfirmUiMode } from "#enums/confirm-ui-mode"; export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler { + private confirmUiMode: ConfirmUiMode; + public static readonly windowWidth: number = 48; private switchCheck: boolean; @@ -105,7 +108,16 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler { this.optionSelectContainer.setPosition(globalScene.game.canvas.width / 6 - 1 + xOffset, -48 + yOffset); - this.setCursor(this.switchCheck ? this.switchCheckCursor : 0); + this.confirmUiMode = args.length >= 6 ? (args[5] as ConfirmUiMode) : ConfirmUiMode.DEFAULT_YES; + + switch (this.confirmUiMode) { + case ConfirmUiMode.DEFAULT_YES: + this.setCursor(this.switchCheck ? this.switchCheckCursor : 0); + break; + case ConfirmUiMode.DEFAULT_NO: + this.setCursor(this.switchCheck ? this.switchCheckCursor : 1); + break; + } return true; } diff --git a/test/phases/learn-move-phase.test.ts b/test/phases/learn-move-phase.test.ts index 88b8187069b..05dbf71d1f4 100644 --- a/test/phases/learn-move-phase.test.ts +++ b/test/phases/learn-move-phase.test.ts @@ -92,6 +92,10 @@ describe("Learn Move Phase", () => { game.onNextPrompt("LearnMovePhase", UiMode.CONFIRM, () => { game.scene.ui.processInput(Button.ACTION); }); + game.onNextPrompt("LearnMovePhase", UiMode.CONFIRM, () => { + game.scene.ui.setCursor(0); + game.scene.ui.processInput(Button.ACTION); + }); await game.phaseInterceptor.to(LearnMovePhase); const levelReq = bulbasaur.getLevelMoves(5)[0][0]; From 1029afcdbf538b84942ef17b4c4c83ac29543702 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Wed, 11 Jun 2025 21:42:04 -0500 Subject: [PATCH 33/51] [Refactor] Remove circular dependencies (part 4) (#5964) * Add abilityAttr.is methods * [WIP] move modifier stuff around * Untangle circular deps from modifiers * Move unlockables to own file * Untangle all circular deps outside of MEs * Move constants in MEs to their own files * Re-add missing import to battle.ts * Add necessary overload for getTag * Add missing type import in weather.ts * Init modifier types and pools in loading-scene * Remove stray commented code * Apply kev's suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/@types/ability-types.ts | 22 +- src/@types/modifier-types.ts | 32 + src/battle-scene.ts | 35 +- src/battle.ts | 6 +- src/constants.ts | 40 + src/data/abilities/ab-attrs/ab-attr.ts | 58 - src/data/abilities/ability-class.ts | 137 -- src/data/abilities/ability.ts | 1167 +++++++---------- src/data/abilities/apply-ab-attrs.ts | 829 ++++++++++++ src/data/arena-tag.ts | 26 +- src/data/balance/pokemon-evolutions.ts | 11 +- src/data/balance/tms.ts | 2 +- src/data/battle-anims.ts | 3 +- src/data/battler-tags.ts | 33 +- src/data/berry.ts | 16 +- src/data/challenge.ts | 2 +- src/data/data-lists.ts | 6 +- src/data/dialogue.ts | 43 - src/data/double-battle-dialogue.ts | 44 + src/data/moves/move.ts | 106 +- .../encounters/a-trainers-test-encounter.ts | 4 +- .../encounters/absolute-avarice-encounter.ts | 2 +- .../an-offer-you-cant-refuse-encounter.ts | 2 +- .../encounters/berries-abound-encounter.ts | 4 +- .../encounters/bug-type-superfan-encounter.ts | 4 +- .../encounters/clowning-around-encounter.ts | 9 +- .../encounters/dancing-lessons-encounter.ts | 2 +- .../encounters/dark-deal-encounter.ts | 2 +- .../encounters/delibirdy-encounter.ts | 2 +- .../department-store-sale-encounter.ts | 4 +- .../encounters/field-trip-encounter.ts | 2 +- .../encounters/fiery-fallout-encounter.ts | 6 +- .../encounters/fight-or-flight-encounter.ts | 4 +- .../encounters/fun-and-games-encounter.ts | 2 +- .../global-trade-system-encounter.ts | 4 +- .../mysterious-challengers-encounter.ts | 4 +- .../encounters/mysterious-chest-encounter.ts | 2 +- .../shady-vitamin-dealer-encounter.ts | 2 +- .../slumbering-snorlax-encounter.ts | 2 +- .../teleporting-hijinks-encounter.ts | 3 +- .../the-expert-pokemon-breeder-encounter.ts | 2 +- .../encounters/the-strong-stuff-encounter.ts | 2 +- .../the-winstrate-challenge-encounter.ts | 8 +- .../encounters/training-session-encounter.ts | 2 +- .../encounters/trash-to-treasure-encounter.ts | 4 +- .../encounters/weird-dream-encounter.ts | 4 +- .../mystery-encounter-save-data.ts | 2 +- .../mystery-encounters/mystery-encounters.ts | 36 - .../utils/encounter-phase-utils.ts | 9 +- .../utils/encounter-pokemon-utils.ts | 2 +- src/data/pokeball.ts | 3 +- src/data/trainers/trainer-config.ts | 6 +- src/data/weather.ts | 6 +- src/enums/modifier-pool-type.ts | 7 + src/{modifier => enums}/modifier-tier.ts | 0 src/enums/unlockables.ts | 7 + src/field/arena.ts | 11 +- src/field/pokemon.ts | 179 ++- src/loading-scene.ts | 5 + src/modifier/init-modifier-pools.ts | 854 ++++++++++++ src/modifier/modifier-pools.ts | 16 + src/modifier/modifier-type.ts | 946 ++----------- src/modifier/modifier.ts | 177 ++- src/overrides.ts | 2 +- src/phases/add-enemy-buff-modifier-phase.ts | 4 +- src/phases/attempt-run-phase.ts | 11 +- src/phases/battle-end-phase.ts | 4 +- src/phases/berry-phase.ts | 13 +- src/phases/encounter-phase.ts | 16 +- src/phases/faint-phase.ts | 13 +- src/phases/game-over-phase.ts | 4 +- src/phases/modifier-reward-phase.ts | 5 +- src/phases/move-effect-phase.ts | 33 +- src/phases/move-end-phase.ts | 4 +- src/phases/move-phase.ts | 34 +- src/phases/new-biome-encounter-phase.ts | 4 +- src/phases/obtain-status-effect-phase.ts | 4 +- src/phases/post-summon-phase.ts | 6 +- src/phases/post-turn-status-effect-phase.ts | 17 +- src/phases/quiet-form-change-phase.ts | 13 +- src/phases/ribbon-modifier-reward-phase.ts | 2 +- src/phases/select-modifier-phase.ts | 4 +- src/phases/stat-stage-change-phase.ts | 28 +- src/phases/summon-phase.ts | 4 +- src/phases/switch-summon-phase.ts | 14 +- src/phases/title-phase.ts | 11 +- src/phases/trainer-victory-phase.ts | 2 +- src/phases/turn-end-phase.ts | 4 +- src/phases/turn-start-phase.ts | 6 +- src/phases/unlock-phase.ts | 2 +- src/phases/victory-phase.ts | 2 +- src/phases/weather-effect-phase.ts | 14 +- src/system/game-data.ts | 2 +- src/system/unlockables.ts | 8 +- src/ui/party-ui-handler.ts | 21 +- src/ui/starter-select-ui-handler.ts | 2 +- src/ui/summary-ui-handler.ts | 2 +- src/ui/text.ts | 2 +- src/utils/modifier-utils.ts | 35 + test/abilities/healer.test.ts | 4 +- test/abilities/neutralizing_gas.test.ts | 3 +- .../abilities/normal-move-type-change.test.ts | 5 +- test/abilities/quick_draw.test.ts | 9 +- test/abilities/sand_veil.test.ts | 3 +- test/abilities/shield_dust.test.ts | 11 +- test/abilities/unburden.test.ts | 3 +- test/battle/damage_calculation.test.ts | 2 +- test/items/light_ball.test.ts | 2 +- test/items/lock_capsule.test.ts | 2 +- test/items/metal_powder.test.ts | 2 +- test/items/quick_powder.test.ts | 2 +- test/items/thick_club.test.ts | 2 +- test/moves/safeguard.test.ts | 3 +- test/moves/secret_power.test.ts | 3 +- .../clowning-around-encounter.test.ts | 4 +- .../encounters/delibirdy-encounter.test.ts | 2 +- .../global-trade-system-encounter.test.ts | 4 +- .../mysterious-challengers-encounter.test.ts | 2 +- .../trash-to-treasure-encounter.test.ts | 5 +- .../uncommon-breed-encounter.test.ts | 2 +- .../encounters/weird-dream-encounter.test.ts | 2 +- test/phases/form-change-phase.test.ts | 2 +- test/phases/game-over-phase.test.ts | 2 +- test/phases/select-modifier-phase.test.ts | 5 +- test/testUtils/gameManager.ts | 3 +- test/testUtils/helpers/field-helper.ts | 2 +- test/testUtils/helpers/overridesHelper.ts | 2 +- test/testUtils/testFileInitialization.ts | 4 + 128 files changed, 2994 insertions(+), 2427 deletions(-) create mode 100644 src/@types/modifier-types.ts delete mode 100644 src/data/abilities/ab-attrs/ab-attr.ts delete mode 100644 src/data/abilities/ability-class.ts create mode 100644 src/data/abilities/apply-ab-attrs.ts create mode 100644 src/data/double-battle-dialogue.ts create mode 100644 src/enums/modifier-pool-type.ts rename src/{modifier => enums}/modifier-tier.ts (100%) create mode 100644 src/enums/unlockables.ts create mode 100644 src/modifier/init-modifier-pools.ts create mode 100644 src/modifier/modifier-pools.ts create mode 100644 src/utils/modifier-utils.ts diff --git a/src/@types/ability-types.ts b/src/@types/ability-types.ts index 5d21aaaa844..6f21a012b64 100644 --- a/src/@types/ability-types.ts +++ b/src/@types/ability-types.ts @@ -1,11 +1,27 @@ -import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr"; +import type { AbAttr } from "#app/data/abilities/ability"; import type Move from "#app/data/moves/move"; import type Pokemon from "#app/field/pokemon"; import type { BattleStat } from "#enums/stat"; +import type { AbAttrConstructorMap } from "#app/data/abilities/ability"; -export type AbAttrApplyFunc = (attr: TAttr, passive: boolean) => void; -export type AbAttrSuccessFunc = (attr: TAttr, passive: boolean) => boolean; +// Intentionally re-export all types from the ability attributes module +export type * from "#app/data/abilities/ability"; + +export type AbAttrApplyFunc = (attr: TAttr, passive: boolean, ...args: any[]) => void; +export type AbAttrSuccessFunc = (attr: TAttr, passive: boolean, ...args: any[]) => boolean; export type AbAttrCondition = (pokemon: Pokemon) => boolean; export type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean; export type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean; export type PokemonStatStageChangeCondition = (target: Pokemon, statsChanged: BattleStat[], stages: number) => boolean; + +/** + * Union type of all ability attribute class names as strings + */ +export type AbAttrString = keyof AbAttrConstructorMap; + +/** + * Map of ability attribute class names to an instance of the class. + */ +export type AbAttrMap = { + [K in keyof AbAttrConstructorMap]: InstanceType; +}; diff --git a/src/@types/modifier-types.ts b/src/@types/modifier-types.ts new file mode 100644 index 00000000000..6c0136e655e --- /dev/null +++ b/src/@types/modifier-types.ts @@ -0,0 +1,32 @@ +/** + * Re-exports of all the types defined in the modifier module. + */ + +import type Pokemon from "#app/field/pokemon"; +import type { ModifierConstructorMap } from "#app/modifier/modifier"; +import type { ModifierType, WeightedModifierType } from "#app/modifier/modifier-type"; +export type ModifierTypeFunc = () => ModifierType; +export type WeightedModifierTypeWeightFunc = (party: Pokemon[], rerollCount?: number) => number; + +export type { ModifierConstructorMap } from "#app/modifier/modifier"; + +/** + * Map of modifier names to their respective instance types + */ +export type ModifierInstanceMap = { + [K in keyof ModifierConstructorMap]: InstanceType; +}; + +/** + * Union type of all modifier constructors. + */ +export type ModifierClass = ModifierConstructorMap[keyof ModifierConstructorMap]; + +/** + * Union type of all modifier names as strings. + */ +export type ModifierString = keyof ModifierConstructorMap; + +export type ModifierPool = { + [tier: string]: WeightedModifierType[]; +} \ No newline at end of file diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 5586691a48d..d5cb5dcae42 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -58,23 +58,15 @@ import { getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, - getModifierPoolForType, - getModifierType, getPartyLuckValue, - ModifierPoolType, - modifierTypes, PokemonHeldItemModifierType, } from "#app/modifier/modifier-type"; +import { getModifierType } from "./utils/modifier-utils"; +import { modifierTypes } from "./data/data-lists"; +import { getModifierPoolForType } from "./utils/modifier-utils"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import AbilityBar from "#app/ui/ability-bar"; -import { - applyAbAttrs, - applyPostBattleInitAbAttrs, - applyPostItemLostAbAttrs, - BlockItemTheftAbAttr, - DoubleBattleChanceAbAttr, - PostBattleInitAbAttr, - PostItemLostAbAttr, -} from "#app/data/abilities/ability"; +import { applyAbAttrs, applyPostBattleInitAbAttrs, applyPostItemLostAbAttrs } from "./data/abilities/apply-ab-attrs"; import { allAbilities } from "./data/data-lists"; import type { FixedBattleConfig } from "#app/battle"; import Battle from "#app/battle"; @@ -145,14 +137,13 @@ import { LoadingScene } from "#app/loading-scene"; import type { MovePhase } from "#app/phases/move-phase"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { allMysteryEncounters, mysteryEncountersByBiome } from "#app/data/mystery-encounters/mystery-encounters"; import { - allMysteryEncounters, ANTI_VARIANCE_WEIGHT_MODIFIER, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, - mysteryEncountersByBiome, -} from "#app/data/mystery-encounters/mystery-encounters"; +} from "./constants"; import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -1264,7 +1255,7 @@ export default class BattleScene extends SceneBase { const doubleChance = new NumberHolder(newWaveIndex % 10 === 0 ? 32 : 8); this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance); for (const p of playerField) { - applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance); + applyAbAttrs("DoubleBattleChanceAbAttr", p, null, false, doubleChance); } return Math.max(doubleChance.value, 1); } @@ -1469,7 +1460,7 @@ export default class BattleScene extends SceneBase { for (const pokemon of this.getPlayerParty()) { pokemon.resetBattleAndWaveData(); pokemon.resetTera(); - applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon); + applyPostBattleInitAbAttrs("PostBattleInitAbAttr", pokemon); if ( pokemon.hasSpecies(SpeciesId.TERAPAGOS) || (this.gameMode.isClassic && this.currentBattle.waveIndex > 180 && this.currentBattle.waveIndex <= 190) @@ -2745,7 +2736,7 @@ export default class BattleScene extends SceneBase { const cancelled = new BooleanHolder(false); if (source && source.isPlayer() !== target.isPlayer()) { - applyAbAttrs(BlockItemTheftAbAttr, source, cancelled); + applyAbAttrs("BlockItemTheftAbAttr", source, cancelled); } if (cancelled.value) { @@ -2785,13 +2776,13 @@ export default class BattleScene extends SceneBase { if (target.isPlayer()) { this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant); if (source && itemLost) { - applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); + applyPostItemLostAbAttrs("PostItemLostAbAttr", source, false); } return true; } this.addEnemyModifier(newItemModifier, ignoreUpdate, instant); if (source && itemLost) { - applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); + applyPostItemLostAbAttrs("PostItemLostAbAttr", source, false); } return true; } @@ -2814,7 +2805,7 @@ export default class BattleScene extends SceneBase { const cancelled = new BooleanHolder(false); if (source && source.isPlayer() !== target.isPlayer()) { - applyAbAttrs(BlockItemTheftAbAttr, source, cancelled); + applyAbAttrs("BlockItemTheftAbAttr", source, cancelled); } if (cancelled.value) { diff --git a/src/battle.ts b/src/battle.ts index 0cf01a0873d..245705f4801 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -13,7 +13,7 @@ import { import Trainer from "./field/trainer"; import { TrainerVariant } from "#enums/trainer-variant"; import type { GameMode } from "./game-mode"; -import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier"; +import { MoneyMultiplierModifier, type PokemonHeldItemModifier } from "./modifier/modifier"; import type { PokeballType } from "#enums/pokeball"; import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { SpeciesFormKey } from "#enums/species-form-key"; @@ -30,7 +30,7 @@ import i18next from "#app/plugins/i18n"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import type { CustomModifierSettings } from "#app/modifier/modifier-type"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { BattleType } from "#enums/battle-type"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; @@ -173,7 +173,7 @@ export default class Battle { this.postBattleLoot.push( ...globalScene .findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferable, + m => m.is("PokemonHeldItemModifier") && m.pokemonId === enemyPokemon.id && m.isTransferable, false, ) .map(i => { diff --git a/src/constants.ts b/src/constants.ts index 62056ecc0d4..f3b37563d11 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -54,3 +54,43 @@ export const defaultStarterSpecies: SpeciesId[] = [ ]; export const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary + +/** + * Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * ) / MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT + */ +export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 3; + +/** + * The divisor for determining ME spawns, defines the "maximum" weight required for a spawn + * If spawn_weight === MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, 100% chance to spawn a ME + */ +export const MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT = 256; + +/** + * When an ME spawn roll fails, WEIGHT_INCREMENT_ON_SPAWN_MISS is added to future rolls for ME spawn checks. + * These values are cleared whenever the next ME spawns, and spawn weight returns to BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + */ +export const WEIGHT_INCREMENT_ON_SPAWN_MISS = 3; + +/** + * Specifies the target average for total ME spawns in a single Classic run. + * Used by anti-variance mechanic to check whether a run is above or below the target on a given wave. + */ +export const AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 12; + +/** + * Will increase/decrease the chance of spawning a ME based on the current run's total MEs encountered vs AVERAGE_ENCOUNTERS_PER_RUN_TARGET + * Example: + * AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 17 (expects avg 1 ME every 10 floors) + * ANTI_VARIANCE_WEIGHT_MODIFIER = 15 + * + * On wave 20, if 1 ME has been encountered, the difference from expected average is 0 MEs. + * So anti-variance adds 0/256 to the spawn weight check for ME spawn. + * + * On wave 20, if 0 MEs have been encountered, the difference from expected average is 1 ME. + * So anti-variance adds 15/256 to the spawn weight check for ME spawn. + * + * On wave 20, if 2 MEs have been encountered, the difference from expected average is -1 ME. + * So anti-variance adds -15/256 to the spawn weight check for ME spawn. + */ +export const ANTI_VARIANCE_WEIGHT_MODIFIER = 15; diff --git a/src/data/abilities/ab-attrs/ab-attr.ts b/src/data/abilities/ab-attrs/ab-attr.ts deleted file mode 100644 index 24fbb6dc338..00000000000 --- a/src/data/abilities/ab-attrs/ab-attr.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type { AbAttrCondition } from "#app/@types/ability-types"; -import type Pokemon from "#app/field/pokemon"; -import type { BooleanHolder } from "#app/utils/common"; - -export abstract class AbAttr { - public showAbility: boolean; - private extraCondition: AbAttrCondition; - - /** - * @param showAbility - Whether to show this ability as a flyout during battle; default `true`. - * Should be kept in parity with mainline where possible. - */ - constructor(showAbility = true) { - this.showAbility = showAbility; - } - - /** - * Applies ability effects without checking conditions - * @param _pokemon - The pokemon to apply this ability to - * @param _passive - Whether or not the ability is a passive - * @param _simulated - Whether the call is simulated - * @param _args - Extra args passed to the function. Handled by child classes. - * @see {@linkcode canApply} - */ - apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder | null, - _args: any[], - ): void {} - - getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null { - return null; - } - - getCondition(): AbAttrCondition | null { - return this.extraCondition || null; - } - - addCondition(condition: AbAttrCondition): AbAttr { - this.extraCondition = condition; - return this; - } - - /** - * Returns a boolean describing whether the ability can be applied under current conditions - * @param _pokemon - The pokemon to apply this ability to - * @param _passive - Whether or not the ability is a passive - * @param _simulated - Whether the call is simulated - * @param _args - Extra args passed to the function. Handled by child classes. - * @returns `true` if the ability can be applied, `false` otherwise - * @see {@linkcode apply} - */ - canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { - return true; - } -} diff --git a/src/data/abilities/ability-class.ts b/src/data/abilities/ability-class.ts deleted file mode 100644 index 10bd01f3987..00000000000 --- a/src/data/abilities/ability-class.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { AbilityId } from "#enums/ability-id"; -import type { AbAttrCondition } from "#app/@types/ability-types"; -import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr"; -import i18next from "i18next"; -import type { Localizable } from "#app/@types/locales"; -import type { Constructor } from "#app/utils/common"; - -export class Ability implements Localizable { - public id: AbilityId; - - private nameAppend: string; - public name: string; - public description: string; - public generation: number; - public isBypassFaint: boolean; - public isIgnorable: boolean; - public isSuppressable = true; - public isCopiable = true; - public isReplaceable = true; - public attrs: AbAttr[]; - public conditions: AbAttrCondition[]; - - constructor(id: AbilityId, generation: number) { - this.id = id; - - this.nameAppend = ""; - this.generation = generation; - this.attrs = []; - this.conditions = []; - - this.isSuppressable = true; - this.isCopiable = true; - this.isReplaceable = true; - - this.localize(); - } - - public get isSwappable(): boolean { - return this.isCopiable && this.isReplaceable; - } - localize(): void { - const i18nKey = AbilityId[this.id] - .split("_") - .filter(f => f) - .map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase())) - .join("") as string; - - this.name = this.id ? `${i18next.t(`ability:${i18nKey}.name`) as string}${this.nameAppend}` : ""; - this.description = this.id ? (i18next.t(`ability:${i18nKey}.description`) as string) : ""; - } - - /** - * Get all ability attributes that match `attrType` - * @param attrType any attribute that extends {@linkcode AbAttr} - * @returns Array of attributes that match `attrType`, Empty Array if none match. - */ - getAttrs(attrType: Constructor): T[] { - return this.attrs.filter((a): a is T => a instanceof attrType); - } - - /** - * Check if an ability has an attribute that matches `attrType` - * @param attrType any attribute that extends {@linkcode AbAttr} - * @returns true if the ability has attribute `attrType` - */ - hasAttr(attrType: Constructor): boolean { - return this.attrs.some(attr => attr instanceof attrType); - } - - attr>(AttrType: T, ...args: ConstructorParameters): Ability { - const attr = new AttrType(...args); - this.attrs.push(attr); - - return this; - } - - conditionalAttr>( - condition: AbAttrCondition, - AttrType: T, - ...args: ConstructorParameters - ): Ability { - const attr = new AttrType(...args); - attr.addCondition(condition); - this.attrs.push(attr); - - return this; - } - - bypassFaint(): Ability { - this.isBypassFaint = true; - return this; - } - - ignorable(): Ability { - this.isIgnorable = true; - return this; - } - - unsuppressable(): Ability { - this.isSuppressable = false; - return this; - } - - uncopiable(): Ability { - this.isCopiable = false; - return this; - } - - unreplaceable(): Ability { - this.isReplaceable = false; - return this; - } - - condition(condition: AbAttrCondition): Ability { - this.conditions.push(condition); - - return this; - } - - partial(): this { - this.nameAppend += " (P)"; - return this; - } - - unimplemented(): this { - this.nameAppend += " (N)"; - return this; - } - - /** - * Internal flag used for developers to document edge cases. When using this, please be sure to document the edge case. - * @returns the ability - */ - edgeCase(): this { - return this; - } -} diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 128e772217f..2efe3607b4f 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -37,8 +37,6 @@ import { BattleType } from "#enums/battle-type"; import type { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { globalScene } from "#app/global-scene"; import { allAbilities } from "#app/data/data-lists"; -import { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr"; -import { Ability } from "#app/data/abilities/ability-class"; // Enum imports import { Stat, type BattleStat, BATTLE_STATS, EFFECTIVE_STATS, getStatKey, type EffectiveStat } from "#enums/stat"; @@ -71,13 +69,223 @@ import type { PokemonDefendCondition, PokemonStatStageChangeCondition, PokemonAttackCondition, - AbAttrApplyFunc, - AbAttrSuccessFunc, + AbAttrString, + AbAttrMap, } from "#app/@types/ability-types"; import type { BattlerIndex } from "#enums/battler-index"; import type Move from "#app/data/moves/move"; import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag"; import { noAbilityTypeOverrideMoves } from "../moves/invalid-moves"; +import type { Localizable } from "#app/@types/locales"; +import { applyAbAttrs } from "./apply-ab-attrs"; + +export class Ability implements Localizable { + public id: AbilityId; + + private nameAppend: string; + public name: string; + public description: string; + public generation: number; + public isBypassFaint: boolean; + public isIgnorable: boolean; + public isSuppressable = true; + public isCopiable = true; + public isReplaceable = true; + public attrs: AbAttr[]; + public conditions: AbAttrCondition[]; + + constructor(id: AbilityId, generation: number) { + this.id = id; + + this.nameAppend = ""; + this.generation = generation; + this.attrs = []; + this.conditions = []; + + this.localize(); + } + + public get isSwappable(): boolean { + return this.isCopiable && this.isReplaceable; + } + + localize(): void { + const i18nKey = AbilityId[this.id] + .split("_") + .filter(f => f) + .map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase())) + .join("") as string; + + this.name = this.id ? `${i18next.t(`ability:${i18nKey}.name`) as string}${this.nameAppend}` : ""; + this.description = this.id ? (i18next.t(`ability:${i18nKey}.description`) as string) : ""; + } + + /** + * Get all ability attributes that match `attrType` + * @param attrType - any attribute that extends {@linkcode AbAttr} + * @returns Array of attributes that match `attrType`, Empty Array if none match. + */ + getAttrs(attrType: T): AbAttrMap[T][] { + const targetAttr = AbilityAttrs[attrType]; + if (!targetAttr) { + return []; + } + return this.attrs.filter((a): a is AbAttrMap[T] => a instanceof targetAttr); + } + + /** + * Check if an ability has an attribute that matches `attrType` + * @param attrType - any attribute that extends {@linkcode AbAttr} + * @returns true if the ability has attribute `attrType` + */ + hasAttr(attrType: T): boolean { + const targetAttr = AbilityAttrs[attrType]; + if (!targetAttr) { + return false; + } + return this.attrs.some(attr => attr instanceof targetAttr); + } + + attr>(AttrType: T, ...args: ConstructorParameters): Ability { + const attr = new AttrType(...args); + this.attrs.push(attr); + + return this; + } + + conditionalAttr>( + condition: AbAttrCondition, + AttrType: T, + ...args: ConstructorParameters + ): Ability { + const attr = new AttrType(...args); + attr.addCondition(condition); + this.attrs.push(attr); + + return this; + } + + bypassFaint(): Ability { + this.isBypassFaint = true; + return this; + } + + ignorable(): Ability { + this.isIgnorable = true; + return this; + } + + unsuppressable(): Ability { + this.isSuppressable = false; + return this; + } + + uncopiable(): Ability { + this.isCopiable = false; + return this; + } + + unreplaceable(): Ability { + this.isReplaceable = false; + return this; + } + + condition(condition: AbAttrCondition): Ability { + this.conditions.push(condition); + + return this; + } + + partial(): this { + this.nameAppend += " (P)"; + return this; + } + + unimplemented(): this { + this.nameAppend += " (N)"; + return this; + } + + /** + * Internal flag used for developers to document edge cases. When using this, please be sure to document the edge case. + * @returns the ability + */ + edgeCase(): this { + return this; + } +} + +export abstract class AbAttr { + public showAbility: boolean; + private extraCondition: AbAttrCondition; + + /** + * Return whether this attribute is of the given type. + * + * @remarks + * Used to avoid requiring the caller to have imported the specific attribute type, avoiding circular dependencies. + * + * @param attr - The attribute to check against + * @returns Whether the attribute is an instance of the given type + */ + public is(attr: K): this is AbAttrMap[K] { + const targetAttr = AbilityAttrs[attr]; + if (!targetAttr) { + return false; + } + return this instanceof targetAttr; + } + + /** + * @param showAbility - Whether to show this ability as a flyout during battle; default `true`. + * Should be kept in parity with mainline where possible. + */ + constructor(showAbility = true) { + this.showAbility = showAbility; + } + + /** + * Applies ability effects without checking conditions + * @param _pokemon - The pokemon to apply this ability to + * @param _passive - Whether or not the ability is a passive + * @param _simulated - Whether the call is simulated + * @param _args - Extra args passed to the function. Handled by child classes. + * @see {@linkcode canApply} + */ + apply( + _pokemon: Pokemon, + _passive: boolean, + _simulated: boolean, + _cancelled: BooleanHolder | null, + _args: any[], + ): void {} + + getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null { + return null; + } + + getCondition(): AbAttrCondition | null { + return this.extraCondition || null; + } + + addCondition(condition: AbAttrCondition): AbAttr { + this.extraCondition = condition; + return this; + } + + /** + * Returns a boolean describing whether the ability can be applied under current conditions + * @param _pokemon - The pokemon to apply this ability to + * @param _passive - Whether or not the ability is a passive + * @param _simulated - Whether the call is simulated + * @param _args - Extra args passed to the function. Handled by child classes. + * @returns `true` if the ability can be applied, `false` otherwise + * @see {@linkcode apply} + */ + canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + return true; + } +} export class BlockRecoilDamageAttr extends AbAttr { constructor() { @@ -131,11 +339,11 @@ export class DoubleBattleChanceAbAttr extends AbAttr { } export class PostBattleInitAbAttr extends AbAttr { - canApplyPostBattleInit(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + canApplyPostBattleInit(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args?: any[]): boolean { return true; } - applyPostBattleInit(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {} + applyPostBattleInit(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args?: any[]): void {} } export class PostBattleInitFormChangeAbAttr extends PostBattleInitAbAttr { @@ -147,7 +355,7 @@ export class PostBattleInitFormChangeAbAttr extends PostBattleInitAbAttr { this.formFunc = formFunc; } - override canApplyPostBattleInit(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): boolean { + override canApplyPostBattleInit(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: never[]): boolean { const formIndex = this.formFunc(pokemon); return formIndex !== pokemon.formIndex && !simulated; } @@ -1478,7 +1686,7 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr { return ( !simulated && move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) && - !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) + !attacker.hasAbilityWithAttr("BlockNonDirectDamageAbAttr") ); } @@ -1657,7 +1865,7 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr { return ( move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) && attacker.getAbility().isSuppressable && - !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr) + !attacker.getAbility().hasAttr("PostDefendAbilityGiveAbAttr") ); } @@ -1924,9 +2132,7 @@ export class FieldMultiplyStatAbAttr extends AbAttr { this.canStack || (!hasApplied.value && this.stat === stat && - checkedPokemon - .getAbilityAttrs(FieldMultiplyStatAbAttr) - .every(attr => (attr as FieldMultiplyStatAbAttr).stat !== stat)) + checkedPokemon.getAbilityAttrs("FieldMultiplyStatAbAttr").every(attr => attr.stat !== stat)) ); } @@ -2650,7 +2856,7 @@ export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr { if ( super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args) && (simulated || - (!attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && + (!attacker.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") && pokemon !== attacker && (!this.contactRequired || move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon })) && @@ -2715,7 +2921,7 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr { /**Battler tags inflicted by abilities post attacking are also considered additional effects.*/ return ( super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args) && - !attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && + !attacker.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") && pokemon !== attacker && (!this.contactRequired || move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon })) && @@ -3308,8 +3514,8 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { for (const opponent of pokemon.getOpponents()) { const cancelled = new BooleanHolder(false); if (this.intimidate) { - applyAbAttrs(IntimidateImmunityAbAttr, opponent, cancelled, simulated); - applyAbAttrs(PostIntimidateStatStageChangeAbAttr, opponent, cancelled, simulated); + applyAbAttrs("IntimidateImmunityAbAttr", opponent, cancelled, simulated); + applyAbAttrs("PostIntimidateStatStageChangeAbAttr", opponent, cancelled, simulated); if (opponent.getTag(BattlerTagType.SUBSTITUTE)) { cancelled.value = true; @@ -5243,7 +5449,7 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr { _weather: Weather | null, _args: any[], ): boolean { - return !pokemon.hasAbilityWithAttr(BlockNonDirectDamageAbAttr); + return !pokemon.hasAbilityWithAttr("BlockNonDirectDamageAbAttr"); } override applyPostWeatherLapse( @@ -5544,7 +5750,7 @@ export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr { } // uncomment to make cheek pouch work with cud chew - // applyAbAttrs(HealFromBerryUseAbAttr, pokemon, new BooleanHolder(false)); + // applyAbAttrs("HealFromBerryUseAbAttr", pokemon, new BooleanHolder(false)); } /** @@ -5671,7 +5877,7 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr { .some( opp => (opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(AbilityId.COMATOSE)) && - !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && + !opp.hasAbilityWithAttr("BlockNonDirectDamageAbAttr") && !opp.switchOutStatus, ); } @@ -5686,7 +5892,7 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr { for (const opp of pokemon.getOpponents()) { if ( (opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(AbilityId.COMATOSE)) && - !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && + !opp.hasAbilityWithAttr("BlockNonDirectDamageAbAttr") && !opp.switchOutStatus ) { if (!simulated) { @@ -6326,8 +6532,8 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr { attacker !== undefined && move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }); const cancelled = new BooleanHolder(false); - globalScene.getField(true).map(p => applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled, simulated)); - return !(!diedToDirectDamage || cancelled.value || attacker!.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)); + globalScene.getField(true).map(p => applyAbAttrs("FieldPreventExplosiveMovesAbAttr", p, cancelled, simulated)); + return !(!diedToDirectDamage || cancelled.value || attacker!.hasAbilityWithAttr("BlockNonDirectDamageAbAttr")); } override applyPostFaint( @@ -7160,64 +7366,6 @@ export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr { } } -function applySingleAbAttrs( - pokemon: Pokemon, - passive: boolean, - attrType: Constructor, - applyFunc: AbAttrApplyFunc, - successFunc: AbAttrSuccessFunc, - args: any[], - gainedMidTurn = false, - simulated = false, - messages: string[] = [], -) { - if (!pokemon?.canApplyAbility(passive) || (passive && pokemon.getPassiveAbility().id === pokemon.getAbility().id)) { - return; - } - - const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility(); - if ( - gainedMidTurn && - ability.getAttrs(attrType).some(attr => attr instanceof PostSummonAbAttr && !attr.shouldActivateOnGain()) - ) { - return; - } - - for (const attr of ability.getAttrs(attrType)) { - const condition = attr.getCondition(); - let abShown = false; - if ((condition && !condition(pokemon)) || !successFunc(attr, passive)) { - continue; - } - - globalScene.phaseManager.setPhaseQueueSplice(); - - if (attr.showAbility && !simulated) { - globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true); - abShown = true; - } - const message = attr.getTriggerMessage(pokemon, ability.name, args); - if (message) { - if (!simulated) { - globalScene.phaseManager.queueMessage(message); - } - messages.push(message); - } - - applyFunc(attr, passive); - - if (abShown) { - globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false); - } - - if (!simulated) { - pokemon.waveData.abilitiesApplied.add(ability.id); - } - - globalScene.phaseManager.clearPhaseQueueSplice(); - } -} - class ForceSwitchOutHelper { constructor(private switchType: SwitchType) {} @@ -7329,7 +7477,7 @@ class ForceSwitchOutHelper { if (player) { const blockedByAbility = new BooleanHolder(false); - applyAbAttrs(ForceSwitchOutImmunityAbAttr, opponent, blockedByAbility); + applyAbAttrs("ForceSwitchOutImmunityAbAttr", opponent, blockedByAbility); return !blockedByAbility.value; } @@ -7367,7 +7515,7 @@ class ForceSwitchOutHelper { */ public getFailedText(target: Pokemon): string | null { const blockedByAbility = new BooleanHolder(false); - applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, blockedByAbility); + applyAbAttrs("ForceSwitchOutImmunityAbAttr", target, blockedByAbility); return blockedByAbility.value ? i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) }) : null; @@ -7522,646 +7670,233 @@ export class PostDamageForceSwitchAbAttr extends PostDamageAbAttr { this.helper.switchOutLogic(pokemon); } } -function applyAbAttrsInternal( - attrType: Constructor, - pokemon: Pokemon | null, - applyFunc: AbAttrApplyFunc, - successFunc: AbAttrSuccessFunc, - args: any[], - simulated = false, - messages: string[] = [], - gainedMidTurn = false, -) { - for (const passive of [false, true]) { - if (pokemon) { - applySingleAbAttrs(pokemon, passive, attrType, applyFunc, successFunc, args, gainedMidTurn, simulated, messages); - globalScene.phaseManager.clearPhaseQueueSplice(); - } - } -} - -export function applyAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - cancelled: BooleanHolder | null, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.apply(pokemon, passive, simulated, cancelled, args), - (attr, passive) => attr.canApply(pokemon, passive, simulated, args), - args, - simulated, - ); -} - -export function applyPostBattleInitAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPostBattleInit(pokemon, passive, simulated, args), - (attr, passive) => attr.canApplyPostBattleInit(pokemon, passive, simulated, args), - args, - simulated, - ); -} - -export function applyPreDefendAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - attacker: Pokemon, - move: Move | null, - cancelled: BooleanHolder | null, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args), - (attr, passive) => attr.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args), - args, - simulated, - ); -} - -export function applyPostDefendAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - attacker: Pokemon, - move: Move, - hitResult: HitResult | null, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args), - (attr, passive) => attr.canApplyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args), - args, - simulated, - ); -} - -export function applyPostMoveUsedAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - move: PokemonMove, - source: Pokemon, - targets: BattlerIndex[], - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, _passive) => attr.applyPostMoveUsed(pokemon, move, source, targets, simulated, args), - (attr, _passive) => attr.canApplyPostMoveUsed(pokemon, move, source, targets, simulated, args), - args, - simulated, - ); -} - -export function applyStatMultiplierAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - stat: BattleStat, - statValue: NumberHolder, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyStatStage(pokemon, passive, simulated, stat, statValue, args), - (attr, passive) => attr.canApplyStatStage(pokemon, passive, simulated, stat, statValue, args), - args, - ); -} /** - * Applies an ally's Stat multiplier attribute - * @param attrType - {@linkcode AllyStatMultiplierAbAttr} should always be AllyStatMultiplierAbAttr for the time being - * @param pokemon - The {@linkcode Pokemon} with the ability - * @param stat - The type of the checked {@linkcode Stat} - * @param statValue - {@linkcode NumberHolder} containing the value of the checked stat - * @param checkedPokemon - The {@linkcode Pokemon} with the checked stat - * @param ignoreAbility - Whether or not the ability should be ignored by the pokemon or its move. - * @param args - unused + * Map of all ability attribute constructors, for use with the `.is` method. */ -export function applyAllyStatMultiplierAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - stat: BattleStat, - statValue: NumberHolder, - simulated = false, - checkedPokemon: Pokemon, - ignoreAbility: boolean, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => - attr.applyAllyStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, ignoreAbility, args), - (attr, passive) => - attr.canApplyAllyStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, ignoreAbility, args), - args, - simulated, - ); -} - -export function applyPostSetStatusAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - effect: StatusEffect, - sourcePokemon?: Pokemon | null, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args), - (attr, passive) => attr.canApplyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args), - args, - simulated, - ); -} - -export function applyPostDamageAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - damage: number, - _passive: boolean, - simulated = false, - args: any[], - source?: Pokemon, -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPostDamage(pokemon, damage, passive, simulated, args, source), - (attr, passive) => attr.canApplyPostDamage(pokemon, damage, passive, simulated, args, source), - args, - ); -} +const AbilityAttrs = Object.freeze({ + BlockRecoilDamageAttr, + DoubleBattleChanceAbAttr, + PostBattleInitAbAttr, + PostBattleInitFormChangeAbAttr, + PostTeraFormChangeStatChangeAbAttr, + ClearWeatherAbAttr, + ClearTerrainAbAttr, + PreDefendAbAttr, + PreDefendFullHpEndureAbAttr, + BlockItemTheftAbAttr, + StabBoostAbAttr, + ReceivedMoveDamageMultiplierAbAttr, + AlliedFieldDamageReductionAbAttr, + ReceivedTypeDamageMultiplierAbAttr, + TypeImmunityAbAttr, + AttackTypeImmunityAbAttr, + TypeImmunityHealAbAttr, + NonSuperEffectiveImmunityAbAttr, + FullHpResistTypeAbAttr, + PostDefendAbAttr, + FieldPriorityMoveImmunityAbAttr, + PostStatStageChangeAbAttr, + MoveImmunityAbAttr, + WonderSkinAbAttr, + MoveImmunityStatStageChangeAbAttr, + ReverseDrainAbAttr, + PostDefendStatStageChangeAbAttr, + PostDefendHpGatedStatStageChangeAbAttr, + PostDefendApplyArenaTrapTagAbAttr, + PostDefendApplyBattlerTagAbAttr, + PostDefendTypeChangeAbAttr, + PostDefendTerrainChangeAbAttr, + PostDefendContactApplyStatusEffectAbAttr, + EffectSporeAbAttr, + PostDefendContactApplyTagChanceAbAttr, + PostDefendCritStatStageChangeAbAttr, + PostDefendContactDamageAbAttr, + PostDefendPerishSongAbAttr, + PostDefendWeatherChangeAbAttr, + PostDefendAbilitySwapAbAttr, + PostDefendAbilityGiveAbAttr, + PostDefendMoveDisableAbAttr, + PostStatStageChangeStatStageChangeAbAttr, + PreAttackAbAttr, + MoveEffectChanceMultiplierAbAttr, + IgnoreMoveEffectsAbAttr, + VariableMovePowerAbAttr, + FieldPreventExplosiveMovesAbAttr, + FieldMultiplyStatAbAttr, + MoveTypeChangeAbAttr, + PokemonTypeChangeAbAttr, + AddSecondStrikeAbAttr, + DamageBoostAbAttr, + MovePowerBoostAbAttr, + MoveTypePowerBoostAbAttr, + LowHpMoveTypePowerBoostAbAttr, + VariableMovePowerBoostAbAttr, + FieldMovePowerBoostAbAttr, + PreAttackFieldMoveTypePowerBoostAbAttr, + FieldMoveTypePowerBoostAbAttr, + UserFieldMoveTypePowerBoostAbAttr, + AllyMoveCategoryPowerBoostAbAttr, + StatMultiplierAbAttr, + PostAttackAbAttr, + AllyStatMultiplierAbAttr, + ExecutedMoveAbAttr, + GorillaTacticsAbAttr, + PostAttackStealHeldItemAbAttr, + PostAttackApplyStatusEffectAbAttr, + PostAttackContactApplyStatusEffectAbAttr, + PostAttackApplyBattlerTagAbAttr, + PostDefendStealHeldItemAbAttr, + PostSetStatusAbAttr, + SynchronizeStatusAbAttr, + PostVictoryAbAttr, + PostVictoryFormChangeAbAttr, + PostKnockOutAbAttr, + PostKnockOutStatStageChangeAbAttr, + CopyFaintedAllyAbilityAbAttr, + IgnoreOpponentStatStagesAbAttr, + IntimidateImmunityAbAttr, + PostIntimidateStatStageChangeAbAttr, + PostSummonAbAttr, + PostSummonRemoveEffectAbAttr, + PostSummonRemoveArenaTagAbAttr, + PostSummonAddArenaTagAbAttr, + PostSummonMessageAbAttr, + PostSummonUnnamedMessageAbAttr, + PostSummonAddBattlerTagAbAttr, + PostSummonRemoveBattlerTagAbAttr, + PostSummonStatStageChangeAbAttr, + PostSummonAllyHealAbAttr, + PostSummonClearAllyStatStagesAbAttr, + DownloadAbAttr, + PostSummonWeatherChangeAbAttr, + PostSummonTerrainChangeAbAttr, + PostSummonHealStatusAbAttr, + PostSummonFormChangeAbAttr, + PostSummonCopyAbilityAbAttr, + PostSummonUserFieldRemoveStatusEffectAbAttr, + PostSummonCopyAllyStatsAbAttr, + PostSummonTransformAbAttr, + PostSummonWeatherSuppressedFormChangeAbAttr, + PostSummonFormChangeByWeatherAbAttr, + CommanderAbAttr, + PreSwitchOutAbAttr, + PreSwitchOutResetStatusAbAttr, + PreSwitchOutClearWeatherAbAttr, + PreSwitchOutHealAbAttr, + PreSwitchOutFormChangeAbAttr, + PreLeaveFieldAbAttr, + PreLeaveFieldClearWeatherAbAttr, + PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, + PreStatStageChangeAbAttr, + ReflectStatStageChangeAbAttr, + ProtectStatAbAttr, + ConfusionOnStatusEffectAbAttr, + PreSetStatusAbAttr, + PreSetStatusEffectImmunityAbAttr, + StatusEffectImmunityAbAttr, + UserFieldStatusEffectImmunityAbAttr, + ConditionalUserFieldStatusEffectImmunityAbAttr, + ConditionalUserFieldProtectStatAbAttr, + PreApplyBattlerTagAbAttr, + PreApplyBattlerTagImmunityAbAttr, + BattlerTagImmunityAbAttr, + UserFieldBattlerTagImmunityAbAttr, + ConditionalUserFieldBattlerTagImmunityAbAttr, + BlockCritAbAttr, + BonusCritAbAttr, + MultCritAbAttr, + ConditionalCritAbAttr, + BlockNonDirectDamageAbAttr, + BlockStatusDamageAbAttr, + BlockOneHitKOAbAttr, + ChangeMovePriorityAbAttr, + IgnoreContactAbAttr, + PreWeatherEffectAbAttr, + PreWeatherDamageAbAttr, + SuppressWeatherEffectAbAttr, + ForewarnAbAttr, + FriskAbAttr, + PostWeatherChangeAbAttr, + PostWeatherChangeFormChangeAbAttr, + PostWeatherLapseAbAttr, + PostWeatherLapseHealAbAttr, + PostWeatherLapseDamageAbAttr, + PostTerrainChangeAbAttr, + PostTurnAbAttr, + PostTurnStatusHealAbAttr, + PostTurnResetStatusAbAttr, + PostTurnRestoreBerryAbAttr, + RepeatBerryNextTurnAbAttr, + MoodyAbAttr, + SpeedBoostAbAttr, + PostTurnHealAbAttr, + PostTurnFormChangeAbAttr, + PostTurnHurtIfSleepingAbAttr, + FetchBallAbAttr, + PostBiomeChangeAbAttr, + PostBiomeChangeWeatherChangeAbAttr, + PostBiomeChangeTerrainChangeAbAttr, + PostMoveUsedAbAttr, + PostDancingMoveAbAttr, + PostItemLostAbAttr, + PostItemLostApplyBattlerTagAbAttr, + StatStageChangeMultiplierAbAttr, + StatStageChangeCopyAbAttr, + BypassBurnDamageReductionAbAttr, + ReduceBurnDamageAbAttr, + DoubleBerryEffectAbAttr, + PreventBerryUseAbAttr, + HealFromBerryUseAbAttr, + RunSuccessAbAttr, + CheckTrappedAbAttr, + ArenaTrapAbAttr, + MaxMultiHitAbAttr, + PostBattleAbAttr, + PostBattleLootAbAttr, + PostFaintAbAttr, + PostFaintUnsuppressedWeatherFormChangeAbAttr, + PostFaintContactDamageAbAttr, + PostFaintHPDamageAbAttr, + RedirectMoveAbAttr, + RedirectTypeMoveAbAttr, + BlockRedirectAbAttr, + ReduceStatusEffectDurationAbAttr, + FlinchEffectAbAttr, + FlinchStatStageChangeAbAttr, + IncreasePpAbAttr, + ForceSwitchOutImmunityAbAttr, + ReduceBerryUseThresholdAbAttr, + WeightMultiplierAbAttr, + SyncEncounterNatureAbAttr, + MoveAbilityBypassAbAttr, + AlwaysHitAbAttr, + IgnoreProtectOnContactAbAttr, + InfiltratorAbAttr, + ReflectStatusMoveAbAttr, + NoTransformAbilityAbAttr, + NoFusionAbilityAbAttr, + IgnoreTypeImmunityAbAttr, + IgnoreTypeStatusEffectImmunityAbAttr, + MoneyAbAttr, + PostSummonStatStageChangeOnArenaAbAttr, + FormBlockDamageAbAttr, + PreSummonAbAttr, + IllusionPreSummonAbAttr, + IllusionBreakAbAttr, + PostDefendIllusionBreakAbAttr, + IllusionPostBattleAbAttr, + BypassSpeedChanceAbAttr, + PreventBypassSpeedChanceAbAttr, + TerrainEventTypeChangeAbAttr, + PostDamageAbAttr, + PostDamageForceSwitchAbAttr, +}); /** - * Applies a field Stat multiplier attribute - * @param attrType {@linkcode FieldMultiplyStatAbAttr} should always be FieldMultiplyBattleStatAbAttr for the time being - * @param pokemon {@linkcode Pokemon} the Pokemon applying this ability - * @param stat {@linkcode Stat} the type of the checked stat - * @param statValue {@linkcode NumberHolder} the value of the checked stat - * @param checkedPokemon {@linkcode Pokemon} the Pokemon with the checked stat - * @param hasApplied {@linkcode BooleanHolder} whether or not a FieldMultiplyBattleStatAbAttr has already affected this stat - * @param args unused + * A map of of all {@linkcode AbAttr} constructors */ -export function applyFieldStatMultiplierAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - stat: Stat, - statValue: NumberHolder, - checkedPokemon: Pokemon, - hasApplied: BooleanHolder, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => - attr.applyFieldStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, hasApplied, args), - (attr, passive) => - attr.canApplyFieldStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, hasApplied, args), - args, - ); -} - -export function applyPreAttackAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - defender: Pokemon | null, - move: Move, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPreAttack(pokemon, passive, simulated, defender, move, args), - (attr, passive) => attr.canApplyPreAttack(pokemon, passive, simulated, defender, move, args), - args, - simulated, - ); -} - -export function applyExecutedMoveAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - attr => attr.applyExecutedMove(pokemon, simulated), - attr => attr.canApplyExecutedMove(pokemon, simulated), - args, - simulated, - ); -} - -export function applyPostAttackAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - defender: Pokemon, - move: Move, - hitResult: HitResult | null, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args), - (attr, passive) => attr.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args), - args, - simulated, - ); -} - -export function applyPostKnockOutAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - knockedOut: Pokemon, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPostKnockOut(pokemon, passive, simulated, knockedOut, args), - (attr, passive) => attr.canApplyPostKnockOut(pokemon, passive, simulated, knockedOut, args), - args, - simulated, - ); -} - -export function applyPostVictoryAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPostVictory(pokemon, passive, simulated, args), - (attr, passive) => attr.canApplyPostVictory(pokemon, passive, simulated, args), - args, - simulated, - ); -} - -export function applyPostSummonAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args), - (attr, passive) => attr.canApplyPostSummon(pokemon, passive, simulated, args), - args, - simulated, - ); -} - -export function applyPreSummonAbAttrs(attrType: Constructor, pokemon: Pokemon, ...args: any[]): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPreSummon(pokemon, passive, args), - (attr, passive) => attr.canApplyPreSummon(pokemon, passive, args), - args, - ); -} - -export function applyPreSwitchOutAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPreSwitchOut(pokemon, passive, simulated, args), - (attr, passive) => attr.canApplyPreSwitchOut(pokemon, passive, simulated, args), - args, - simulated, - ); -} - -export function applyPreLeaveFieldAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, args), - (attr, passive) => attr.canApplyPreLeaveField(pokemon, passive, simulated, args), - args, - simulated, - ); -} - -export function applyPreStatStageChangeAbAttrs( - attrType: Constructor, - pokemon: Pokemon | null, - stat: BattleStat, - cancelled: BooleanHolder, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args), - (attr, passive) => attr.canApplyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args), - args, - simulated, - ); -} - -export function applyPostStatStageChangeAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - stats: BattleStat[], - stages: number, - selfTarget: boolean, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, _passive) => attr.applyPostStatStageChange(pokemon, simulated, stats, stages, selfTarget, args), - (attr, _passive) => attr.canApplyPostStatStageChange(pokemon, simulated, stats, stages, selfTarget, args), - args, - simulated, - ); -} - -export function applyPreSetStatusAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - effect: StatusEffect | undefined, - cancelled: BooleanHolder, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args), - (attr, passive) => attr.canApplyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args), - args, - simulated, - ); -} - -export function applyPreApplyBattlerTagAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - tag: BattlerTag, - cancelled: BooleanHolder, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args), - (attr, passive) => attr.canApplyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args), - args, - simulated, - ); -} - -export function applyPreWeatherEffectAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - weather: Weather | null, - cancelled: BooleanHolder, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args), - (attr, passive) => attr.canApplyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args), - args, - simulated, - ); -} - -export function applyPostTurnAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPostTurn(pokemon, passive, simulated, args), - (attr, passive) => attr.canApplyPostTurn(pokemon, passive, simulated, args), - args, - simulated, - ); -} - -export function applyPostWeatherChangeAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - weather: WeatherType, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPostWeatherChange(pokemon, passive, simulated, weather, args), - (attr, passive) => attr.canApplyPostWeatherChange(pokemon, passive, simulated, weather, args), - args, - simulated, - ); -} - -export function applyPostWeatherLapseAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - weather: Weather | null, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPostWeatherLapse(pokemon, passive, simulated, weather, args), - (attr, passive) => attr.canApplyPostWeatherLapse(pokemon, passive, simulated, weather, args), - args, - simulated, - ); -} - -export function applyPostTerrainChangeAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - terrain: TerrainType, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPostTerrainChange(pokemon, passive, simulated, terrain, args), - (attr, passive) => attr.canApplyPostTerrainChange(pokemon, passive, simulated, terrain, args), - args, - simulated, - ); -} - -export function applyCheckTrappedAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - trapped: BooleanHolder, - otherPokemon: Pokemon, - messages: string[], - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args), - (attr, passive) => attr.canApplyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args), - args, - simulated, - messages, - ); -} - -export function applyPostBattleAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPostBattle(pokemon, passive, simulated, args), - (attr, passive) => attr.canApplyPostBattle(pokemon, passive, simulated, args), - args, - simulated, - ); -} - -export function applyPostFaintAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - attacker?: Pokemon, - move?: Move, - hitResult?: HitResult, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => attr.applyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args), - (attr, passive) => attr.canApplyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args), - args, - simulated, - ); -} - -export function applyPostItemLostAbAttrs( - attrType: Constructor, - pokemon: Pokemon, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, _passive) => attr.applyPostItemLost(pokemon, simulated, args), - (attr, _passive) => attr.canApplyPostItemLost(pokemon, simulated, args), - args, - ); -} - -/** - * Applies abilities when they become active mid-turn (ability switch) - * - * Ignores passives as they don't change and shouldn't be reapplied when main abilities change - */ -export function applyOnGainAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void { - applySingleAbAttrs( - pokemon, - passive, - PostSummonAbAttr, - (attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args), - (attr, passive) => attr.canApplyPostSummon(pokemon, passive, simulated, args), - args, - true, - simulated, - ); -} - -/** - * Applies ability attributes which activate when the ability is lost or suppressed (i.e. primal weather) - */ -export function applyOnLoseAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void { - applySingleAbAttrs( - pokemon, - passive, - PreLeaveFieldAbAttr, - (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [...args, true]), - (attr, passive) => attr.canApplyPreLeaveField(pokemon, passive, simulated, [...args, true]), - args, - true, - simulated, - ); - - applySingleAbAttrs( - pokemon, - passive, - IllusionBreakAbAttr, - (attr, passive) => attr.apply(pokemon, passive, simulated, null, args), - (attr, passive) => attr.canApply(pokemon, passive, simulated, args), - args, - true, - simulated, - ); -} +export type AbAttrConstructorMap = typeof AbilityAttrs; /** * Sets the ability of a Pokémon as revealed. diff --git a/src/data/abilities/apply-ab-attrs.ts b/src/data/abilities/apply-ab-attrs.ts new file mode 100644 index 00000000000..e2f8ec9c14c --- /dev/null +++ b/src/data/abilities/apply-ab-attrs.ts @@ -0,0 +1,829 @@ +import type { AbAttrApplyFunc, AbAttrMap, AbAttrString, AbAttrSuccessFunc } from "#app/@types/ability-types"; +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import type { BooleanHolder, NumberHolder } from "#app/utils/common"; +import type { BattlerIndex } from "#enums/battler-index"; +import type { HitResult } from "#enums/hit-result"; +import type { BattleStat, Stat } from "#enums/stat"; +import type { StatusEffect } from "#enums/status-effect"; +import type { WeatherType } from "#enums/weather-type"; +import type { BattlerTag } from "../battler-tags"; +import type Move from "../moves/move"; +import type { PokemonMove } from "../moves/pokemon-move"; +import type { TerrainType } from "../terrain"; +import type { Weather } from "../weather"; +import type { + PostBattleInitAbAttr, + PreDefendAbAttr, + PostDefendAbAttr, + PostMoveUsedAbAttr, + StatMultiplierAbAttr, + AllyStatMultiplierAbAttr, + PostSetStatusAbAttr, + PostDamageAbAttr, + FieldMultiplyStatAbAttr, + PreAttackAbAttr, + ExecutedMoveAbAttr, + PostAttackAbAttr, + PostKnockOutAbAttr, + PostVictoryAbAttr, + PostSummonAbAttr, + PreSummonAbAttr, + PreSwitchOutAbAttr, + PreLeaveFieldAbAttr, + PreStatStageChangeAbAttr, + PostStatStageChangeAbAttr, + PreSetStatusAbAttr, + PreApplyBattlerTagAbAttr, + PreWeatherEffectAbAttr, + PreWeatherDamageAbAttr, + PostTurnAbAttr, + PostWeatherChangeAbAttr, + PostWeatherLapseAbAttr, + PostTerrainChangeAbAttr, + CheckTrappedAbAttr, + PostBattleAbAttr, + PostFaintAbAttr, + PostItemLostAbAttr, +} from "./ability"; + +function applySingleAbAttrs( + pokemon: Pokemon, + passive: boolean, + attrType: T, + applyFunc: AbAttrApplyFunc, + successFunc: AbAttrSuccessFunc, + args: any[], + gainedMidTurn = false, + simulated = false, + messages: string[] = [], +) { + if (!pokemon?.canApplyAbility(passive) || (passive && pokemon.getPassiveAbility().id === pokemon.getAbility().id)) { + return; + } + + const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility(); + if ( + gainedMidTurn && + ability.getAttrs(attrType).some(attr => { + attr.is("PostSummonAbAttr") && !attr.shouldActivateOnGain(); + }) + ) { + return; + } + + for (const attr of ability.getAttrs(attrType)) { + const condition = attr.getCondition(); + let abShown = false; + if ((condition && !condition(pokemon)) || !successFunc(attr, passive)) { + continue; + } + + globalScene.phaseManager.setPhaseQueueSplice(); + + if (attr.showAbility && !simulated) { + globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true); + abShown = true; + } + const message = attr.getTriggerMessage(pokemon, ability.name, args); + if (message) { + if (!simulated) { + globalScene.phaseManager.queueMessage(message); + } + messages.push(message); + } + + applyFunc(attr, passive); + + if (abShown) { + globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false); + } + + if (!simulated) { + pokemon.waveData.abilitiesApplied.add(ability.id); + } + + globalScene.phaseManager.clearPhaseQueueSplice(); + } +} + +function applyAbAttrsInternal( + attrType: T, + pokemon: Pokemon | null, + applyFunc: AbAttrApplyFunc, + successFunc: AbAttrSuccessFunc, + args: any[], + simulated = false, + messages: string[] = [], + gainedMidTurn = false, +) { + for (const passive of [false, true]) { + if (pokemon) { + applySingleAbAttrs(pokemon, passive, attrType, applyFunc, successFunc, args, gainedMidTurn, simulated, messages); + globalScene.phaseManager.clearPhaseQueueSplice(); + } + } +} + +export function applyAbAttrs( + attrType: T, + pokemon: Pokemon, + cancelled: BooleanHolder | null, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + // @ts-expect-error: TODO: fix the error on `cancelled` + (attr, passive) => attr.apply(pokemon, passive, simulated, cancelled, args), + (attr, passive) => attr.canApply(pokemon, passive, simulated, args), + args, + simulated, + ); +} + +// TODO: Improve the type signatures of the following methods / refactor the apply methods + +export function applyPostBattleInitAbAttrs( + attrType: AbAttrMap[K] extends PostBattleInitAbAttr ? K : never, + pokemon: Pokemon, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => (attr as PostBattleInitAbAttr).applyPostBattleInit(pokemon, passive, simulated, args), + (attr, passive) => (attr as PostBattleInitAbAttr).canApplyPostBattleInit(pokemon, passive, simulated, args), + args, + simulated, + ); +} + +export function applyPreDefendAbAttrs( + attrType: AbAttrMap[K] extends PreDefendAbAttr ? K : never, + pokemon: Pokemon, + attacker: Pokemon, + move: Move | null, + cancelled: BooleanHolder | null, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => + (attr as PreDefendAbAttr).applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args), + (attr, passive) => + (attr as PreDefendAbAttr).canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args), + args, + simulated, + ); +} + +export function applyPostDefendAbAttrs( + attrType: AbAttrMap[K] extends PostDefendAbAttr ? K : never, + pokemon: Pokemon, + attacker: Pokemon, + move: Move, + hitResult: HitResult | null, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => + (attr as PostDefendAbAttr).applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args), + (attr, passive) => + (attr as PostDefendAbAttr).canApplyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args), + args, + simulated, + ); +} + +export function applyPostMoveUsedAbAttrs( + attrType: AbAttrMap[K] extends PostMoveUsedAbAttr ? K : never, + pokemon: Pokemon, + move: PokemonMove, + source: Pokemon, + targets: BattlerIndex[], + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, _passive) => (attr as PostMoveUsedAbAttr).applyPostMoveUsed(pokemon, move, source, targets, simulated, args), + (attr, _passive) => + (attr as PostMoveUsedAbAttr).canApplyPostMoveUsed(pokemon, move, source, targets, simulated, args), + args, + simulated, + ); +} + +export function applyStatMultiplierAbAttrs( + attrType: AbAttrMap[K] extends StatMultiplierAbAttr ? K : never, + pokemon: Pokemon, + stat: BattleStat, + statValue: NumberHolder, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => + (attr as StatMultiplierAbAttr).applyStatStage(pokemon, passive, simulated, stat, statValue, args), + (attr, passive) => + (attr as StatMultiplierAbAttr).canApplyStatStage(pokemon, passive, simulated, stat, statValue, args), + args, + ); +} + +/** + * Applies an ally's Stat multiplier attribute + * @param attrType - {@linkcode AllyStatMultiplierAbAttr} should always be AllyStatMultiplierAbAttr for the time being + * @param pokemon - The {@linkcode Pokemon} with the ability + * @param stat - The type of the checked {@linkcode Stat} + * @param statValue - {@linkcode NumberHolder} containing the value of the checked stat + * @param checkedPokemon - The {@linkcode Pokemon} with the checked stat + * @param ignoreAbility - Whether or not the ability should be ignored by the pokemon or its move. + * @param args - unused + */ +export function applyAllyStatMultiplierAbAttrs( + attrType: AbAttrMap[K] extends AllyStatMultiplierAbAttr ? K : never, + pokemon: Pokemon, + stat: BattleStat, + statValue: NumberHolder, + simulated = false, + checkedPokemon: Pokemon, + ignoreAbility: boolean, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => + (attr as AllyStatMultiplierAbAttr).applyAllyStat( + pokemon, + passive, + simulated, + stat, + statValue, + checkedPokemon, + ignoreAbility, + args, + ), + (attr, passive) => + (attr as AllyStatMultiplierAbAttr).canApplyAllyStat( + pokemon, + passive, + simulated, + stat, + statValue, + checkedPokemon, + ignoreAbility, + args, + ), + args, + simulated, + ); +} + +export function applyPostSetStatusAbAttrs( + attrType: AbAttrMap[K] extends PostSetStatusAbAttr ? K : never, + pokemon: Pokemon, + effect: StatusEffect, + sourcePokemon?: Pokemon | null, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => + (attr as PostSetStatusAbAttr).applyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args), + (attr, passive) => + (attr as PostSetStatusAbAttr).canApplyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args), + args, + simulated, + ); +} + +export function applyPostDamageAbAttrs( + attrType: AbAttrMap[K] extends PostDamageAbAttr ? K : never, + pokemon: Pokemon, + damage: number, + _passive: boolean, + simulated = false, + args: any[], + source?: Pokemon, +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => (attr as PostDamageAbAttr).applyPostDamage(pokemon, damage, passive, simulated, args, source), + (attr, passive) => (attr as PostDamageAbAttr).canApplyPostDamage(pokemon, damage, passive, simulated, args, source), + args, + ); +} +/** + * Applies a field Stat multiplier attribute + * @param attrType {@linkcode FieldMultiplyStatAbAttr} should always be FieldMultiplyBattleStatAbAttr for the time being + * @param pokemon {@linkcode Pokemon} the Pokemon applying this ability + * @param stat {@linkcode Stat} the type of the checked stat + * @param statValue {@linkcode NumberHolder} the value of the checked stat + * @param checkedPokemon {@linkcode Pokemon} the Pokemon with the checked stat + * @param hasApplied {@linkcode BooleanHolder} whether or not a FieldMultiplyBattleStatAbAttr has already affected this stat + * @param args unused + */ + +export function applyFieldStatMultiplierAbAttrs( + attrType: AbAttrMap[K] extends FieldMultiplyStatAbAttr ? K : never, + pokemon: Pokemon, + stat: Stat, + statValue: NumberHolder, + checkedPokemon: Pokemon, + hasApplied: BooleanHolder, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => + (attr as FieldMultiplyStatAbAttr).applyFieldStat( + pokemon, + passive, + simulated, + stat, + statValue, + checkedPokemon, + hasApplied, + args, + ), + (attr, passive) => + (attr as FieldMultiplyStatAbAttr).canApplyFieldStat( + pokemon, + passive, + simulated, + stat, + statValue, + checkedPokemon, + hasApplied, + args, + ), + args, + ); +} + +export function applyPreAttackAbAttrs( + attrType: AbAttrMap[K] extends PreAttackAbAttr ? K : never, + pokemon: Pokemon, + defender: Pokemon | null, + move: Move, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => (attr as PreAttackAbAttr).applyPreAttack(pokemon, passive, simulated, defender, move, args), + (attr, passive) => (attr as PreAttackAbAttr).canApplyPreAttack(pokemon, passive, simulated, defender, move, args), + args, + simulated, + ); +} + +export function applyExecutedMoveAbAttrs( + attrType: AbAttrMap[K] extends ExecutedMoveAbAttr ? K : never, + pokemon: Pokemon, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + attr => (attr as ExecutedMoveAbAttr).applyExecutedMove(pokemon, simulated), + attr => (attr as ExecutedMoveAbAttr).canApplyExecutedMove(pokemon, simulated), + args, + simulated, + ); +} + +export function applyPostAttackAbAttrs( + attrType: AbAttrMap[K] extends PostAttackAbAttr ? K : never, + pokemon: Pokemon, + defender: Pokemon, + move: Move, + hitResult: HitResult | null, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => + (attr as PostAttackAbAttr).applyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args), + (attr, passive) => + (attr as PostAttackAbAttr).canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args), + args, + simulated, + ); +} + +export function applyPostKnockOutAbAttrs( + attrType: AbAttrMap[K] extends PostKnockOutAbAttr ? K : never, + pokemon: Pokemon, + knockedOut: Pokemon, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => (attr as PostKnockOutAbAttr).applyPostKnockOut(pokemon, passive, simulated, knockedOut, args), + (attr, passive) => (attr as PostKnockOutAbAttr).canApplyPostKnockOut(pokemon, passive, simulated, knockedOut, args), + args, + simulated, + ); +} + +export function applyPostVictoryAbAttrs( + attrType: AbAttrMap[K] extends PostVictoryAbAttr ? K : never, + pokemon: Pokemon, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => (attr as PostVictoryAbAttr).applyPostVictory(pokemon, passive, simulated, args), + (attr, passive) => (attr as PostVictoryAbAttr).canApplyPostVictory(pokemon, passive, simulated, args), + args, + simulated, + ); +} + +export function applyPostSummonAbAttrs( + attrType: AbAttrMap[K] extends PostSummonAbAttr ? K : never, + pokemon: Pokemon, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => (attr as PostSummonAbAttr).applyPostSummon(pokemon, passive, simulated, args), + (attr, passive) => (attr as PostSummonAbAttr).canApplyPostSummon(pokemon, passive, simulated, args), + args, + simulated, + ); +} + +export function applyPreSummonAbAttrs( + attrType: AbAttrMap[K] extends PreSummonAbAttr ? K : never, + pokemon: Pokemon, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => (attr as PreSummonAbAttr).applyPreSummon(pokemon, passive, args), + (attr, passive) => (attr as PreSummonAbAttr).canApplyPreSummon(pokemon, passive, args), + args, + ); +} + +export function applyPreSwitchOutAbAttrs( + attrType: AbAttrMap[K] extends PreSwitchOutAbAttr ? K : never, + pokemon: Pokemon, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => (attr as PreSwitchOutAbAttr).applyPreSwitchOut(pokemon, passive, simulated, args), + (attr, passive) => (attr as PreSwitchOutAbAttr).canApplyPreSwitchOut(pokemon, passive, simulated, args), + args, + simulated, + ); +} + +export function applyPreLeaveFieldAbAttrs( + attrType: AbAttrMap[K] extends PreLeaveFieldAbAttr ? K : never, + pokemon: Pokemon, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => (attr as PreLeaveFieldAbAttr).applyPreLeaveField(pokemon, passive, simulated, args), + (attr, passive) => (attr as PreLeaveFieldAbAttr).canApplyPreLeaveField(pokemon, passive, simulated, args), + args, + simulated, + ); +} + +export function applyPreStatStageChangeAbAttrs( + attrType: AbAttrMap[K] extends PreStatStageChangeAbAttr ? K : never, + pokemon: Pokemon | null, + stat: BattleStat, + cancelled: BooleanHolder, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => + (attr as PreStatStageChangeAbAttr).applyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args), + (attr, passive) => + (attr as PreStatStageChangeAbAttr).canApplyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args), + args, + simulated, + ); +} + +export function applyPostStatStageChangeAbAttrs( + attrType: AbAttrMap[K] extends PostStatStageChangeAbAttr ? K : never, + pokemon: Pokemon, + stats: BattleStat[], + stages: number, + selfTarget: boolean, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, _passive) => + (attr as PostStatStageChangeAbAttr).applyPostStatStageChange(pokemon, simulated, stats, stages, selfTarget, args), + (attr, _passive) => + (attr as PostStatStageChangeAbAttr).canApplyPostStatStageChange( + pokemon, + simulated, + stats, + stages, + selfTarget, + args, + ), + args, + simulated, + ); +} + +export function applyPreSetStatusAbAttrs( + attrType: AbAttrMap[K] extends PreSetStatusAbAttr ? K : never, + pokemon: Pokemon, + effect: StatusEffect | undefined, + cancelled: BooleanHolder, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => + (attr as PreSetStatusAbAttr).applyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args), + (attr, passive) => + (attr as PreSetStatusAbAttr).canApplyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args), + args, + simulated, + ); +} + +export function applyPreApplyBattlerTagAbAttrs( + attrType: AbAttrMap[K] extends PreApplyBattlerTagAbAttr ? K : never, + pokemon: Pokemon, + tag: BattlerTag, + cancelled: BooleanHolder, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => + (attr as PreApplyBattlerTagAbAttr).applyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args), + (attr, passive) => + (attr as PreApplyBattlerTagAbAttr).canApplyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args), + args, + simulated, + ); +} + +export function applyPreWeatherEffectAbAttrs( + attrType: AbAttrMap[K] extends PreWeatherEffectAbAttr ? K : never, + pokemon: Pokemon, + weather: Weather | null, + cancelled: BooleanHolder, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => + (attr as PreWeatherDamageAbAttr).applyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args), + (attr, passive) => + (attr as PreWeatherDamageAbAttr).canApplyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args), + args, + simulated, + ); +} + +export function applyPostTurnAbAttrs( + attrType: AbAttrMap[K] extends PostTurnAbAttr ? K : never, + pokemon: Pokemon, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => (attr as PostTurnAbAttr).applyPostTurn(pokemon, passive, simulated, args), + (attr, passive) => (attr as PostTurnAbAttr).canApplyPostTurn(pokemon, passive, simulated, args), + args, + simulated, + ); +} + +export function applyPostWeatherChangeAbAttrs( + attrType: AbAttrMap[K] extends PostWeatherChangeAbAttr ? K : never, + pokemon: Pokemon, + weather: WeatherType, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => + (attr as PostWeatherChangeAbAttr).applyPostWeatherChange(pokemon, passive, simulated, weather, args), + (attr, passive) => + (attr as PostWeatherChangeAbAttr).canApplyPostWeatherChange(pokemon, passive, simulated, weather, args), + args, + simulated, + ); +} + +export function applyPostWeatherLapseAbAttrs( + attrType: AbAttrMap[K] extends PostWeatherLapseAbAttr ? K : never, + pokemon: Pokemon, + weather: Weather | null, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => + (attr as PostWeatherLapseAbAttr).applyPostWeatherLapse(pokemon, passive, simulated, weather, args), + (attr, passive) => + (attr as PostWeatherLapseAbAttr).canApplyPostWeatherLapse(pokemon, passive, simulated, weather, args), + args, + simulated, + ); +} + +export function applyPostTerrainChangeAbAttrs( + attrType: AbAttrMap[K] extends PostTerrainChangeAbAttr ? K : never, + pokemon: Pokemon, + terrain: TerrainType, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => + (attr as PostTerrainChangeAbAttr).applyPostTerrainChange(pokemon, passive, simulated, terrain, args), + (attr, passive) => + (attr as PostTerrainChangeAbAttr).canApplyPostTerrainChange(pokemon, passive, simulated, terrain, args), + args, + simulated, + ); +} + +export function applyCheckTrappedAbAttrs( + attrType: AbAttrMap[K] extends CheckTrappedAbAttr ? K : never, + pokemon: Pokemon, + trapped: BooleanHolder, + otherPokemon: Pokemon, + messages: string[], + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => + (attr as CheckTrappedAbAttr).applyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args), + (attr, passive) => + (attr as CheckTrappedAbAttr).canApplyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args), + args, + simulated, + messages, + ); +} + +export function applyPostBattleAbAttrs( + attrType: AbAttrMap[K] extends PostBattleAbAttr ? K : never, + pokemon: Pokemon, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => (attr as PostBattleAbAttr).applyPostBattle(pokemon, passive, simulated, args), + (attr, passive) => (attr as PostBattleAbAttr).canApplyPostBattle(pokemon, passive, simulated, args), + args, + simulated, + ); +} + +export function applyPostFaintAbAttrs( + attrType: AbAttrMap[K] extends PostFaintAbAttr ? K : never, + pokemon: Pokemon, + attacker?: Pokemon, + move?: Move, + hitResult?: HitResult, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => + (attr as PostFaintAbAttr).applyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args), + (attr, passive) => + (attr as PostFaintAbAttr).canApplyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args), + args, + simulated, + ); +} + +export function applyPostItemLostAbAttrs( + attrType: AbAttrMap[K] extends PostItemLostAbAttr ? K : never, + pokemon: Pokemon, + simulated = false, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, _passive) => (attr as PostItemLostAbAttr).applyPostItemLost(pokemon, simulated, args), + (attr, _passive) => (attr as PostItemLostAbAttr).canApplyPostItemLost(pokemon, simulated, args), + args, + ); +} + +/** + * Applies abilities when they become active mid-turn (ability switch) + * + * Ignores passives as they don't change and shouldn't be reapplied when main abilities change + */ +export function applyOnGainAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void { + applySingleAbAttrs( + pokemon, + passive, + "PostSummonAbAttr", + (attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args), + (attr, passive) => attr.canApplyPostSummon(pokemon, passive, simulated, args), + args, + true, + simulated, + ); +} +/** + * Applies ability attributes which activate when the ability is lost or suppressed (i.e. primal weather) + */ +export function applyOnLoseAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void { + applySingleAbAttrs( + pokemon, + passive, + "PreLeaveFieldAbAttr", + (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [...args, true]), + (attr, passive) => attr.canApplyPreLeaveField(pokemon, passive, simulated, [...args, true]), + args, + true, + simulated, + ); + + applySingleAbAttrs( + pokemon, + passive, + "IllusionBreakAbAttr", + (attr, passive) => attr.apply(pokemon, passive, simulated, null, args), + (attr, passive) => attr.canApply(pokemon, passive, simulated, args), + args, + true, + simulated, + ); +} diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index e18ee5ac556..da1bbbda2e9 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -10,15 +10,7 @@ import type Pokemon from "#app/field/pokemon"; import { HitResult } from "#enums/hit-result"; import { StatusEffect } from "#enums/status-effect"; import type { BattlerIndex } from "#enums/battler-index"; -import { - BlockNonDirectDamageAbAttr, - InfiltratorAbAttr, - PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, - ProtectStatAbAttr, - applyAbAttrs, - applyOnGainAbAttrs, - applyOnLoseAbAttrs, -} from "#app/data/abilities/ability"; +import { applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "./abilities/apply-ab-attrs"; import { Stat } from "#enums/stat"; import { CommonBattleAnim } from "#app/data/battle-anims"; import { CommonAnim } from "#enums/move-anims-common"; @@ -144,7 +136,7 @@ export class MistTag extends ArenaTag { if (attacker) { const bypassed = new BooleanHolder(false); // TODO: Allow this to be simulated - applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed); + applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed); if (bypassed.value) { return false; } @@ -209,7 +201,7 @@ export class WeakenMoveScreenTag extends ArenaTag { ): boolean { if (this.weakenedCategories.includes(moveCategory)) { const bypassed = new BooleanHolder(false); - applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed); + applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed); if (bypassed.value) { return false; } @@ -765,7 +757,7 @@ class SpikesTag extends ArenaTrapTag { } const cancelled = new BooleanHolder(false); - applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled); if (simulated || cancelled.value) { return !cancelled.value; } @@ -953,7 +945,7 @@ class StealthRockTag extends ArenaTrapTag { override activateTrap(pokemon: Pokemon, simulated: boolean): boolean { const cancelled = new BooleanHolder(false); - applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled); if (cancelled.value) { return false; } @@ -1010,7 +1002,7 @@ class StickyWebTag extends ArenaTrapTag { override activateTrap(pokemon: Pokemon, simulated: boolean): boolean { if (pokemon.isGrounded()) { const cancelled = new BooleanHolder(false); - applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled); + applyAbAttrs("ProtectStatAbAttr", pokemon, cancelled); if (simulated) { return !cancelled.value; @@ -1444,8 +1436,8 @@ export class SuppressAbilitiesTag extends ArenaTag { // Could have a custom message that plays when a specific pokemon's NG ends? This entire thing exists due to passives after all const setter = globalScene .getField() - .filter(p => p?.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false))[0]; - applyOnGainAbAttrs(setter, setter.getAbility().hasAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr)); + .filter(p => p?.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false))[0]; + applyOnGainAbAttrs(setter, setter.getAbility().hasAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr")); } } @@ -1457,7 +1449,7 @@ export class SuppressAbilitiesTag extends ArenaTag { for (const pokemon of globalScene.getField(true)) { // There is only one pokemon with this attr on the field on removal, so its abilities are already active - if (pokemon && !pokemon.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false)) { + if (pokemon && !pokemon.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false)) { [true, false].forEach(passive => applyOnGainAbAttrs(pokemon, passive)); } } diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index 298cf2d0719..d307f5cfbf6 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -11,7 +11,6 @@ import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { SpeciesFormKey } from "#enums/species-form-key"; import { TimeOfDay } from "#enums/time-of-day"; -import { DamageMoneyRewardModifier, ExtraModifierModifier, MoneyMultiplierModifier, SpeciesStatBoosterModifier, TempExtraModifierModifier } from "#app/modifier/modifier"; import type { SpeciesStatBoosterModifierType } from "#app/modifier/modifier-type"; import { speciesStarterCosts } from "./starters"; import i18next from "i18next"; @@ -275,9 +274,9 @@ class MoveTypeEvolutionCondition extends SpeciesEvolutionCondition { class TreasureEvolutionCondition extends SpeciesEvolutionCondition { constructor() { super(p => p.evoCounter - + p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length - + globalScene.findModifiers(m => m instanceof MoneyMultiplierModifier - || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length > 9); + + p.getHeldItems().filter(m => m.is("DamageMoneyRewardModifier")).length + + globalScene.findModifiers(m => m.is("MoneyMultiplierModifier") + || m.is("ExtraModifierModifier") || m.is("TempExtraModifierModifier")).length > 9); this.description = i18next.t("pokemonEvolutions:treasure"); } } @@ -1794,8 +1793,8 @@ export const pokemonEvolutions: PokemonEvolutions = { ], [SpeciesId.CLAMPERL]: [ // TODO: Change the SpeciesEvolutionConditions here to use a bespoke HeldItemEvolutionCondition after the modifier rework - new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.getHeldItems().some(m => m instanceof SpeciesStatBoosterModifier && (m.type as SpeciesStatBoosterModifierType).key === "DEEP_SEA_TOOTH")), SpeciesWildEvolutionDelay.VERY_LONG), - new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.getHeldItems().some(m => m instanceof SpeciesStatBoosterModifier && (m.type as SpeciesStatBoosterModifierType).key === "DEEP_SEA_SCALE")), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.getHeldItems().some(m => m.is("SpeciesStatBoosterModifier") && (m.type as SpeciesStatBoosterModifierType).key === "DEEP_SEA_TOOTH")), SpeciesWildEvolutionDelay.VERY_LONG), + new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.getHeldItems().some(m => m.is("SpeciesStatBoosterModifier") && (m.type as SpeciesStatBoosterModifierType).key === "DEEP_SEA_SCALE")), SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.BOLDORE]: [ new SpeciesEvolution(SpeciesId.GIGALITH, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) diff --git a/src/data/balance/tms.ts b/src/data/balance/tms.ts index 2b0e8f5142d..e194dc4040c 100644 --- a/src/data/balance/tms.ts +++ b/src/data/balance/tms.ts @@ -1,4 +1,4 @@ -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index 0b40469b255..7aa419ca470 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -10,6 +10,7 @@ import { isNullOrUndefined } from "../utils/common"; import Phaser from "phaser"; import { EncounterAnim } from "#enums/encounter-anims"; import { AnimBlendType, AnimFrameTarget, AnimFocus, ChargeAnim, CommonAnim } from "#enums/move-anims-common"; +import { BattlerTagType } from "#enums/battler-tag-type"; export class AnimConfig { public id: number; @@ -770,7 +771,7 @@ export abstract class BattleAnim { const user = !isOppAnim ? this.user : this.target; const target = !isOppAnim ? this.target : this.user; - const targetSubstitute = onSubstitute && user !== target ? target!.getTag(SubstituteTag) : null; + const targetSubstitute = onSubstitute && user !== target ? target!.getTag(BattlerTagType.SUBSTITUTE) : null; const userInitialX = user!.x; // TODO: is this bang correct? const userInitialY = user!.y; // TODO: is this bang correct? diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index ffa179c6aab..0daf1913737 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1,13 +1,6 @@ import { globalScene } from "#app/global-scene"; import Overrides from "#app/overrides"; -import { - applyAbAttrs, - BlockNonDirectDamageAbAttr, - FlinchEffectAbAttr, - ProtectStatAbAttr, - ConditionalUserFieldProtectStatAbAttr, - ReverseDrainAbAttr, -} from "#app/data/abilities/ability"; +import { applyAbAttrs } from "./abilities/apply-ab-attrs"; import { allAbilities } from "./data-lists"; import { CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims"; import { ChargeAnim, CommonAnim } from "#enums/move-anims-common"; @@ -648,7 +641,7 @@ export class FlinchedTag extends BattlerTag { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), ); - applyAbAttrs(FlinchEffectAbAttr, pokemon, null); + applyAbAttrs("FlinchEffectAbAttr", pokemon, null); return true; } @@ -942,7 +935,7 @@ export class SeedTag extends BattlerTag { const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex); if (source) { const cancelled = new BooleanHolder(false); - applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled); if (!cancelled.value) { globalScene.phaseManager.unshiftNew( @@ -953,7 +946,7 @@ export class SeedTag extends BattlerTag { ); const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT }); - const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false); + const reverseDrain = pokemon.hasAbilityWithAttr("ReverseDrainAbAttr", false); globalScene.phaseManager.unshiftNew( "PokemonHealPhase", source.getBattlerIndex(), @@ -1026,7 +1019,7 @@ export class PowderTag extends BattlerTag { globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER); const cancelDamage = new BooleanHolder(false); - applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelDamage); + applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelDamage); if (!cancelDamage.value) { pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT }); } @@ -1079,7 +1072,7 @@ export class NightmareTag extends BattlerTag { phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE); // TODO: Update animation type const cancelled = new BooleanHolder(false); - applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled); if (!cancelled.value) { pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT }); @@ -1438,7 +1431,7 @@ export abstract class DamagingTrapTag extends TrappedTag { phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, this.commonAnim); const cancelled = new BooleanHolder(false); - applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled); if (!cancelled.value) { pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT }); @@ -1681,7 +1674,7 @@ export class ContactDamageProtectedTag extends ContactProtectedTag { */ override onContact(attacker: Pokemon, user: Pokemon): void { const cancelled = new BooleanHolder(false); - applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); + applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled); if (!cancelled.value) { attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), { result: HitResult.INDIRECT, @@ -2277,7 +2270,7 @@ export class SaltCuredTag extends BattlerTag { ); const cancelled = new BooleanHolder(false); - applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled); if (!cancelled.value) { const pokemonSteelOrWater = pokemon.isOfType(PokemonType.STEEL) || pokemon.isOfType(PokemonType.WATER); @@ -2331,7 +2324,7 @@ export class CursedTag extends BattlerTag { ); const cancelled = new BooleanHolder(false); - applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled); if (!cancelled.value) { pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT }); @@ -2666,7 +2659,7 @@ export class GulpMissileTag extends BattlerTag { } const cancelled = new BooleanHolder(false); - applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled); + applyAbAttrs("BlockNonDirectDamageAbAttr", attacker, cancelled); if (!cancelled.value) { attacker.damageAndUpdate(Math.max(1, Math.floor(attacker.getMaxHp() / 4)), { result: HitResult.INDIRECT }); @@ -3056,8 +3049,8 @@ export class MysteryEncounterPostSummonTag extends BattlerTag { if (lapseType === BattlerTagLapseType.CUSTOM) { const cancelled = new BooleanHolder(false); - applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled); - applyAbAttrs(ConditionalUserFieldProtectStatAbAttr, pokemon, cancelled, false, pokemon); + applyAbAttrs("ProtectStatAbAttr", pokemon, cancelled); + applyAbAttrs("ConditionalUserFieldProtectStatAbAttr", pokemon, cancelled, false, pokemon); if (!cancelled.value) { if (pokemon.mysteryEncounterBattleEffects) { pokemon.mysteryEncounterBattleEffects(pokemon); diff --git a/src/data/berry.ts b/src/data/berry.ts index df500fa0609..7d1e62362a8 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -3,7 +3,7 @@ import type Pokemon from "../field/pokemon"; import { HitResult } from "#enums/hit-result"; import { getStatusEffectHealText } from "./status-effect"; import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils/common"; -import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./abilities/ability"; +import { applyAbAttrs } from "./abilities/apply-ab-attrs"; import i18next from "i18next"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; @@ -38,25 +38,25 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate { const threshold = new NumberHolder(0.25); // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth const stat: BattleStat = berryType - BerryType.ENIGMA; - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); + applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold); return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6; }; case BerryType.LANSAT: return (pokemon: Pokemon) => { const threshold = new NumberHolder(0.25); - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); + applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold); return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST); }; case BerryType.STARF: return (pokemon: Pokemon) => { const threshold = new NumberHolder(0.25); - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); + applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold); return pokemon.getHpRatio() < 0.25; }; case BerryType.LEPPA: return (pokemon: Pokemon) => { const threshold = new NumberHolder(0.25); - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); + applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold); return !!pokemon.getMoveset().find(m => !m.getPpRatio()); }; } @@ -72,7 +72,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { case BerryType.ENIGMA: { const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4)); - applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, hpHealed); + applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, hpHealed); globalScene.phaseManager.unshiftNew( "PokemonHealPhase", consumer.getBattlerIndex(), @@ -105,7 +105,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { // Offset BerryType such that LIECHI --> Stat.ATK = 1, GANLON --> Stat.DEF = 2, etc etc. const stat: BattleStat = berryType - BerryType.ENIGMA; const statStages = new NumberHolder(1); - applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, statStages); + applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, statStages); globalScene.phaseManager.unshiftNew( "StatStageChangePhase", consumer.getBattlerIndex(), @@ -126,7 +126,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { { const randStat = randSeedInt(Stat.SPD, Stat.ATK); const stages = new NumberHolder(2); - applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, stages); + applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, stages); globalScene.phaseManager.unshiftNew( "StatStageChangePhase", consumer.getBattlerIndex(), diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 8bdccb6d5fd..683fb48a9ba 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -21,7 +21,7 @@ import { TrainerType } from "#enums/trainer-type"; import { Nature } from "#enums/nature"; import type { MoveId } from "#enums/move-id"; import { TypeColor, TypeShadow } from "#enums/color"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { globalScene } from "#app/global-scene"; import { pokemonFormChanges } from "./pokemon-forms"; import { pokemonEvolutions } from "./balance/pokemon-evolutions"; diff --git a/src/data/data-lists.ts b/src/data/data-lists.ts index c763a001280..4d482dc2d7d 100644 --- a/src/data/data-lists.ts +++ b/src/data/data-lists.ts @@ -1,5 +1,9 @@ -import type { Ability } from "./abilities/ability-class"; +import type { ModifierTypes } from "#app/modifier/modifier-type"; +import type { Ability } from "./abilities/ability"; import type Move from "./moves/move"; export const allAbilities: Ability[] = []; export const allMoves: Move[] = []; + +// TODO: Figure out what this is used for and provide an appropriate tsdoc comment +export const modifierTypes = {} as ModifierTypes; diff --git a/src/data/dialogue.ts b/src/data/dialogue.ts index fa640e92b00..6bb96f0efb2 100644 --- a/src/data/dialogue.ts +++ b/src/data/dialogue.ts @@ -1723,49 +1723,6 @@ export const trainerTypeDialogue: TrainerTypeDialogue = { ], }; -export const doubleBattleDialogue = { - blue_red_double: { - encounter: ["doubleBattleDialogue:blue_red_double.encounter.1"], - victory: ["doubleBattleDialogue:blue_red_double.victory.1"], - }, - red_blue_double: { - encounter: ["doubleBattleDialogue:red_blue_double.encounter.1"], - victory: ["doubleBattleDialogue:red_blue_double.victory.1"], - }, - tate_liza_double: { - encounter: ["doubleBattleDialogue:tate_liza_double.encounter.1"], - victory: ["doubleBattleDialogue:tate_liza_double.victory.1"], - }, - liza_tate_double: { - encounter: ["doubleBattleDialogue:liza_tate_double.encounter.1"], - victory: ["doubleBattleDialogue:liza_tate_double.victory.1"], - }, - wallace_steven_double: { - encounter: ["doubleBattleDialogue:wallace_steven_double.encounter.1"], - victory: ["doubleBattleDialogue:wallace_steven_double.victory.1"], - }, - steven_wallace_double: { - encounter: ["doubleBattleDialogue:steven_wallace_double.encounter.1"], - victory: ["doubleBattleDialogue:steven_wallace_double.victory.1"], - }, - alder_iris_double: { - encounter: ["doubleBattleDialogue:alder_iris_double.encounter.1"], - victory: ["doubleBattleDialogue:alder_iris_double.victory.1"], - }, - iris_alder_double: { - encounter: ["doubleBattleDialogue:iris_alder_double.encounter.1"], - victory: ["doubleBattleDialogue:iris_alder_double.victory.1"], - }, - marnie_piers_double: { - encounter: ["doubleBattleDialogue:marnie_piers_double.encounter.1"], - victory: ["doubleBattleDialogue:marnie_piers_double.victory.1"], - }, - piers_marnie_double: { - encounter: ["doubleBattleDialogue:piers_marnie_double.encounter.1"], - victory: ["doubleBattleDialogue:piers_marnie_double.victory.1"], - }, -}; - export const battleSpecDialogue = { [BattleSpec.FINAL_BOSS]: { encounter: "battleSpecDialogue:encounter", diff --git a/src/data/double-battle-dialogue.ts b/src/data/double-battle-dialogue.ts new file mode 100644 index 00000000000..f15b74e4729 --- /dev/null +++ b/src/data/double-battle-dialogue.ts @@ -0,0 +1,44 @@ +// TODO: Move this back into `dialogue.ts` after finding a suitable way to remove the circular dependencies +// that caused this to be moved out in the first place +export const doubleBattleDialogue = { + blue_red_double: { + encounter: ["doubleBattleDialogue:blue_red_double.encounter.1"], + victory: ["doubleBattleDialogue:blue_red_double.victory.1"], + }, + red_blue_double: { + encounter: ["doubleBattleDialogue:red_blue_double.encounter.1"], + victory: ["doubleBattleDialogue:red_blue_double.victory.1"], + }, + tate_liza_double: { + encounter: ["doubleBattleDialogue:tate_liza_double.encounter.1"], + victory: ["doubleBattleDialogue:tate_liza_double.victory.1"], + }, + liza_tate_double: { + encounter: ["doubleBattleDialogue:liza_tate_double.encounter.1"], + victory: ["doubleBattleDialogue:liza_tate_double.victory.1"], + }, + wallace_steven_double: { + encounter: ["doubleBattleDialogue:wallace_steven_double.encounter.1"], + victory: ["doubleBattleDialogue:wallace_steven_double.victory.1"], + }, + steven_wallace_double: { + encounter: ["doubleBattleDialogue:steven_wallace_double.encounter.1"], + victory: ["doubleBattleDialogue:steven_wallace_double.victory.1"], + }, + alder_iris_double: { + encounter: ["doubleBattleDialogue:alder_iris_double.encounter.1"], + victory: ["doubleBattleDialogue:alder_iris_double.victory.1"], + }, + iris_alder_double: { + encounter: ["doubleBattleDialogue:iris_alder_double.encounter.1"], + victory: ["doubleBattleDialogue:iris_alder_double.victory.1"], + }, + marnie_piers_double: { + encounter: ["doubleBattleDialogue:marnie_piers_double.encounter.1"], + victory: ["doubleBattleDialogue:marnie_piers_double.victory.1"], + }, + piers_marnie_double: { + encounter: ["doubleBattleDialogue:piers_marnie_double.encounter.1"], + victory: ["doubleBattleDialogue:piers_marnie_double.victory.1"], + }, +}; diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 57660b51391..e713020cf9c 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -33,38 +33,12 @@ import type { ArenaTrapTag } from "../arena-tag"; import { WeakenMoveTypeTag } from "../arena-tag"; import { ArenaTagSide } from "#enums/arena-tag-side"; import { - AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPostAttackAbAttrs, applyPostItemLostAbAttrs, applyPreAttackAbAttrs, - applyPreDefendAbAttrs, - BlockItemTheftAbAttr, - BlockNonDirectDamageAbAttr, - BlockOneHitKOAbAttr, - BlockRecoilDamageAttr, - ChangeMovePriorityAbAttr, - ConfusionOnStatusEffectAbAttr, - FieldMoveTypePowerBoostAbAttr, - FieldPreventExplosiveMovesAbAttr, - ForceSwitchOutImmunityAbAttr, - HealFromBerryUseAbAttr, - IgnoreContactAbAttr, - IgnoreMoveEffectsAbAttr, - IgnoreProtectOnContactAbAttr, - InfiltratorAbAttr, - MaxMultiHitAbAttr, - MoveAbilityBypassAbAttr, - MoveEffectChanceMultiplierAbAttr, - MoveTypeChangeAbAttr, - PostDamageForceSwitchAbAttr, - PostItemLostAbAttr, - ReflectStatusMoveAbAttr, - ReverseDrainAbAttr, - UserFieldMoveTypePowerBoostAbAttr, - VariableMovePowerAbAttr, - WonderSkinAbAttr, -} from "../abilities/ability"; + applyPreDefendAbAttrs +} from "../abilities/apply-ab-attrs"; import { allAbilities, allMoves } from "../data-lists"; import { AttackTypeBoosterModifier, @@ -77,7 +51,7 @@ import { import type { BattlerIndex } from "#enums/battler-index"; import { BattleType } from "#enums/battle-type"; import { TerrainType } from "../terrain"; -import { ModifierPoolType } from "#app/modifier/modifier-type"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import { Command } from "#enums/command"; import i18next from "i18next"; import type { Localizable } from "#app/@types/locales"; @@ -377,7 +351,7 @@ export default abstract class Move implements Localizable { const bypassed = new BooleanHolder(false); // TODO: Allow this to be simulated - applyAbAttrs(InfiltratorAbAttr, user, null, false, bypassed); + applyAbAttrs("InfiltratorAbAttr", user, null, false, bypassed); return !bypassed.value && !this.hasFlag(MoveFlags.SOUND_BASED) @@ -668,14 +642,14 @@ export default abstract class Move implements Localizable { // special cases below, eg: if the move flag is MAKES_CONTACT, and the user pokemon has an ability that ignores contact (like "Long Reach"), then overrides and move does not make contact switch (flag) { case MoveFlags.MAKES_CONTACT: - if (user.hasAbilityWithAttr(IgnoreContactAbAttr) || this.hitsSubstitute(user, target)) { + if (user.hasAbilityWithAttr("IgnoreContactAbAttr") || this.hitsSubstitute(user, target)) { return false; } break; case MoveFlags.IGNORE_ABILITIES: - if (user.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) { + if (user.hasAbilityWithAttr("MoveAbilityBypassAbAttr")) { const abilityEffectsIgnored = new BooleanHolder(false); - applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, false, this); + applyAbAttrs("MoveAbilityBypassAbAttr", user, abilityEffectsIgnored, false, this); if (abilityEffectsIgnored.value) { return true; } @@ -684,7 +658,7 @@ export default abstract class Move implements Localizable { } return this.hasFlag(MoveFlags.IGNORE_ABILITIES) && !isFollowUp; case MoveFlags.IGNORE_PROTECT: - if (user.hasAbilityWithAttr(IgnoreProtectOnContactAbAttr) + if (user.hasAbilityWithAttr("IgnoreProtectOnContactAbAttr") && this.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user })) { return true; } @@ -695,7 +669,7 @@ export default abstract class Move implements Localizable { target?.getTag(SemiInvulnerableTag) || !(target?.getTag(BattlerTagType.MAGIC_COAT) || (!this.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user, target }) && - target?.hasAbilityWithAttr(ReflectStatusMoveAbAttr))) + target?.hasAbilityWithAttr("ReflectStatusMoveAbAttr"))) ) { return false; } @@ -792,7 +766,7 @@ export default abstract class Move implements Localizable { const moveAccuracy = new NumberHolder(this.accuracy); applyMoveAttrs("VariableAccuracyAttr", user, target, this, moveAccuracy); - applyPreDefendAbAttrs(WonderSkinAbAttr, target, user, this, { value: false }, simulated, moveAccuracy); + applyPreDefendAbAttrs("WonderSkinAbAttr", target, user, this, { value: false }, simulated, moveAccuracy); if (moveAccuracy.value === -1) { return moveAccuracy.value; @@ -835,25 +809,25 @@ export default abstract class Move implements Localizable { const typeChangeMovePowerMultiplier = new NumberHolder(1); const typeChangeHolder = new NumberHolder(this.type); - applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, typeChangeHolder, typeChangeMovePowerMultiplier); + applyPreAttackAbAttrs("MoveTypeChangeAbAttr", source, target, this, true, typeChangeHolder, typeChangeMovePowerMultiplier); const sourceTeraType = source.getTeraType(); if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { power.value = 60; } - applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, target, this, simulated, power); + applyPreAttackAbAttrs("VariableMovePowerAbAttr", source, target, this, simulated, power); const ally = source.getAlly(); if (!isNullOrUndefined(ally)) { - applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, ally, target, this, simulated, power); + applyPreAttackAbAttrs("AllyMoveCategoryPowerBoostAbAttr", ally, target, this, simulated, power); } const fieldAuras = new Set( globalScene.getField(true) - .map((p) => p.getAbilityAttrs(FieldMoveTypePowerBoostAbAttr).filter(attr => { + .map((p) => p.getAbilityAttrs("FieldMoveTypePowerBoostAbAttr").filter(attr => { const condition = attr.getCondition(); return (!condition || condition(p)); - }) as FieldMoveTypePowerBoostAbAttr[]) + })) .flat(), ); for (const aura of fieldAuras) { @@ -861,7 +835,7 @@ export default abstract class Move implements Localizable { } 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; @@ -888,7 +862,7 @@ export default abstract class Move implements Localizable { const priority = new NumberHolder(this.priority); applyMoveAttrs("IncrementMovePriorityAttr", user, null, this, priority); - applyAbAttrs(ChangeMovePriorityAbAttr, user, null, simulated, this, priority); + applyAbAttrs("ChangeMovePriorityAbAttr", user, null, simulated, this, priority); return priority.value; } @@ -1340,7 +1314,7 @@ export class MoveEffectAttr extends MoveAttr { getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): number { const moveChance = new NumberHolder(this.effectChanceOverride ?? move.chance); - applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, !showAbility, moveChance, move); + applyAbAttrs("MoveEffectChanceMultiplierAbAttr", user, null, !showAbility, moveChance, move); if ((!move.hasAttr("FlinchAttr") || moveChance.value <= move.chance) && !move.hasAttr("SecretPowerAttr")) { const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; @@ -1348,7 +1322,7 @@ export class MoveEffectAttr extends MoveAttr { } if (!selfEffect) { - applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr, target, user, null, null, !showAbility, moveChance); + applyPreDefendAbAttrs("IgnoreMoveEffectsAbAttr", target, user, null, null, !showAbility, moveChance); } return moveChance.value; } @@ -1726,8 +1700,8 @@ export class RecoilAttr extends MoveEffectAttr { const cancelled = new BooleanHolder(false); if (!this.unblockable) { - applyAbAttrs(BlockRecoilDamageAttr, user, cancelled); - applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); + applyAbAttrs("BlockRecoilDamageAttr", user, cancelled); + applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled); } if (cancelled.value) { @@ -1860,7 +1834,7 @@ export class HalfSacrificialAttr extends MoveEffectAttr { const cancelled = new BooleanHolder(false); // Check to see if the Pokemon has an ability that blocks non-direct damage - applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); + applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled); if (!cancelled.value) { user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT, ignoreSegments: true }); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", { pokemonName: getPokemonNameWithAffix(user) })); // Queue recoil message @@ -2059,7 +2033,7 @@ export class FlameBurstAttr extends MoveEffectAttr { const cancelled = new BooleanHolder(false); if (!isNullOrUndefined(targetAlly)) { - applyAbAttrs(BlockNonDirectDamageAbAttr, targetAlly, cancelled); + applyAbAttrs("BlockNonDirectDamageAbAttr", targetAlly, cancelled); } if (cancelled.value || !targetAlly || targetAlly.switchOutStatus) { @@ -2289,7 +2263,7 @@ export class HitHealAttr extends MoveEffectAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { let healAmount = 0; let message = ""; - const reverseDrain = target.hasAbilityWithAttr(ReverseDrainAbAttr, false); + const reverseDrain = target.hasAbilityWithAttr("ReverseDrainAbAttr", false); if (this.healStat !== null) { // Strength Sap formula healAmount = target.getEffectiveStat(this.healStat); @@ -2300,7 +2274,7 @@ export class HitHealAttr extends MoveEffectAttr { message = i18next.t("battle:regainHealth", { pokemonName: getPokemonNameWithAffix(user) }); } if (reverseDrain) { - if (user.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { + if (user.hasAbilityWithAttr("BlockNonDirectDamageAbAttr")) { healAmount = 0; message = ""; } else { @@ -2430,7 +2404,7 @@ export class MultiHitAttr extends MoveAttr { { const rand = user.randBattleSeedInt(20); const hitValue = new NumberHolder(rand); - applyAbAttrs(MaxMultiHitAbAttr, user, null, false, hitValue); + applyAbAttrs("MaxMultiHitAbAttr", user, null, false, hitValue); if (hitValue.value >= 13) { return 2; } else if (hitValue.value >= 6) { @@ -2538,7 +2512,7 @@ export class StatusEffectAttr extends MoveEffectAttr { } if (((!pokemon.status || this.overrideStatus) || (pokemon.status.effect === this.effect && moveChance < 0)) && pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining, null, this.overrideStatus, quiet)) { - applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect); + applyPostAttackAbAttrs("ConfusionOnStatusEffectAbAttr", user, target, move, null, false, this.effect); return true; } } @@ -2694,7 +2668,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { // Check for abilities that block item theft // TODO: This should not trigger if the target would faint beforehand const cancelled = new BooleanHolder(false); - applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); + applyAbAttrs("BlockItemTheftAbAttr", target, cancelled); if (cancelled.value) { return false; @@ -2811,8 +2785,8 @@ export class EatBerryAttr extends MoveEffectAttr { protected eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer, updateHarvest = consumer === berryOwner) { // consumer eats berry, owner triggers unburden and similar effects getBerryEffectFunc(this.chosenBerry.berryType)(consumer); - applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner, false); - applyAbAttrs(HealFromBerryUseAbAttr, consumer, new BooleanHolder(false)); + applyPostItemLostAbAttrs("PostItemLostAbAttr", berryOwner, false); + applyAbAttrs("HealFromBerryUseAbAttr", consumer, new BooleanHolder(false)); consumer.recordEatenBerry(this.chosenBerry.berryType, updateHarvest); } } @@ -2837,7 +2811,7 @@ export class StealEatBerryAttr extends EatBerryAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { // check for abilities that block item theft const cancelled = new BooleanHolder(false); - applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); + applyAbAttrs("BlockItemTheftAbAttr", target, cancelled); if (cancelled.value === true) { return false; } @@ -2851,7 +2825,7 @@ export class StealEatBerryAttr extends EatBerryAttr { // pick a random berry and eat it 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 }); globalScene.phaseManager.queueMessage(message); this.reduceBerryModifier(target); @@ -2892,7 +2866,7 @@ export class HealStatusEffectAttr extends MoveEffectAttr { // Special edge case for shield dust blocking Sparkling Aria curing burn const moveTargets = getMoveTargets(user, move.id); - if (target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && move.id === MoveId.SPARKLING_ARIA && moveTargets.targets.length === 1) { + if (target.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") && move.id === MoveId.SPARKLING_ARIA && moveTargets.targets.length === 1) { return false; } @@ -3042,7 +3016,7 @@ export class OneHitKOAttr extends MoveAttr { getCondition(): MoveConditionFunc { return (user, target, move) => { const cancelled = new BooleanHolder(false); - applyAbAttrs(BlockOneHitKOAbAttr, target, cancelled); + applyAbAttrs("BlockOneHitKOAbAttr", target, cancelled); return !cancelled.value && user.level >= target.level; }; } @@ -5442,7 +5416,7 @@ export class NoEffectAttr extends MoveAttr { const crashDamageFunc = (user: Pokemon, move: Move) => { const cancelled = new BooleanHolder(false); - applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); + applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled); if (cancelled.value) { return false; } @@ -6302,7 +6276,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { * 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 (target.getAbility().hasAttr(PostDamageForceSwitchAbAttr) + if (target.getAbility().hasAttr("PostDamageForceSwitchAbAttr") && [ MoveId.U_TURN, MoveId.VOLT_SWITCH, MoveId.FLIP_TURN ].includes(move.id) ) { if (this.hpDroppedBelowHalf(target)) { @@ -6391,7 +6365,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { * 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 (target.getAbility().hasAttr(PostDamageForceSwitchAbAttr) + if (target.getAbility().hasAttr("PostDamageForceSwitchAbAttr") && [ MoveId.U_TURN, MoveId.VOLT_SWITCH, MoveId.FLIP_TURN ].includes(move.id) ) { if (this.hpDroppedBelowHalf(target)) { @@ -6434,7 +6408,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { getFailedText(_user: Pokemon, target: Pokemon, _move: Move): string | undefined { const blockedByAbility = new BooleanHolder(false); - applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, blockedByAbility); + applyAbAttrs("ForceSwitchOutImmunityAbAttr", target, blockedByAbility); if (blockedByAbility.value) { return i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) }); } @@ -6474,7 +6448,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { } const blockedByAbility = new BooleanHolder(false); - applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, blockedByAbility); + applyAbAttrs("ForceSwitchOutImmunityAbAttr", target, blockedByAbility); if (blockedByAbility.value) { return false; } @@ -7981,7 +7955,7 @@ const failIfSingleBattle: MoveConditionFunc = (user, target, move) => globalScen const failIfDampCondition: MoveConditionFunc = (user, target, move) => { const cancelled = new BooleanHolder(false); - 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 if (cancelled.value) { globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cannotUseMove", { pokemonName: getPokemonNameWithAffix(user), moveName: move.name })); diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index 11081892205..7a1c9821e89 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -19,8 +19,8 @@ import i18next from "i18next"; import type { IEggOptions } from "#app/data/egg"; import { EggSourceType } from "#enums/egg-source-types"; import { EggTier } from "#enums/egg-type"; -import { ModifierTier } from "#app/modifier/modifier-tier"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { ModifierTier } from "#enums/modifier-tier"; +import { modifierTypes } from "#app/data/data-lists"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; /** the i18n namespace for the encounter */ diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index 2213dc4afaa..3eda96e4028 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -10,7 +10,7 @@ import type Pokemon from "#app/field/pokemon"; import { EnemyPokemon } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { globalScene } from "#app/global-scene"; diff --git a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts index cdb98c56ed1..271346616d1 100644 --- a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts +++ b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts @@ -4,7 +4,7 @@ import { setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { globalScene } from "#app/global-scene"; diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index 65ae3ea6c4f..d86a8439804 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -12,7 +12,9 @@ import { import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import type { BerryModifierType, ModifierTypeOption } from "#app/modifier/modifier-type"; -import { ModifierPoolType, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; +import { regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import { randSeedInt } from "#app/utils/common"; import { BattlerTagType } from "#enums/battler-tag-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index c318efc0cb3..df06f40c159 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -38,7 +38,7 @@ import { } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { PokemonType } from "#enums/pokemon-type"; import type { AttackTypeBoosterModifierType, ModifierTypeOption } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { AttackTypeBoosterModifier, @@ -50,7 +50,7 @@ import { import i18next from "i18next"; import MoveInfoOverlay from "#app/ui/move-info-overlay"; import { allMoves } from "#app/data/data-lists"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index a81dcc841b7..542bd163713 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -11,9 +11,10 @@ import { import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { ModifierPoolType, modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { globalScene } from "#app/global-scene"; @@ -38,7 +39,6 @@ import i18next from "i18next"; import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler"; import type { PlayerPokemon } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; -import { Ability } from "#app/data/abilities/ability-class"; import { BerryModifier } from "#app/modifier/modifier"; import { BerryType } from "#enums/berry-type"; import { BattlerIndex } from "#enums/battler-index"; @@ -49,6 +49,7 @@ import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { EncounterAnim } from "#enums/encounter-anims"; import { Challenges } from "#enums/challenges"; +import { allAbilities } from "#app/data/data-lists"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/clowningAround"; @@ -139,7 +140,7 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder // Generate random ability for Blacephalon from pool const ability = RANDOM_ABILITY_POOL[randSeedInt(RANDOM_ABILITY_POOL.length)]; - encounter.setDialogueToken("ability", new Ability(ability, 3).name); + encounter.setDialogueToken("ability", allAbilities[ability].name); encounter.misc = { ability }; // Decide the random types for Blacephalon. They should not be the same. diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index a74980919d6..4444a2e6b1b 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -26,7 +26,7 @@ import type Pokemon from "#app/field/pokemon"; import { EnemyPokemon } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import PokemonData from "#app/system/pokemon-data"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { BattlerTagType } from "#enums/battler-tag-type"; diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 6474df3570e..002b38f445d 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -3,7 +3,7 @@ import { isNullOrUndefined, randSeedInt } from "#app/utils/common"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { globalScene } from "#app/global-scene"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index 40893d93930..692ffe6e80e 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -28,7 +28,7 @@ import { PreserveBerryModifier, } from "#app/modifier/modifier"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import i18next from "#app/plugins/i18n"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { randSeedItem } from "#app/utils/common"; diff --git a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts index 2b6ac9b7cf3..46152a7dc41 100644 --- a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts +++ b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts @@ -2,8 +2,8 @@ import { leaveEncounterWithoutBattle, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import type { ModifierTypeFunc } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import type { ModifierTypeFunc } from "#app/@types/modifier-types"; +import { modifierTypes } from "#app/data/data-lists"; import { randSeedInt } from "#app/utils/common"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; diff --git a/src/data/mystery-encounters/encounters/field-trip-encounter.ts b/src/data/mystery-encounters/encounters/field-trip-encounter.ts index 82fbfd51eec..6ab0f8a6a4b 100644 --- a/src/data/mystery-encounters/encounters/field-trip-encounter.ts +++ b/src/data/mystery-encounters/encounters/field-trip-encounter.ts @@ -9,7 +9,7 @@ import { } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { PlayerPokemon } from "#app/field/pokemon"; import type { PokemonMove } from "#app/data/moves/pokemon-move"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index d42778cb17c..4b24bf9cada 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -10,7 +10,7 @@ import { generateModifierType, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; @@ -45,8 +45,8 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Stat } from "#enums/stat"; -import { Ability } from "#app/data/abilities/ability-class"; import { FIRE_RESISTANT_ABILITIES } from "#app/data/mystery-encounters/requirements/requirement-groups"; +import { allAbilities } from "#app/data/data-lists"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/fieryFallout"; @@ -246,7 +246,7 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w if (chosenPokemon.trySetStatus(StatusEffect.BURN)) { // Burn applied encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender()); - encounter.setDialogueToken("abilityName", new Ability(AbilityId.HEATPROOF, 3).name); + encounter.setDialogueToken("abilityName", allAbilities[AbilityId.HEATPROOF].name); queueEncounterMessage(`${namespace}:option.2.target_burned`); // Also permanently change the burned Pokemon's ability to Heatproof diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index 83538e9e0e9..f925452e143 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -9,13 +9,13 @@ import { } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import type Pokemon from "#app/field/pokemon"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import type { ModifierTypeOption } from "#app/modifier/modifier-type"; import { getPlayerModifierTypeOptions, - ModifierPoolType, regenerateModifierPoolThresholds, } from "#app/modifier/modifier-type"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; diff --git a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts index 456562ca70e..48557541512 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -26,7 +26,7 @@ import { PlayerGender } from "#enums/player-gender"; import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball"; import { addPokeballOpenParticles } from "#app/field/anims"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { Nature } from "#enums/nature"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index a84b0af30ed..1b188915de7 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -4,14 +4,14 @@ import { setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { TrainerSlot } from "#enums/trainer-slot"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { MusicPreference } from "#app/system/settings/settings"; import type { ModifierTypeOption } from "#app/modifier/modifier-type"; import { getPlayerModifierTypeOptions, - ModifierPoolType, regenerateModifierPoolThresholds, } from "#app/modifier/modifier-type"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; diff --git a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts index 6907e18cfdc..010358ea3b2 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -7,8 +7,8 @@ import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { trainerPartyTemplates } from "#app/data/trainers/TrainerPartyTemplate"; import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; -import { ModifierTier } from "#app/modifier/modifier-tier"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { ModifierTier } from "#enums/modifier-tier"; +import { modifierTypes } from "#app/data/data-lists"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { globalScene } from "#app/global-scene"; diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index 62029eb1847..022b0125fde 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -16,7 +16,7 @@ import { } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { randSeedInt } from "#app/utils/common"; import { MoveId } from "#enums/move-id"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; diff --git a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts index 1afc67e1d12..967c105c740 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -7,7 +7,7 @@ import { } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { randSeedInt } from "#app/utils/common"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index 1ecd73143fc..483c577e851 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -1,6 +1,6 @@ import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { globalScene } from "#app/global-scene"; diff --git a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts index edc9cf13834..9c9232612c6 100644 --- a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts +++ b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts @@ -23,7 +23,8 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { BiomeId } from "#enums/biome-id"; import { getBiomeKey } from "#app/field/arena"; import { PokemonType } from "#enums/pokemon-type"; -import { getPartyLuckValue, modifierTypes } from "#app/modifier/modifier-type"; +import { getPartyLuckValue } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { TrainerSlot } from "#enums/trainer-slot"; import { BattlerTagType } from "#enums/battler-tag-type"; import { getPokemonNameWithAffix } from "#app/messages"; diff --git a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts index fdfcc8f2f13..0676b40c548 100644 --- a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts @@ -26,7 +26,7 @@ import { EggSourceType } from "#enums/egg-source-types"; import { EggTier } from "#enums/egg-type"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { PokemonType } from "#enums/pokemon-type"; import { getPokeballTintColor } from "#app/data/pokeball"; diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index a3eb78f479e..61d9dcfccfd 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -8,7 +8,7 @@ import { generateModifierType, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index 91754629821..e2c87d8c0ae 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -8,7 +8,7 @@ import { transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; @@ -24,11 +24,11 @@ import { PokemonType } from "#enums/pokemon-type"; import { BerryType } from "#enums/berry-type"; import { Stat } from "#enums/stat"; import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms/form-change-triggers"; -import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/abilities/ability"; +import { applyPostBattleInitAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import i18next from "i18next"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -221,7 +221,7 @@ function endTrainerBattleAndShowDialogue(): Promise { // Each trainer battle is supposed to be a new fight, so reset all per-battle activation effects pokemon.resetBattleAndWaveData(); - applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon); + applyPostBattleInitAbAttrs("PostBattleInitAbAttr", pokemon); } globalScene.phaseManager.unshiftNew("ShowTrainerPhase"); diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index 9ab91f439bf..04e64083602 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -1,4 +1,4 @@ -import type { Ability } from "#app/data/abilities/ability-class"; +import type { Ability } from "#app/data/abilities/ability"; import { allAbilities } from "#app/data/data-lists"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index 8bcd024de5c..1416c63dd28 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -8,7 +8,7 @@ import { transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; @@ -21,7 +21,7 @@ import { HitHealModifier, PokemonHeldItemModifier, TurnHealModifier } from "#app import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import i18next from "#app/plugins/i18n"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { MoveId } from "#enums/move-id"; import { BattlerIndex } from "#enums/battler-index"; diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index fe1911c9007..83e876d1aa8 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -25,7 +25,7 @@ import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier } from import { achvs } from "#app/system/achv"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import i18next from "#app/plugins/i18n"; import { doPokemonTransformationSequence, @@ -34,7 +34,7 @@ import { import { getLevelTotalExp } from "#app/data/exp"; import { Stat } from "#enums/stat"; import { Challenges } from "#enums/challenges"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { PlayerGender } from "#enums/player-gender"; import { TrainerType } from "#enums/trainer-type"; import PokemonData from "#app/system/pokemon-data"; diff --git a/src/data/mystery-encounters/mystery-encounter-save-data.ts b/src/data/mystery-encounters/mystery-encounter-save-data.ts index dd633390e46..20c10c7c5b9 100644 --- a/src/data/mystery-encounters/mystery-encounter-save-data.ts +++ b/src/data/mystery-encounters/mystery-encounter-save-data.ts @@ -1,5 +1,5 @@ import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import { BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT } from "#app/data/mystery-encounters/mystery-encounters"; +import { BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT } from "#app/constants"; import { isNullOrUndefined } from "#app/utils/common"; import type { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; diff --git a/src/data/mystery-encounters/mystery-encounters.ts b/src/data/mystery-encounters/mystery-encounters.ts index 3a1d3760c83..5ee289a6c56 100644 --- a/src/data/mystery-encounters/mystery-encounters.ts +++ b/src/data/mystery-encounters/mystery-encounters.ts @@ -34,42 +34,6 @@ import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encount import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter"; import { getBiomeName } from "#app/data/balance/biomes"; -/** - * Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * ) / MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT - */ -export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 3; -/** - * The divisor for determining ME spawns, defines the "maximum" weight required for a spawn - * If spawn_weight === MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, 100% chance to spawn a ME - */ -export const MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT = 256; -/** - * When an ME spawn roll fails, WEIGHT_INCREMENT_ON_SPAWN_MISS is added to future rolls for ME spawn checks. - * These values are cleared whenever the next ME spawns, and spawn weight returns to BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT - */ -export const WEIGHT_INCREMENT_ON_SPAWN_MISS = 3; -/** - * Specifies the target average for total ME spawns in a single Classic run. - * Used by anti-variance mechanic to check whether a run is above or below the target on a given wave. - */ -export const AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 12; -/** - * Will increase/decrease the chance of spawning a ME based on the current run's total MEs encountered vs AVERAGE_ENCOUNTERS_PER_RUN_TARGET - * Example: - * AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 17 (expects avg 1 ME every 10 floors) - * ANTI_VARIANCE_WEIGHT_MODIFIER = 15 - * - * On wave 20, if 1 ME has been encountered, the difference from expected average is 0 MEs. - * So anti-variance adds 0/256 to the spawn weight check for ME spawn. - * - * On wave 20, if 0 MEs have been encountered, the difference from expected average is 1 ME. - * So anti-variance adds 15/256 to the spawn weight check for ME spawn. - * - * On wave 20, if 2 MEs have been encountered, the difference from expected average is -1 ME. - * So anti-variance adds -15/256 to the spawn weight check for ME spawn. - */ -export const ANTI_VARIANCE_WEIGHT_MODIFIER = 15; - export const EXTREME_ENCOUNTER_BIOMES = [ BiomeId.SEA, BiomeId.SEABED, diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 69984229681..599edb11628 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -3,10 +3,7 @@ import { BattlerIndex } from "#enums/battler-index"; import { BattleType } from "#enums/battle-type"; import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes"; import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; -import { - AVERAGE_ENCOUNTERS_PER_RUN_TARGET, - WEIGHT_INCREMENT_ON_SPAWN_MISS, -} from "#app/data/mystery-encounters/mystery-encounters"; +import { AVERAGE_ENCOUNTERS_PER_RUN_TARGET, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/constants"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import type { PlayerPokemon } from "#app/field/pokemon"; import type { AiType } from "#enums/ai-type"; @@ -17,12 +14,12 @@ import { FieldPosition } from "#enums/field-position"; import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type"; import { getPartyLuckValue, - ModifierPoolType, ModifierTypeGenerator, ModifierTypeOption, - modifierTypes, regenerateModifierPoolThresholds, } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import type PokemonData from "#app/system/pokemon-data"; import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler"; diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index e8a3db46cff..4671869a2ba 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -29,7 +29,7 @@ import { } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getPokemonNameWithAffix } from "#app/messages"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { Gender } from "#app/data/gender"; import type { PermanentStat } from "#enums/stat"; import { SummaryUiMode } from "#app/ui/summary-ui-handler"; diff --git a/src/data/pokeball.ts b/src/data/pokeball.ts index 7a44ebdda7c..a479fd8068a 100644 --- a/src/data/pokeball.ts +++ b/src/data/pokeball.ts @@ -1,5 +1,4 @@ import { globalScene } from "#app/global-scene"; -import { CriticalCatchChanceBoosterModifier } from "#app/modifier/modifier"; import { NumberHolder } from "#app/utils/common"; import { PokeballType } from "#enums/pokeball"; import i18next from "i18next"; @@ -94,7 +93,7 @@ export function getCriticalCaptureChance(modifiedCatchRate: number): number { } const dexCount = globalScene.gameData.getSpeciesCount(d => !!d.caughtAttr); const catchingCharmMultiplier = new NumberHolder(1); - globalScene.findModifier(m => m instanceof CriticalCatchChanceBoosterModifier)?.apply(catchingCharmMultiplier); + globalScene.findModifier(m => m.is("CriticalCatchChanceBoosterModifier"))?.apply(catchingCharmMultiplier); const dexMultiplier = globalScene.gameMode.isDaily || dexCount > 800 ? 2.5 diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 2f1f7ed07a8..8c065666202 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -1,11 +1,11 @@ import { globalScene } from "#app/global-scene"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "../data-lists"; import { PokemonMove } from "../moves/pokemon-move"; import { toReadableString, isNullOrUndefined, randSeedItem, randSeedInt, randSeedIntRange } from "#app/utils/common"; import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { tmSpecies } from "#app/data/balance/tms"; -import { doubleBattleDialogue } from "#app/data/dialogue"; +import { doubleBattleDialogue } from "../double-battle-dialogue"; import { TrainerVariant } from "#enums/trainer-variant"; import { getIsInitialized, initI18n } from "#app/plugins/i18n"; import i18next from "i18next"; @@ -37,7 +37,7 @@ import { timedEventManager } from "#app/global-event-manager"; // Type imports import type { PokemonSpeciesFilter } from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species"; -import type { ModifierTypeFunc } from "#app/modifier/modifier-type"; +import type { ModifierTypeFunc } from "#app/@types/modifier-types"; import type { EnemyPokemon } from "#app/field/pokemon"; import type { EvilTeam } from "./evil-admin-trainer-pools"; import type { diff --git a/src/data/weather.ts b/src/data/weather.ts index 822e5aa8303..425e15b12a8 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -5,12 +5,12 @@ import type Pokemon from "../field/pokemon"; import { PokemonType } from "#enums/pokemon-type"; import type Move from "./moves/move"; import { randSeedInt } from "#app/utils/common"; -import { SuppressWeatherEffectAbAttr } from "./abilities/ability"; import { TerrainType, getTerrainName } from "./terrain"; import i18next from "i18next"; import { globalScene } from "#app/global-scene"; import type { Arena } from "#app/field/arena"; import { timedEventManager } from "#app/global-event-manager"; +import type { SuppressWeatherEffectAbAttr } from "./abilities/ability"; export class Weather { public weatherType: WeatherType; @@ -108,10 +108,10 @@ export class Weather { for (const pokemon of field) { let suppressWeatherEffectAbAttr: SuppressWeatherEffectAbAttr | null = pokemon .getAbility() - .getAttrs(SuppressWeatherEffectAbAttr)[0]; + .getAttrs("SuppressWeatherEffectAbAttr")[0]; if (!suppressWeatherEffectAbAttr) { suppressWeatherEffectAbAttr = pokemon.hasPassive() - ? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr)[0] + ? pokemon.getPassiveAbility().getAttrs("SuppressWeatherEffectAbAttr")[0] : null; } if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable)) { diff --git a/src/enums/modifier-pool-type.ts b/src/enums/modifier-pool-type.ts new file mode 100644 index 00000000000..0d2b92ba80d --- /dev/null +++ b/src/enums/modifier-pool-type.ts @@ -0,0 +1,7 @@ +export enum ModifierPoolType { + PLAYER, + WILD, + TRAINER, + ENEMY_BUFF, + DAILY_STARTER +} diff --git a/src/modifier/modifier-tier.ts b/src/enums/modifier-tier.ts similarity index 100% rename from src/modifier/modifier-tier.ts rename to src/enums/modifier-tier.ts diff --git a/src/enums/unlockables.ts b/src/enums/unlockables.ts new file mode 100644 index 00000000000..77b39a17e90 --- /dev/null +++ b/src/enums/unlockables.ts @@ -0,0 +1,7 @@ + +export enum Unlockables { + ENDLESS_MODE, + MINI_BLACK_HOLE, + SPLICED_ENDLESS_MODE, + EVIOLITE +} diff --git a/src/field/arena.ts b/src/field/arena.ts index fffbf8dfa26..c77c9a5b3ce 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -24,10 +24,7 @@ import { applyAbAttrs, applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs, - PostTerrainChangeAbAttr, - PostWeatherChangeAbAttr, - TerrainEventTypeChangeAbAttr, -} from "#app/data/abilities/ability"; +} from "#app/data/abilities/apply-ab-attrs"; import type Pokemon from "#app/field/pokemon"; import Overrides from "#app/overrides"; import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena"; @@ -374,7 +371,7 @@ export class Arena { pokemon.findAndRemoveTags( t => "weatherTypes" in t && !(t.weatherTypes as WeatherType[]).find(t => t === weather), ); - applyPostWeatherChangeAbAttrs(PostWeatherChangeAbAttr, pokemon, weather); + applyPostWeatherChangeAbAttrs("PostWeatherChangeAbAttr", pokemon, weather); }); return true; @@ -463,8 +460,8 @@ export class Arena { pokemon.findAndRemoveTags( t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain), ); - applyPostTerrainChangeAbAttrs(PostTerrainChangeAbAttr, pokemon, terrain); - applyAbAttrs(TerrainEventTypeChangeAbAttr, pokemon, null, false); + applyPostTerrainChangeAbAttrs("PostTerrainChangeAbAttr", pokemon, terrain); + applyAbAttrs("TerrainEventTypeChangeAbAttr", pokemon, null, false); }); return true; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 7032cd06131..6a34d936a51 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -111,61 +111,23 @@ import { WeatherType } from "#enums/weather-type"; import { NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; import { ArenaTagSide } from "#enums/arena-tag-side"; import type { SuppressAbilitiesTag } from "#app/data/arena-tag"; -import type { Ability } from "#app/data/abilities/ability-class"; -import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr"; +import type { Ability } from "#app/data/abilities/ability"; import { - StatMultiplierAbAttr, - BlockCritAbAttr, - BonusCritAbAttr, - BypassBurnDamageReductionAbAttr, - FieldPriorityMoveImmunityAbAttr, - IgnoreOpponentStatStagesAbAttr, - MoveImmunityAbAttr, - PreDefendFullHpEndureAbAttr, - ReceivedMoveDamageMultiplierAbAttr, - StabBoostAbAttr, - StatusEffectImmunityAbAttr, - TypeImmunityAbAttr, - WeightMultiplierAbAttr, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, - NoFusionAbilityAbAttr, - MultCritAbAttr, - IgnoreTypeImmunityAbAttr, - DamageBoostAbAttr, - IgnoreTypeStatusEffectImmunityAbAttr, - ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, - FieldMultiplyStatAbAttr, - AddSecondStrikeAbAttr, - UserFieldStatusEffectImmunityAbAttr, - UserFieldBattlerTagImmunityAbAttr, - BattlerTagImmunityAbAttr, - MoveTypeChangeAbAttr, - FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, - CheckTrappedAbAttr, - InfiltratorAbAttr, - AlliedFieldDamageReductionAbAttr, - PostDamageAbAttr, applyPostDamageAbAttrs, - CommanderAbAttr, applyPostItemLostAbAttrs, - PostItemLostAbAttr, applyOnGainAbAttrs, - PreLeaveFieldAbAttr, applyPreLeaveFieldAbAttrs, applyOnLoseAbAttrs, - PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, applyAllyStatMultiplierAbAttrs, - AllyStatMultiplierAbAttr, - MoveAbilityBypassAbAttr, - PreSummonAbAttr, -} from "#app/data/abilities/ability"; +} from "#app/data/abilities/apply-ab-attrs"; import { allAbilities } from "#app/data/data-lists"; import type PokemonData from "#app/system/pokemon-data"; import { BattlerIndex } from "#enums/battler-index"; @@ -192,7 +154,7 @@ import type { TrainerSlot } from "#enums/trainer-slot"; import Overrides from "#app/overrides"; import i18next from "i18next"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { applyChallenges } from "#app/data/challenge"; import { ChallengeType } from "#enums/challenge-type"; import { AbilityId } from "#enums/ability-id"; @@ -229,6 +191,7 @@ import { HitResult } from "#enums/hit-result"; import { AiType } from "#enums/ai-type"; import type { MoveResult } from "#enums/move-result"; import { PokemonMove } from "#app/data/moves/pokemon-move"; +import type { AbAttrMap, AbAttrString } from "#app/@types/ability-types"; /** Base typeclass for damage parameter methods, used for DRY */ type damageParams = { @@ -1403,7 +1366,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyMoveAttrs("HighCritAttr", source, this, move, critStage); globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage); globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage); - applyAbAttrs(BonusCritAbAttr, source, null, false, critStage); + applyAbAttrs("BonusCritAbAttr", source, null, false, critStage); const critBoostTag = source.getTag(CritBoostTag); if (critBoostTag) { if (critBoostTag instanceof DragonCheerTag) { @@ -1464,19 +1427,27 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // The Ruin abilities here are never ignored, but they reveal themselves on summon anyway const fieldApplied = new BooleanHolder(false); for (const pokemon of globalScene.getField(true)) { - applyFieldStatMultiplierAbAttrs(FieldMultiplyStatAbAttr, pokemon, stat, statValue, this, fieldApplied, simulated); + applyFieldStatMultiplierAbAttrs( + "FieldMultiplyStatAbAttr", + pokemon, + stat, + statValue, + this, + fieldApplied, + simulated, + ); if (fieldApplied.value) { break; } } if (!ignoreAbility) { - applyStatMultiplierAbAttrs(StatMultiplierAbAttr, this, stat, statValue, simulated); + applyStatMultiplierAbAttrs("StatMultiplierAbAttr", this, stat, statValue, simulated); } const ally = this.getAlly(); if (!isNullOrUndefined(ally)) { applyAllyStatMultiplierAbAttrs( - AllyStatMultiplierAbAttr, + "AllyStatMultiplierAbAttr", ally, stat, statValue, @@ -2059,15 +2030,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param ignoreOverride - Whether to ignore ability changing effects; Default `false` * @returns An array of all the ability attributes on this ability. */ - public getAbilityAttrs( - attrType: { new (...args: any[]): T }, - canApply = true, - ignoreOverride = false, - ): T[] { - const abilityAttrs: T[] = []; + public getAbilityAttrs(attrType: T, canApply = true, ignoreOverride = false): AbAttrMap[T][] { + const abilityAttrs: AbAttrMap[T][] = []; if (!canApply || this.canApplyAbility()) { - abilityAttrs.push(...this.getAbility(ignoreOverride).getAttrs(attrType)); + abilityAttrs.push(...this.getAbility(ignoreOverride).getAttrs(attrType)); } if (!canApply || this.canApplyAbility(true)) { @@ -2152,7 +2119,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return false; } const ability = !passive ? this.getAbility() : this.getPassiveAbility(); - if (this.isFusion() && ability.hasAttr(NoFusionAbilityAbAttr)) { + if (this.isFusion() && ability.hasAttr("NoFusionAbilityAbAttr")) { return false; } const arena = globalScene?.arena; @@ -2163,10 +2130,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return false; } const suppressAbilitiesTag = arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag; - const suppressOffField = ability.hasAttr(PreSummonAbAttr); + const suppressOffField = ability.hasAttr("PreSummonAbAttr"); if ((this.isOnField() || suppressOffField) && suppressAbilitiesTag && !suppressAbilitiesTag.isBeingRemoved()) { - const thisAbilitySuppressing = ability.hasAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr); - const hasSuppressingAbility = this.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false); + const thisAbilitySuppressing = ability.hasAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr"); + const hasSuppressingAbility = this.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false); // Neutralizing gas is up - suppress abilities unless they are unsuppressable or this pokemon is responsible for the gas // (Balance decided that the other ability of a neutralizing gas pokemon should not be neutralized) // If the ability itself is neutralizing gas, don't suppress it (handled through arena tag) @@ -2207,7 +2174,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param ignoreOverride Whether to ignore ability changing effects; default `false` * @returns `true` if an ability with the given {@linkcode AbAttr} is present and active */ - public hasAbilityWithAttr(attrType: Constructor, canApply = true, ignoreOverride = false): boolean { + public hasAbilityWithAttr(attrType: AbAttrString, canApply = true, ignoreOverride = false): boolean { if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).hasAttr(attrType)) { return true; } @@ -2229,7 +2196,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const weight = new NumberHolder(this.species.weight - weightRemoved); // This will trigger the ability overlay so only call this function when necessary - applyAbAttrs(WeightMultiplierAbAttr, this, null, false, weight); + applyAbAttrs("WeightMultiplierAbAttr", this, null, false, weight); return Math.max(minWeight, weight.value); } @@ -2300,7 +2267,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const opposingField = opposingFieldUnfiltered.filter(enemyPkm => enemyPkm.switchOutStatus === false); for (const opponent of opposingField) { - applyCheckTrappedAbAttrs(CheckTrappedAbAttr, opponent, trappedByAbility, this, trappedAbMessages, simulated); + applyCheckTrappedAbAttrs("CheckTrappedAbAttr", opponent, trappedByAbility, this, trappedAbMessages, simulated); } const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; @@ -2322,7 +2289,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const moveTypeHolder = new NumberHolder(move.type); applyMoveAttrs("VariableMoveTypeAttr", this, null, move, moveTypeHolder); - applyPreAttackAbAttrs(MoveTypeChangeAbAttr, this, null, move, simulated, moveTypeHolder); + applyPreAttackAbAttrs("MoveTypeChangeAbAttr", this, null, move, simulated, moveTypeHolder); // If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type, // then bypass the check for ion deluge and electrify @@ -2387,16 +2354,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const cancelledHolder = cancelled ?? new BooleanHolder(false); if (!ignoreAbility) { - applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelledHolder, simulated, typeMultiplier); + applyPreDefendAbAttrs("TypeImmunityAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier); if (!cancelledHolder.value) { - applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelledHolder, simulated, typeMultiplier); + applyPreDefendAbAttrs("MoveImmunityAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier); } if (!cancelledHolder.value) { const defendingSidePlayField = this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); defendingSidePlayField.forEach(p => - applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelledHolder), + applyPreDefendAbAttrs("FieldPriorityMoveImmunityAbAttr", p, source, move, cancelledHolder), ); } } @@ -2411,7 +2378,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Apply Tera Shell's effect to attacks after all immunities are accounted for if (!ignoreAbility && move.category !== MoveCategory.STATUS) { - applyPreDefendAbAttrs(FullHpResistTypeAbAttr, this, source, move, cancelledHolder, simulated, typeMultiplier); + applyPreDefendAbAttrs("FullHpResistTypeAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier); } if (move.category === MoveCategory.STATUS && move.hitsSubstitute(source, this)) { @@ -2463,8 +2430,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (source) { const ignoreImmunity = new BooleanHolder(false); - if (source.isActive(true) && source.hasAbilityWithAttr(IgnoreTypeImmunityAbAttr)) { - applyAbAttrs(IgnoreTypeImmunityAbAttr, source, ignoreImmunity, simulated, moveType, defType); + if (source.isActive(true) && source.hasAbilityWithAttr("IgnoreTypeImmunityAbAttr")) { + applyAbAttrs("IgnoreTypeImmunityAbAttr", source, ignoreImmunity, simulated, moveType, defType); } if (ignoreImmunity.value) { if (multiplier.value === 0) { @@ -3415,7 +3382,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } if (!ignoreOppAbility) { - applyAbAttrs(IgnoreOpponentStatStagesAbAttr, opponent, null, simulated, stat, ignoreStatStage); + applyAbAttrs("IgnoreOpponentStatStagesAbAttr", opponent, null, simulated, stat, ignoreStatStage); } if (move) { applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, opponent, move, ignoreStatStage); @@ -3454,8 +3421,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const ignoreAccStatStage = new BooleanHolder(false); const ignoreEvaStatStage = new BooleanHolder(false); - applyAbAttrs(IgnoreOpponentStatStagesAbAttr, target, null, false, Stat.ACC, ignoreAccStatStage); - applyAbAttrs(IgnoreOpponentStatStagesAbAttr, this, null, false, Stat.EVA, ignoreEvaStatStage); + applyAbAttrs("IgnoreOpponentStatStagesAbAttr", target, null, false, Stat.ACC, ignoreAccStatStage); + applyAbAttrs("IgnoreOpponentStatStagesAbAttr", this, null, false, Stat.EVA, ignoreEvaStatStage); applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage); globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage); @@ -3475,16 +3442,33 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { : 3 / (3 + Math.min(targetEvaStage.value - userAccStage.value, 6)); } - applyStatMultiplierAbAttrs(StatMultiplierAbAttr, this, Stat.ACC, accuracyMultiplier, false, sourceMove); + applyStatMultiplierAbAttrs("StatMultiplierAbAttr", this, Stat.ACC, accuracyMultiplier, false, sourceMove); const evasionMultiplier = new NumberHolder(1); - applyStatMultiplierAbAttrs(StatMultiplierAbAttr, target, Stat.EVA, evasionMultiplier); + applyStatMultiplierAbAttrs("StatMultiplierAbAttr", target, Stat.EVA, evasionMultiplier); const ally = this.getAlly(); if (!isNullOrUndefined(ally)) { - const ignore = this.hasAbilityWithAttr(MoveAbilityBypassAbAttr) || sourceMove.hasFlag(MoveFlags.IGNORE_ABILITIES); - applyAllyStatMultiplierAbAttrs(AllyStatMultiplierAbAttr, ally, Stat.ACC, accuracyMultiplier, false, this, ignore); - applyAllyStatMultiplierAbAttrs(AllyStatMultiplierAbAttr, ally, Stat.EVA, evasionMultiplier, false, this, ignore); + const ignore = + this.hasAbilityWithAttr("MoveAbilityBypassAbAttr") || sourceMove.hasFlag(MoveFlags.IGNORE_ABILITIES); + applyAllyStatMultiplierAbAttrs( + "AllyStatMultiplierAbAttr", + ally, + Stat.ACC, + accuracyMultiplier, + false, + this, + ignore, + ); + applyAllyStatMultiplierAbAttrs( + "AllyStatMultiplierAbAttr", + ally, + Stat.EVA, + evasionMultiplier, + false, + this, + ignore, + ); } return accuracyMultiplier.value / evasionMultiplier.value; @@ -3599,7 +3583,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyMoveAttrs("CombinedPledgeStabBoostAttr", source, this, move, stabMultiplier); if (!ignoreSourceAbility) { - applyAbAttrs(StabBoostAbAttr, source, null, simulated, stabMultiplier); + applyAbAttrs("StabBoostAbAttr", source, null, simulated, stabMultiplier); } if (source.isTerastallized && sourceTeraType === moveType && moveType !== PokemonType.STELLAR) { @@ -3748,7 +3732,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ); if (!ignoreSourceAbility) { applyPreAttackAbAttrs( - AddSecondStrikeAbAttr, + "AddSecondStrikeAbAttr", source, this, move, @@ -3766,7 +3750,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** The damage multiplier when the given move critically hits */ const criticalMultiplier = new NumberHolder(isCritical ? 1.5 : 1); - applyAbAttrs(MultCritAbAttr, source, null, simulated, criticalMultiplier); + applyAbAttrs("MultCritAbAttr", source, null, simulated, criticalMultiplier); /** * A multiplier for random damage spread in the range [0.85, 1] @@ -3787,7 +3771,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ) { const burnDamageReductionCancelled = new BooleanHolder(false); if (!ignoreSourceAbility) { - applyAbAttrs(BypassBurnDamageReductionAbAttr, source, burnDamageReductionCancelled, simulated); + applyAbAttrs("BypassBurnDamageReductionAbAttr", source, burnDamageReductionCancelled, simulated); } if (!burnDamageReductionCancelled.value) { burnMultiplier = 0.5; @@ -3851,7 +3835,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** Doubles damage if the attacker has Tinted Lens and is using a resisted move */ if (!ignoreSourceAbility) { - applyPreAttackAbAttrs(DamageBoostAbAttr, source, this, move, simulated, damage); + applyPreAttackAbAttrs("DamageBoostAbAttr", source, this, move, simulated, damage); } /** Apply the enemy's Damage and Resistance tokens */ @@ -3864,12 +3848,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** Apply this Pokemon's post-calc defensive modifiers (e.g. Fur Coat) */ if (!ignoreAbility) { - applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, move, cancelled, simulated, damage); + applyPreDefendAbAttrs("ReceivedMoveDamageMultiplierAbAttr", this, source, move, cancelled, simulated, damage); const ally = this.getAlly(); /** Additionally apply friend guard damage reduction if ally has it. */ if (globalScene.currentBattle.double && !isNullOrUndefined(ally) && ally.isActive(true)) { - applyPreDefendAbAttrs(AlliedFieldDamageReductionAbAttr, ally, source, move, cancelled, simulated, damage); + applyPreDefendAbAttrs("AlliedFieldDamageReductionAbAttr", ally, source, move, cancelled, simulated, damage); } } @@ -3877,7 +3861,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyMoveAttrs("ModifiedDamageAttr", source, this, move, damage); if (this.isFullHp() && !ignoreAbility) { - applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, move, cancelled, false, damage); + applyPreDefendAbAttrs("PreDefendFullHpEndureAbAttr", this, source, move, cancelled, false, damage); } // debug message for when damage is applied (i.e. not simulated) @@ -3919,13 +3903,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { isCritical.value = true; } applyMoveAttrs("CritOnlyAttr", source, this, move, isCritical); - applyAbAttrs(ConditionalCritAbAttr, source, null, simulated, isCritical, this, move); + applyAbAttrs("ConditionalCritAbAttr", source, null, simulated, isCritical, this, move); if (!isCritical.value) { const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))]; isCritical.value = critChance === 1 || !globalScene.randBattleSeedInt(critChance); } - applyAbAttrs(BlockCritAbAttr, this, null, simulated, isCritical); + applyAbAttrs("BlockCritAbAttr", this, null, simulated, isCritical); return isCritical.value; } @@ -4032,7 +4016,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * Multi-hits are handled in move-effect-phase.ts for PostDamageAbAttr */ if (!source || source.turnData.hitCount <= 1) { - applyPostDamageAbAttrs(PostDamageAbAttr, this, damage, this.hasPassive(), false, [], source); + applyPostDamageAbAttrs("PostDamageAbAttr", this, damage, this.hasPassive(), false, [], source); } return damage; } @@ -4080,11 +4064,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const stubTag = new BattlerTag(tagType, 0, 0); const cancelled = new BooleanHolder(false); - applyPreApplyBattlerTagAbAttrs(BattlerTagImmunityAbAttr, this, stubTag, cancelled, true); + applyPreApplyBattlerTagAbAttrs("BattlerTagImmunityAbAttr", this, stubTag, cancelled, true); const userField = this.getAlliedField(); userField.forEach(pokemon => - applyPreApplyBattlerTagAbAttrs(UserFieldBattlerTagImmunityAbAttr, pokemon, stubTag, cancelled, true, this), + applyPreApplyBattlerTagAbAttrs("UserFieldBattlerTagImmunityAbAttr", pokemon, stubTag, cancelled, true, this), ); return !cancelled.value; @@ -4100,13 +4084,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const newTag = getBattlerTag(tagType, turnCount, sourceMove!, sourceId!); // TODO: are the bangs correct? const cancelled = new BooleanHolder(false); - applyPreApplyBattlerTagAbAttrs(BattlerTagImmunityAbAttr, this, newTag, cancelled); + applyPreApplyBattlerTagAbAttrs("BattlerTagImmunityAbAttr", this, newTag, cancelled); if (cancelled.value) { return false; } for (const pokemon of this.getAlliedField()) { - applyPreApplyBattlerTagAbAttrs(UserFieldBattlerTagImmunityAbAttr, pokemon, newTag, cancelled, false, this); + applyPreApplyBattlerTagAbAttrs("UserFieldBattlerTagImmunityAbAttr", pokemon, newTag, cancelled, false, this); if (cancelled.value) { return false; } @@ -4124,6 +4108,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /**@overload */ getTag(tagType: BattlerTagType.GRUDGE): GrudgeTag | nil; + /** @overload */ + getTag(tagType: BattlerTagType.SUBSTITUTE): SubstituteTag | undefined; + /** @overload */ getTag(tagType: BattlerTagType): BattlerTag | undefined; @@ -4626,7 +4613,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity const cancelImmunity = new BooleanHolder(false); if (sourcePokemon) { - applyAbAttrs(IgnoreTypeStatusEffectImmunityAbAttr, sourcePokemon, cancelImmunity, false, effect, defType); + applyAbAttrs("IgnoreTypeStatusEffectImmunityAbAttr", sourcePokemon, cancelImmunity, false, effect, defType); if (cancelImmunity.value) { return false; } @@ -4675,14 +4662,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } const cancelled = new BooleanHolder(false); - applyPreSetStatusAbAttrs(StatusEffectImmunityAbAttr, this, effect, cancelled, quiet); + applyPreSetStatusAbAttrs("StatusEffectImmunityAbAttr", this, effect, cancelled, quiet); if (cancelled.value) { return false; } for (const pokemon of this.getAlliedField()) { applyPreSetStatusAbAttrs( - UserFieldStatusEffectImmunityAbAttr, + "UserFieldStatusEffectImmunityAbAttr", pokemon, effect, cancelled, @@ -4839,7 +4826,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (globalScene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) { const bypassed = new BooleanHolder(false); if (attacker) { - applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed); + applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed); } return !bypassed.value; } @@ -4863,7 +4850,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // If this Pokemon has Commander and Dondozo as an active ally, hide this Pokemon's sprite. if ( - this.hasAbilityWithAttr(CommanderAbAttr) && + this.hasAbilityWithAttr("CommanderAbAttr") && globalScene.currentBattle.double && this.getAlly()?.species.speciesId === SpeciesId.DONDOZO ) { @@ -5388,7 +5375,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.hideInfo(); } // Trigger abilities that activate upon leaving the field - applyPreLeaveFieldAbAttrs(PreLeaveFieldAbAttr, this); + applyPreLeaveFieldAbAttrs("PreLeaveFieldAbAttr", this); this.setSwitchOutStatus(true); globalScene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true); globalScene.field.remove(this, destroy); @@ -5448,7 +5435,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { globalScene.removeModifier(heldItem, this.isEnemy()); } if (forBattle) { - applyPostItemLostAbAttrs(PostItemLostAbAttr, this, false); + applyPostItemLostAbAttrs("PostItemLostAbAttr", this, false); } return true; diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 67ca9a28bc5..f67d19e1027 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -21,6 +21,8 @@ import { initVouchers } from "#app/system/voucher"; import { BiomeId } from "#enums/biome-id"; import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters"; import { timedEventManager } from "./global-event-manager"; +import { initModifierPools } from "./modifier/init-modifier-pools"; +import { initModifierTypes } from "./modifier/modifier-type"; export class LoadingScene extends SceneBase { public static readonly KEY = "loading"; @@ -363,6 +365,9 @@ export class LoadingScene extends SceneBase { this.loadLoadingScreen(); + initModifierTypes(); + initModifierPools(); + initAchievements(); initVouchers(); initStatsKeys(); diff --git a/src/modifier/init-modifier-pools.ts b/src/modifier/init-modifier-pools.ts new file mode 100644 index 00000000000..60697333600 --- /dev/null +++ b/src/modifier/init-modifier-pools.ts @@ -0,0 +1,854 @@ +import type Pokemon from "#app/field/pokemon"; +import { + dailyStarterModifierPool, + enemyBuffModifierPool, + modifierPool, + trainerModifierPool, + wildModifierPool, +} from "#app/modifier/modifier-pools"; +import { globalScene } from "#app/global-scene"; +import { DoubleBattleChanceBoosterModifier, SpeciesCritBoosterModifier, TurnStatusEffectModifier } from "./modifier"; +import { WeightedModifierType } from "./modifier-type"; +import { ModifierTier } from "../enums/modifier-tier"; +import type { WeightedModifierTypeWeightFunc } from "#app/@types/modifier-types"; +import { modifierTypes } from "#app/data/data-lists"; +import { PokeballType } from "#enums/pokeball"; +import { BerryModifier } from "./modifier"; +import { BerryType } from "#enums/berry-type"; +import { SpeciesId } from "#enums/species-id"; +import { timedEventManager } from "#app/global-event-manager"; +import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; +import { Unlockables } from "#enums/unlockables"; +import { isNullOrUndefined } from "#app/utils/common"; +import { MoveId } from "#enums/move-id"; +import { StatusEffect } from "#enums/status-effect"; +import { AbilityId } from "#enums/ability-id"; +import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; +// biome-ignore lint/correctness/noUnusedImports: This is used in a tsdoc comment +import type { initModifierTypes } from "./modifier-type"; + +/** + * Initialize the wild modifier pool + */ +function initWildModifierPool() { + wildModifierPool[ModifierTier.COMMON] = [new WeightedModifierType(modifierTypes.BERRY, 1)].map(m => { + m.setTier(ModifierTier.COMMON); + return m; + }); + wildModifierPool[ModifierTier.GREAT] = [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 1)].map(m => { + m.setTier(ModifierTier.GREAT); + return m; + }); + wildModifierPool[ModifierTier.ULTRA] = [ + new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), + new WeightedModifierType(modifierTypes.WHITE_HERB, 0), + ].map(m => { + m.setTier(ModifierTier.ULTRA); + return m; + }); + wildModifierPool[ModifierTier.ROGUE] = [new WeightedModifierType(modifierTypes.LUCKY_EGG, 4)].map(m => { + m.setTier(ModifierTier.ROGUE); + return m; + }); + wildModifierPool[ModifierTier.MASTER] = [new WeightedModifierType(modifierTypes.GOLDEN_EGG, 1)].map(m => { + m.setTier(ModifierTier.MASTER); + return m; + }); +} + +/** + * Initialize the common modifier pool + */ +function initCommonModifierPool() { + modifierPool[ModifierTier.COMMON] = [ + new WeightedModifierType(modifierTypes.POKEBALL, () => (hasMaximumBalls(PokeballType.POKEBALL) ? 0 : 6), 6), + new WeightedModifierType(modifierTypes.RARE_CANDY, 2), + new WeightedModifierType( + modifierTypes.POTION, + (party: Pokemon[]) => { + const thresholdPartyMemberCount = Math.min( + party.filter(p => p.getInverseHp() >= 10 && p.getHpRatio() <= 0.875 && !p.isFainted()).length, + 3, + ); + return thresholdPartyMemberCount * 3; + }, + 9, + ), + new WeightedModifierType( + modifierTypes.SUPER_POTION, + (party: Pokemon[]) => { + const thresholdPartyMemberCount = Math.min( + party.filter(p => p.getInverseHp() >= 25 && p.getHpRatio() <= 0.75 && !p.isFainted()).length, + 3, + ); + return thresholdPartyMemberCount; + }, + 3, + ), + new WeightedModifierType( + modifierTypes.ETHER, + (party: Pokemon[]) => { + const thresholdPartyMemberCount = Math.min( + party.filter( + p => + p.hp && + !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + p + .getMoveset() + .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) + .length, + ).length, + 3, + ); + return thresholdPartyMemberCount * 3; + }, + 9, + ), + new WeightedModifierType( + modifierTypes.MAX_ETHER, + (party: Pokemon[]) => { + const thresholdPartyMemberCount = Math.min( + party.filter( + p => + p.hp && + !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + p + .getMoveset() + .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) + .length, + ).length, + 3, + ); + return thresholdPartyMemberCount; + }, + 3, + ), + new WeightedModifierType(modifierTypes.LURE, lureWeightFunc(10, 2)), + new WeightedModifierType(modifierTypes.TEMP_STAT_STAGE_BOOSTER, 4), + new WeightedModifierType(modifierTypes.BERRY, 2), + new WeightedModifierType(modifierTypes.TM_COMMON, 2), + ].map(m => { + m.setTier(ModifierTier.COMMON); + return m; + }); +} + +/** + * Initialize the Great modifier pool + */ +function initGreatModifierPool() { + modifierPool[ModifierTier.GREAT] = [ + new WeightedModifierType(modifierTypes.GREAT_BALL, () => (hasMaximumBalls(PokeballType.GREAT_BALL) ? 0 : 6), 6), + new WeightedModifierType(modifierTypes.PP_UP, 2), + new WeightedModifierType( + modifierTypes.FULL_HEAL, + (party: Pokemon[]) => { + const statusEffectPartyMemberCount = Math.min( + party.filter( + p => + p.hp && + !!p.status && + !p.getHeldItems().some(i => { + if (i instanceof TurnStatusEffectModifier) { + return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect; + } + return false; + }), + ).length, + 3, + ); + return statusEffectPartyMemberCount * 6; + }, + 18, + ), + new WeightedModifierType( + modifierTypes.REVIVE, + (party: Pokemon[]) => { + const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3); + return faintedPartyMemberCount * 9; + }, + 27, + ), + new WeightedModifierType( + modifierTypes.MAX_REVIVE, + (party: Pokemon[]) => { + const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3); + return faintedPartyMemberCount * 3; + }, + 9, + ), + new WeightedModifierType( + modifierTypes.SACRED_ASH, + (party: Pokemon[]) => { + return party.filter(p => p.isFainted()).length >= Math.ceil(party.length / 2) ? 1 : 0; + }, + 1, + ), + new WeightedModifierType( + modifierTypes.HYPER_POTION, + (party: Pokemon[]) => { + const thresholdPartyMemberCount = Math.min( + party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.625 && !p.isFainted()).length, + 3, + ); + return thresholdPartyMemberCount * 3; + }, + 9, + ), + new WeightedModifierType( + modifierTypes.MAX_POTION, + (party: Pokemon[]) => { + const thresholdPartyMemberCount = Math.min( + party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.5 && !p.isFainted()).length, + 3, + ); + return thresholdPartyMemberCount; + }, + 3, + ), + new WeightedModifierType( + modifierTypes.FULL_RESTORE, + (party: Pokemon[]) => { + const statusEffectPartyMemberCount = Math.min( + party.filter( + p => + p.hp && + !!p.status && + !p.getHeldItems().some(i => { + if (i instanceof TurnStatusEffectModifier) { + return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect; + } + return false; + }), + ).length, + 3, + ); + const thresholdPartyMemberCount = Math.floor( + (Math.min(party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.5 && !p.isFainted()).length, 3) + + statusEffectPartyMemberCount) / + 2, + ); + return thresholdPartyMemberCount; + }, + 3, + ), + new WeightedModifierType( + modifierTypes.ELIXIR, + (party: Pokemon[]) => { + const thresholdPartyMemberCount = Math.min( + party.filter( + p => + p.hp && + !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + p + .getMoveset() + .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) + .length, + ).length, + 3, + ); + return thresholdPartyMemberCount * 3; + }, + 9, + ), + new WeightedModifierType( + modifierTypes.MAX_ELIXIR, + (party: Pokemon[]) => { + const thresholdPartyMemberCount = Math.min( + party.filter( + p => + p.hp && + !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + p + .getMoveset() + .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) + .length, + ).length, + 3, + ); + return thresholdPartyMemberCount; + }, + 3, + ), + new WeightedModifierType(modifierTypes.DIRE_HIT, 4), + new WeightedModifierType(modifierTypes.SUPER_LURE, lureWeightFunc(15, 4)), + new WeightedModifierType(modifierTypes.NUGGET, skipInLastClassicWaveOrDefault(5)), + new WeightedModifierType(modifierTypes.SPECIES_STAT_BOOSTER, 4), + new WeightedModifierType( + modifierTypes.EVOLUTION_ITEM, + () => { + return Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 15), 8); + }, + 8, + ), + new WeightedModifierType( + modifierTypes.MAP, + () => (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex < 180 ? 2 : 0), + 2, + ), + new WeightedModifierType(modifierTypes.SOOTHE_BELL, 2), + new WeightedModifierType(modifierTypes.TM_GREAT, 3), + new WeightedModifierType( + modifierTypes.MEMORY_MUSHROOM, + (party: Pokemon[]) => { + if (!party.find(p => p.getLearnableLevelMoves().length)) { + return 0; + } + const highestPartyLevel = party + .map(p => p.level) + .reduce((highestLevel: number, level: number) => Math.max(highestLevel, level), 1); + return Math.min(Math.ceil(highestPartyLevel / 20), 4); + }, + 4, + ), + new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3), + new WeightedModifierType(modifierTypes.TERA_SHARD, (party: Pokemon[]) => + party.filter( + p => + !(p.hasSpecies(SpeciesId.TERAPAGOS) || p.hasSpecies(SpeciesId.OGERPON) || p.hasSpecies(SpeciesId.SHEDINJA)), + ).length > 0 + ? 1 + : 0, + ), + new WeightedModifierType( + modifierTypes.DNA_SPLICERS, + (party: Pokemon[]) => { + if (party.filter(p => !p.fusionSpecies).length > 1) { + if (globalScene.gameMode.isSplicedOnly) { + return 4; + } + if (globalScene.gameMode.isClassic && timedEventManager.areFusionsBoosted()) { + return 2; + } + } + return 0; + }, + 4, + ), + new WeightedModifierType( + modifierTypes.VOUCHER, + (_party: Pokemon[], rerollCount: number) => (!globalScene.gameMode.isDaily ? Math.max(1 - rerollCount, 0) : 0), + 1, + ), + ].map(m => { + m.setTier(ModifierTier.GREAT); + return m; + }); +} + +/** + * Initialize the Ultra modifier pool + */ +function initUltraModifierPool() { + modifierPool[ModifierTier.ULTRA] = [ + new WeightedModifierType(modifierTypes.ULTRA_BALL, () => (hasMaximumBalls(PokeballType.ULTRA_BALL) ? 0 : 15), 15), + new WeightedModifierType(modifierTypes.MAX_LURE, lureWeightFunc(30, 4)), + new WeightedModifierType(modifierTypes.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)), + new WeightedModifierType(modifierTypes.PP_MAX, 3), + new WeightedModifierType(modifierTypes.MINT, 4), + new WeightedModifierType( + modifierTypes.RARE_EVOLUTION_ITEM, + () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 15) * 4, 32), + 32, + ), + new WeightedModifierType( + modifierTypes.FORM_CHANGE_ITEM, + () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6, + 24, + ), + new WeightedModifierType(modifierTypes.AMULET_COIN, skipInLastClassicWaveOrDefault(3)), + new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => { + const { gameMode, gameData } = globalScene; + if (gameMode.isDaily || (!gameMode.isFreshStartChallenge() && gameData.isUnlocked(Unlockables.EVIOLITE))) { + return party.some(p => { + // Check if Pokemon's species (or fusion species, if applicable) can evolve or if they're G-Max'd + if ( + !p.isMax() && + (p.getSpeciesForm(true).speciesId in pokemonEvolutions || + (p.isFusion() && p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions)) + ) { + // Check if Pokemon is already holding an Eviolite + return !p.getHeldItems().some(i => i.type.id === "EVIOLITE"); + } + return false; + }) + ? 10 + : 0; + } + return 0; + }), + new WeightedModifierType(modifierTypes.RARE_SPECIES_STAT_BOOSTER, 12), + new WeightedModifierType( + modifierTypes.LEEK, + (party: Pokemon[]) => { + const checkedSpecies = [SpeciesId.FARFETCHD, SpeciesId.GALAR_FARFETCHD, SpeciesId.SIRFETCHD]; + // If a party member doesn't already have a Leek and is one of the relevant species, Leek can appear + return party.some( + p => + !p.getHeldItems().some(i => i instanceof SpeciesCritBoosterModifier) && + (checkedSpecies.includes(p.getSpeciesForm(true).speciesId) || + (p.isFusion() && checkedSpecies.includes(p.getFusionSpeciesForm(true).speciesId))), + ) + ? 12 + : 0; + }, + 12, + ), + new WeightedModifierType( + modifierTypes.TOXIC_ORB, + (party: Pokemon[]) => { + return party.some(p => { + const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); + + if (!isHoldingOrb) { + const moveset = p + .getMoveset(true) + .filter(m => !isNullOrUndefined(m)) + .map(m => m.moveId); + const canSetStatus = p.canSetStatus(StatusEffect.TOXIC, true, true, null, true); + + // Moves that take advantage of obtaining the actual status effect + const hasStatusMoves = [MoveId.FACADE, MoveId.PSYCHO_SHIFT].some(m => moveset.includes(m)); + // Moves that take advantage of being able to give the target a status orb + // TODO: Take moves (Trick, Fling, Switcheroo) from comment when they are implemented + const hasItemMoves = [ + /* MoveId.TRICK, MoveId.FLING, MoveId.SWITCHEROO */ + ].some(m => moveset.includes(m)); + + if (canSetStatus) { + // Abilities that take advantage of obtaining the actual status effect, separated based on specificity to the orb + const hasGeneralAbility = [ + AbilityId.QUICK_FEET, + AbilityId.GUTS, + AbilityId.MARVEL_SCALE, + AbilityId.MAGIC_GUARD, + ].some(a => p.hasAbility(a, false, true)); + const hasSpecificAbility = [AbilityId.TOXIC_BOOST, AbilityId.POISON_HEAL].some(a => + p.hasAbility(a, false, true), + ); + const hasOppositeAbility = [AbilityId.FLARE_BOOST].some(a => p.hasAbility(a, false, true)); + + return hasSpecificAbility || (hasGeneralAbility && !hasOppositeAbility) || hasStatusMoves; + } + return hasItemMoves; + } + + return false; + }) + ? 10 + : 0; + }, + 10, + ), + new WeightedModifierType( + modifierTypes.FLAME_ORB, + (party: Pokemon[]) => { + return party.some(p => { + const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); + + if (!isHoldingOrb) { + const moveset = p + .getMoveset(true) + .filter(m => !isNullOrUndefined(m)) + .map(m => m.moveId); + const canSetStatus = p.canSetStatus(StatusEffect.BURN, true, true, null, true); + + // Moves that take advantage of obtaining the actual status effect + const hasStatusMoves = [MoveId.FACADE, MoveId.PSYCHO_SHIFT].some(m => moveset.includes(m)); + // Moves that take advantage of being able to give the target a status orb + // TODO: Take moves (Trick, Fling, Switcheroo) from comment when they are implemented + const hasItemMoves = [ + /* MoveId.TRICK, MoveId.FLING, MoveId.SWITCHEROO */ + ].some(m => moveset.includes(m)); + + if (canSetStatus) { + // Abilities that take advantage of obtaining the actual status effect, separated based on specificity to the orb + const hasGeneralAbility = [ + AbilityId.QUICK_FEET, + AbilityId.GUTS, + AbilityId.MARVEL_SCALE, + AbilityId.MAGIC_GUARD, + ].some(a => p.hasAbility(a, false, true)); + const hasSpecificAbility = [AbilityId.FLARE_BOOST].some(a => p.hasAbility(a, false, true)); + const hasOppositeAbility = [AbilityId.TOXIC_BOOST, AbilityId.POISON_HEAL].some(a => + p.hasAbility(a, false, true), + ); + + return hasSpecificAbility || (hasGeneralAbility && !hasOppositeAbility) || hasStatusMoves; + } + return hasItemMoves; + } + + return false; + }) + ? 10 + : 0; + }, + 10, + ), + new WeightedModifierType( + modifierTypes.MYSTICAL_ROCK, + (party: Pokemon[]) => { + return party.some(p => { + let isHoldingMax = false; + for (const i of p.getHeldItems()) { + if (i.type.id === "MYSTICAL_ROCK") { + isHoldingMax = i.getStackCount() === i.getMaxStackCount(); + break; + } + } + + if (!isHoldingMax) { + const moveset = p.getMoveset(true).map(m => m.moveId); + + const hasAbility = [ + AbilityId.DROUGHT, + AbilityId.ORICHALCUM_PULSE, + AbilityId.DRIZZLE, + AbilityId.SAND_STREAM, + AbilityId.SAND_SPIT, + AbilityId.SNOW_WARNING, + AbilityId.ELECTRIC_SURGE, + AbilityId.HADRON_ENGINE, + AbilityId.PSYCHIC_SURGE, + AbilityId.GRASSY_SURGE, + AbilityId.SEED_SOWER, + AbilityId.MISTY_SURGE, + ].some(a => p.hasAbility(a, false, true)); + + const hasMoves = [ + MoveId.SUNNY_DAY, + MoveId.RAIN_DANCE, + MoveId.SANDSTORM, + MoveId.SNOWSCAPE, + MoveId.HAIL, + MoveId.CHILLY_RECEPTION, + MoveId.ELECTRIC_TERRAIN, + MoveId.PSYCHIC_TERRAIN, + MoveId.GRASSY_TERRAIN, + MoveId.MISTY_TERRAIN, + ].some(m => moveset.includes(m)); + + return hasAbility || hasMoves; + } + return false; + }) + ? 10 + : 0; + }, + 10, + ), + new WeightedModifierType(modifierTypes.REVIVER_SEED, 4), + new WeightedModifierType(modifierTypes.CANDY_JAR, skipInLastClassicWaveOrDefault(5)), + new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 9), + new WeightedModifierType(modifierTypes.TM_ULTRA, 11), + new WeightedModifierType(modifierTypes.RARER_CANDY, 4), + new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, skipInLastClassicWaveOrDefault(2)), + new WeightedModifierType(modifierTypes.IV_SCANNER, skipInLastClassicWaveOrDefault(4)), + new WeightedModifierType(modifierTypes.EXP_CHARM, skipInLastClassicWaveOrDefault(8)), + new WeightedModifierType(modifierTypes.EXP_SHARE, skipInLastClassicWaveOrDefault(10)), + new WeightedModifierType( + modifierTypes.TERA_ORB, + () => + !globalScene.gameMode.isClassic + ? Math.min(Math.max(Math.floor(globalScene.currentBattle.waveIndex / 50) * 2, 1), 4) + : 0, + 4, + ), + new WeightedModifierType(modifierTypes.QUICK_CLAW, 3), + new WeightedModifierType(modifierTypes.WIDE_LENS, 7), + ].map(m => { + m.setTier(ModifierTier.ULTRA); + return m; + }); +} + +function initRogueModifierPool() { + modifierPool[ModifierTier.ROGUE] = [ + new WeightedModifierType(modifierTypes.ROGUE_BALL, () => (hasMaximumBalls(PokeballType.ROGUE_BALL) ? 0 : 16), 16), + new WeightedModifierType(modifierTypes.RELIC_GOLD, skipInLastClassicWaveOrDefault(2)), + new WeightedModifierType(modifierTypes.LEFTOVERS, 3), + new WeightedModifierType(modifierTypes.SHELL_BELL, 3), + new WeightedModifierType(modifierTypes.BERRY_POUCH, 4), + new WeightedModifierType(modifierTypes.GRIP_CLAW, 5), + new WeightedModifierType(modifierTypes.SCOPE_LENS, 4), + new WeightedModifierType(modifierTypes.BATON, 2), + new WeightedModifierType(modifierTypes.SOUL_DEW, 7), + new WeightedModifierType(modifierTypes.CATCHING_CHARM, () => (!globalScene.gameMode.isClassic ? 4 : 0), 4), + new WeightedModifierType(modifierTypes.ABILITY_CHARM, skipInClassicAfterWave(189, 6)), + new WeightedModifierType(modifierTypes.FOCUS_BAND, 5), + new WeightedModifierType(modifierTypes.KINGS_ROCK, 3), + new WeightedModifierType(modifierTypes.LOCK_CAPSULE, () => (globalScene.gameMode.isClassic ? 0 : 3)), + new WeightedModifierType(modifierTypes.SUPER_EXP_CHARM, skipInLastClassicWaveOrDefault(8)), + new WeightedModifierType( + modifierTypes.RARE_FORM_CHANGE_ITEM, + () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6, + 24, + ), + new WeightedModifierType( + modifierTypes.MEGA_BRACELET, + () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9, + 36, + ), + new WeightedModifierType( + modifierTypes.DYNAMAX_BAND, + () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9, + 36, + ), + new WeightedModifierType( + modifierTypes.VOUCHER_PLUS, + (_party: Pokemon[], rerollCount: number) => + !globalScene.gameMode.isDaily ? Math.max(3 - rerollCount * 1, 0) : 0, + 3, + ), + ].map(m => { + m.setTier(ModifierTier.ROGUE); + return m; + }); +} + +/** + * Initialize the Master modifier pool + */ +function initMasterModifierPool() { + modifierPool[ModifierTier.MASTER] = [ + new WeightedModifierType(modifierTypes.MASTER_BALL, () => (hasMaximumBalls(PokeballType.MASTER_BALL) ? 0 : 24), 24), + new WeightedModifierType(modifierTypes.SHINY_CHARM, 14), + new WeightedModifierType(modifierTypes.HEALING_CHARM, 18), + new WeightedModifierType(modifierTypes.MULTI_LENS, 18), + new WeightedModifierType( + modifierTypes.VOUCHER_PREMIUM, + (_party: Pokemon[], rerollCount: number) => + !globalScene.gameMode.isDaily && !globalScene.gameMode.isEndless && !globalScene.gameMode.isSplicedOnly + ? Math.max(5 - rerollCount * 2, 0) + : 0, + 5, + ), + new WeightedModifierType( + modifierTypes.DNA_SPLICERS, + (party: Pokemon[]) => + !(globalScene.gameMode.isClassic && timedEventManager.areFusionsBoosted()) && + !globalScene.gameMode.isSplicedOnly && + party.filter(p => !p.fusionSpecies).length > 1 + ? 24 + : 0, + 24, + ), + new WeightedModifierType( + modifierTypes.MINI_BLACK_HOLE, + () => + globalScene.gameMode.isDaily || + (!globalScene.gameMode.isFreshStartChallenge() && globalScene.gameData.isUnlocked(Unlockables.MINI_BLACK_HOLE)) + ? 1 + : 0, + 1, + ), + ].map(m => { + m.setTier(ModifierTier.MASTER); + return m; + }); +} + +function initTrainerModifierPool() { + trainerModifierPool[ModifierTier.COMMON] = [ + new WeightedModifierType(modifierTypes.BERRY, 8), + new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3), + ].map(m => { + m.setTier(ModifierTier.COMMON); + return m; + }); + trainerModifierPool[ModifierTier.GREAT] = [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3)].map(m => { + m.setTier(ModifierTier.GREAT); + return m; + }); + trainerModifierPool[ModifierTier.ULTRA] = [ + new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), + new WeightedModifierType(modifierTypes.WHITE_HERB, 0), + ].map(m => { + m.setTier(ModifierTier.ULTRA); + return m; + }); + trainerModifierPool[ModifierTier.ROGUE] = [ + new WeightedModifierType(modifierTypes.FOCUS_BAND, 2), + new WeightedModifierType(modifierTypes.LUCKY_EGG, 4), + new WeightedModifierType(modifierTypes.QUICK_CLAW, 1), + new WeightedModifierType(modifierTypes.GRIP_CLAW, 1), + new WeightedModifierType(modifierTypes.WIDE_LENS, 1), + ].map(m => { + m.setTier(ModifierTier.ROGUE); + return m; + }); + trainerModifierPool[ModifierTier.MASTER] = [ + new WeightedModifierType(modifierTypes.KINGS_ROCK, 1), + new WeightedModifierType(modifierTypes.LEFTOVERS, 1), + new WeightedModifierType(modifierTypes.SHELL_BELL, 1), + new WeightedModifierType(modifierTypes.SCOPE_LENS, 1), + ].map(m => { + m.setTier(ModifierTier.MASTER); + return m; + }); +} + +/** + * Initialize the enemy buff modifier pool + */ +function initEnemyBuffModifierPool() { + enemyBuffModifierPool[ModifierTier.COMMON] = [ + new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 9), + new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 9), + new WeightedModifierType(modifierTypes.ENEMY_ATTACK_POISON_CHANCE, 3), + new WeightedModifierType(modifierTypes.ENEMY_ATTACK_PARALYZE_CHANCE, 3), + new WeightedModifierType(modifierTypes.ENEMY_ATTACK_BURN_CHANCE, 3), + new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 9), + new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 4), + new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 1), + ].map(m => { + m.setTier(ModifierTier.COMMON); + return m; + }); + enemyBuffModifierPool[ModifierTier.GREAT] = [ + new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 5), + new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 5), + new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 5), + new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 5), + new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 1), + ].map(m => { + m.setTier(ModifierTier.GREAT); + return m; + }); + enemyBuffModifierPool[ModifierTier.ULTRA] = [ + new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 10), + new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 10), + new WeightedModifierType(modifierTypes.ENEMY_HEAL, 10), + new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 10), + new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 10), + new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 5), + ].map(m => { + m.setTier(ModifierTier.ULTRA); + return m; + }); + enemyBuffModifierPool[ModifierTier.ROGUE] = [].map((m: WeightedModifierType) => { + m.setTier(ModifierTier.ROGUE); + return m; + }); + enemyBuffModifierPool[ModifierTier.MASTER] = [].map((m: WeightedModifierType) => { + m.setTier(ModifierTier.MASTER); + return m; + }); +} + +/** + * Initialize the daily starter modifier pool + */ +function initDailyStarterModifierPool() { + dailyStarterModifierPool[ModifierTier.COMMON] = [ + new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 1), + new WeightedModifierType(modifierTypes.BERRY, 3), + ].map(m => { + m.setTier(ModifierTier.COMMON); + return m; + }); + dailyStarterModifierPool[ModifierTier.GREAT] = [new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 5)].map( + m => { + m.setTier(ModifierTier.GREAT); + return m; + }, + ); + dailyStarterModifierPool[ModifierTier.ULTRA] = [ + new WeightedModifierType(modifierTypes.REVIVER_SEED, 4), + new WeightedModifierType(modifierTypes.SOOTHE_BELL, 1), + new WeightedModifierType(modifierTypes.SOUL_DEW, 1), + new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, 1), + ].map(m => { + m.setTier(ModifierTier.ULTRA); + return m; + }); + dailyStarterModifierPool[ModifierTier.ROGUE] = [ + new WeightedModifierType(modifierTypes.GRIP_CLAW, 5), + new WeightedModifierType(modifierTypes.BATON, 2), + new WeightedModifierType(modifierTypes.FOCUS_BAND, 5), + new WeightedModifierType(modifierTypes.QUICK_CLAW, 3), + new WeightedModifierType(modifierTypes.KINGS_ROCK, 3), + ].map(m => { + m.setTier(ModifierTier.ROGUE); + return m; + }); + dailyStarterModifierPool[ModifierTier.MASTER] = [ + new WeightedModifierType(modifierTypes.LEFTOVERS, 1), + new WeightedModifierType(modifierTypes.SHELL_BELL, 1), + ].map(m => { + m.setTier(ModifierTier.MASTER); + return m; + }); +} + +/** + * Initialize {@linkcode modifierPool} with the initial set of modifier types. + * {@linkcode initModifierTypes} MUST be called before this function. + */ +export function initModifierPools() { + // The modifier pools the player chooses from during modifier selection + initCommonModifierPool(); + initGreatModifierPool(); + initUltraModifierPool(); + initRogueModifierPool(); + initMasterModifierPool(); + + // Modifier pools for specific scenarios + initWildModifierPool(); + initTrainerModifierPool(); + initEnemyBuffModifierPool(); + initDailyStarterModifierPool(); +} + +/** + * High order function that returns a WeightedModifierTypeWeightFunc that will only be applied on + * classic and skip an ModifierType if current wave is greater or equal to the one passed down + * @param wave - Wave where we should stop showing the modifier + * @param defaultWeight - ModifierType default weight + * @returns A WeightedModifierTypeWeightFunc + */ +function skipInClassicAfterWave(wave: number, defaultWeight: number): WeightedModifierTypeWeightFunc { + return () => { + const gameMode = globalScene.gameMode; + const currentWave = globalScene.currentBattle.waveIndex; + return gameMode.isClassic && currentWave >= wave ? 0 : defaultWeight; + }; +} + +/** + * High order function that returns a WeightedModifierTypeWeightFunc that will only be applied on + * classic and it will skip a ModifierType if it is the last wave pull. + * @param defaultWeight ModifierType default weight + * @returns A WeightedModifierTypeWeightFunc + */ +function skipInLastClassicWaveOrDefault(defaultWeight: number): WeightedModifierTypeWeightFunc { + return skipInClassicAfterWave(199, defaultWeight); +} + +/** + * High order function that returns a WeightedModifierTypeWeightFunc to ensure Lures don't spawn on Classic 199 + * or if the lure still has over 60% of its duration left + * @param maxBattles The max battles the lure type in question lasts. 10 for green, 15 for Super, 30 for Max + * @param weight The desired weight for the lure when it does spawn + * @returns A WeightedModifierTypeWeightFunc + */ +function lureWeightFunc(maxBattles: number, weight: number): WeightedModifierTypeWeightFunc { + return () => { + const lures = globalScene.getModifiers(DoubleBattleChanceBoosterModifier); + return !(globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex === 199) && + (lures.length === 0 || + lures.filter(m => m.getMaxBattles() === maxBattles && m.getBattleCount() >= maxBattles * 0.6).length === 0) + ? weight + : 0; + }; +} + +/** + * Used to check if the player has max of a given ball type in Classic + * @param ballType The {@linkcode PokeballType} being checked + * @returns boolean: true if the player has the maximum of a given ball type + */ +function hasMaximumBalls(ballType: PokeballType): boolean { + return globalScene.gameMode.isClassic && globalScene.pokeballCounts[ballType] >= MAX_PER_TYPE_POKEBALLS; +} diff --git a/src/modifier/modifier-pools.ts b/src/modifier/modifier-pools.ts new file mode 100644 index 00000000000..3396dca1f93 --- /dev/null +++ b/src/modifier/modifier-pools.ts @@ -0,0 +1,16 @@ +/** + * Contains modifier pools for different contexts in the game. + * Can be safely imported without worrying about circular dependencies. + */ + +import type { ModifierPool } from "#app/@types/modifier-types"; + +export const modifierPool: ModifierPool = {}; + +export const wildModifierPool: ModifierPool = {}; + +export const trainerModifierPool: ModifierPool = {}; + +export const enemyBuffModifierPool: ModifierPool = {}; + +export const dailyStarterModifierPool: ModifierPool = {}; diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index b37ed7dea3b..2c848c69e18 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -2,9 +2,9 @@ import { globalScene } from "#app/global-scene"; import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { tmPoolTiers, tmSpecies } from "#app/data/balance/tms"; import { getBerryEffectDescription, getBerryName } from "#app/data/berry"; -import { allMoves } from "#app/data/data-lists"; +import { allMoves, modifierTypes } from "#app/data/data-lists"; import { getNatureName, getNatureStatMultiplier } from "#app/data/nature"; -import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; +import { getPokeballCatchMultiplier, getPokeballName } from "#app/data/pokeball"; import { pokemonFormChanges, SpeciesFormChangeCondition } from "#app/data/pokemon-forms"; import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { FormChangeItem } from "#enums/form-change-item"; @@ -97,9 +97,8 @@ import { CriticalCatchChanceBoosterModifier, FieldEffectModifier, } from "#app/modifier/modifier"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import Overrides from "#app/overrides"; -import { Unlockables } from "#app/system/unlockables"; import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "#app/system/voucher"; import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party-ui-handler"; import PartyUiHandler from "#app/ui/party-ui-handler"; @@ -113,7 +112,6 @@ import { padInt, randSeedInt, } from "#app/utils/common"; -import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; import { MoveId } from "#enums/move-id"; @@ -127,18 +125,13 @@ import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; import { timedEventManager } from "#app/global-event-manager"; import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; +import { getModifierPoolForType, getModifierType } from "#app/utils/modifier-utils"; +import type { ModifierTypeFunc, WeightedModifierTypeWeightFunc } from "#app/@types/modifier-types"; const outputModifierData = false; const useMaxWeightForOutput = false; -export enum ModifierPoolType { - PLAYER, - WILD, - TRAINER, - ENEMY_BUFF, - DAILY_STARTER, -} - type NewModifierFunc = (type: ModifierType, args: any[]) => Modifier; export class ModifierType { @@ -150,6 +143,19 @@ export class ModifierType { public tier: ModifierTier; protected newModifierFunc: NewModifierFunc | null; + /** + * Checks if the modifier type is of a specific type + * @param modifierType - The type to check against + * @return Whether the modifier type is of the specified type + */ + public is(modifierType: K): this is ModifierTypeInstanceMap[K] { + const targetType = ModifierTypeConstructorMap[modifierType]; + if (!targetType) { + return false; + } + return this instanceof targetType; + } + constructor( localeKey: string | null, iconImage: string | null, @@ -218,7 +224,7 @@ export class ModifierType { * @param func */ withIdFromFunc(func: ModifierTypeFunc): ModifierType { - this.id = Object.keys(modifierTypes).find(k => modifierTypes[k] === func)!; // TODO: is this bang correct? + this.id = Object.keys(modifierTypeInitObj).find(k => modifierTypeInitObj[k] === func)!; // TODO: is this bang correct? return this; } @@ -299,7 +305,7 @@ export interface GeneratedPersistentModifierType { getPregenArgs(): any[]; } -class AddPokeballModifierType extends ModifierType { +export class AddPokeballModifierType extends ModifierType { private pokeballType: PokeballType; private count: number; @@ -329,7 +335,7 @@ class AddPokeballModifierType extends ModifierType { } } -class AddVoucherModifierType extends ModifierType { +export class AddVoucherModifierType extends ModifierType { private voucherType: VoucherType; private count: number; @@ -1631,7 +1637,7 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator { } } -class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { +export class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { constructor(isRareFormChangeItem: boolean) { super((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in FormChangeItem) { @@ -1794,52 +1800,7 @@ export class EnemyEndureChanceModifierType extends ModifierType { } } -export type ModifierTypeFunc = () => ModifierType; -type WeightedModifierTypeWeightFunc = (party: Pokemon[], rerollCount?: number) => number; - -/** - * High order function that returns a WeightedModifierTypeWeightFunc that will only be applied on - * classic and skip an ModifierType if current wave is greater or equal to the one passed down - * @param wave - Wave where we should stop showing the modifier - * @param defaultWeight - ModifierType default weight - * @returns A WeightedModifierTypeWeightFunc - */ -function skipInClassicAfterWave(wave: number, defaultWeight: number): WeightedModifierTypeWeightFunc { - return () => { - const gameMode = globalScene.gameMode; - const currentWave = globalScene.currentBattle.waveIndex; - return gameMode.isClassic && currentWave >= wave ? 0 : defaultWeight; - }; -} - -/** - * High order function that returns a WeightedModifierTypeWeightFunc that will only be applied on - * classic and it will skip a ModifierType if it is the last wave pull. - * @param defaultWeight ModifierType default weight - * @returns A WeightedModifierTypeWeightFunc - */ -function skipInLastClassicWaveOrDefault(defaultWeight: number): WeightedModifierTypeWeightFunc { - return skipInClassicAfterWave(199, defaultWeight); -} - -/** - * High order function that returns a WeightedModifierTypeWeightFunc to ensure Lures don't spawn on Classic 199 - * or if the lure still has over 60% of its duration left - * @param maxBattles The max battles the lure type in question lasts. 10 for green, 15 for Super, 30 for Max - * @param weight The desired weight for the lure when it does spawn - * @returns A WeightedModifierTypeWeightFunc - */ -function lureWeightFunc(maxBattles: number, weight: number): WeightedModifierTypeWeightFunc { - return () => { - const lures = globalScene.getModifiers(DoubleBattleChanceBoosterModifier); - return !(globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex === 199) && - (lures.length === 0 || - lures.filter(m => m.getMaxBattles() === maxBattles && m.getBattleCount() >= maxBattles * 0.6).length === 0) - ? weight - : 0; - }; -} -class WeightedModifierType { +export class WeightedModifierType { public modifierType: ModifierType; public weight: number | WeightedModifierTypeWeightFunc; public maxWeight: number | WeightedModifierTypeWeightFunc; @@ -1850,7 +1811,7 @@ class WeightedModifierType { maxWeight?: number | WeightedModifierTypeWeightFunc, ) { this.modifierType = modifierTypeFunc(); - this.modifierType.id = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifierTypeFunc)!; // TODO: is this bang correct? + this.modifierType.id = Object.keys(modifierTypeInitObj).find(k => modifierTypeInitObj[k] === modifierTypeFunc)!; // TODO: is this bang correct? this.weight = weight; this.maxWeight = maxWeight || (!(weight instanceof Function) ? weight : 0); } @@ -1870,39 +1831,39 @@ export type GeneratorModifierOverride = { count?: number; } & ( | { - name: keyof Pick; + name: keyof Pick; type?: SpeciesStatBoosterItem; } | { - name: keyof Pick; + name: keyof Pick; type?: TempBattleStat; } | { - name: keyof Pick; + name: keyof Pick; type?: Stat; } | { - name: keyof Pick; + name: keyof Pick; type?: Nature; } | { - name: keyof Pick; + name: keyof Pick; type?: PokemonType; } | { - name: keyof Pick; + name: keyof Pick; type?: BerryType; } | { - name: keyof Pick; + name: keyof Pick; type?: EvolutionItem; } | { - name: keyof Pick; + name: keyof Pick; type?: FormChangeItem; } | { - name: keyof Pick; + name: keyof Pick; type?: MoveId; } ); @@ -1910,9 +1871,9 @@ export type GeneratorModifierOverride = { /** Type used to construct modifiers and held items for overriding purposes. */ export type ModifierOverride = GeneratorModifierOverride | BaseModifierOverride; -export type ModifierTypeKeys = keyof typeof modifierTypes; +export type ModifierTypeKeys = keyof typeof modifierTypeInitObj; -export const modifierTypes = { +const modifierTypeInitObj = Object.freeze({ POKEBALL: () => new AddPokeballModifierType("pb", PokeballType.POKEBALL, 5), GREAT_BALL: () => new AddPokeballModifierType("gb", PokeballType.GREAT_BALL, 5), ULTRA_BALL: () => new AddPokeballModifierType("ub", PokeballType.ULTRA_BALL, 5), @@ -2421,748 +2382,18 @@ export const modifierTypes = { "golden_net", (type, _args) => new BoostBugSpawnModifier(type), ), -}; +}); -interface ModifierPool { +/** + * The initial set of modifier types, used to generate the modifier pool. + */ +export type ModifierTypes = typeof modifierTypeInitObj; + +export interface ModifierPool { [tier: string]: WeightedModifierType[]; } -/** - * Used to check if the player has max of a given ball type in Classic - * @param ballType The {@linkcode PokeballType} being checked - * @returns boolean: true if the player has the maximum of a given ball type - */ -function hasMaximumBalls(ballType: PokeballType): boolean { - return globalScene.gameMode.isClassic && globalScene.pokeballCounts[ballType] >= MAX_PER_TYPE_POKEBALLS; -} - -const modifierPool: ModifierPool = { - [ModifierTier.COMMON]: [ - new WeightedModifierType(modifierTypes.POKEBALL, () => (hasMaximumBalls(PokeballType.POKEBALL) ? 0 : 6), 6), - new WeightedModifierType(modifierTypes.RARE_CANDY, 2), - new WeightedModifierType( - modifierTypes.POTION, - (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min( - party.filter(p => p.getInverseHp() >= 10 && p.getHpRatio() <= 0.875 && !p.isFainted()).length, - 3, - ); - return thresholdPartyMemberCount * 3; - }, - 9, - ), - new WeightedModifierType( - modifierTypes.SUPER_POTION, - (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min( - party.filter(p => p.getInverseHp() >= 25 && p.getHpRatio() <= 0.75 && !p.isFainted()).length, - 3, - ); - return thresholdPartyMemberCount; - }, - 3, - ), - new WeightedModifierType( - modifierTypes.ETHER, - (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min( - party.filter( - p => - p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && - p - .getMoveset() - .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) - .length, - ).length, - 3, - ); - return thresholdPartyMemberCount * 3; - }, - 9, - ), - new WeightedModifierType( - modifierTypes.MAX_ETHER, - (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min( - party.filter( - p => - p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && - p - .getMoveset() - .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) - .length, - ).length, - 3, - ); - return thresholdPartyMemberCount; - }, - 3, - ), - new WeightedModifierType(modifierTypes.LURE, lureWeightFunc(10, 2)), - new WeightedModifierType(modifierTypes.TEMP_STAT_STAGE_BOOSTER, 4), - new WeightedModifierType(modifierTypes.BERRY, 2), - new WeightedModifierType(modifierTypes.TM_COMMON, 2), - ].map(m => { - m.setTier(ModifierTier.COMMON); - return m; - }), - [ModifierTier.GREAT]: [ - new WeightedModifierType(modifierTypes.GREAT_BALL, () => (hasMaximumBalls(PokeballType.GREAT_BALL) ? 0 : 6), 6), - new WeightedModifierType(modifierTypes.PP_UP, 2), - new WeightedModifierType( - modifierTypes.FULL_HEAL, - (party: Pokemon[]) => { - const statusEffectPartyMemberCount = Math.min( - party.filter( - p => - p.hp && - !!p.status && - !p.getHeldItems().some(i => { - if (i instanceof TurnStatusEffectModifier) { - return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect; - } - return false; - }), - ).length, - 3, - ); - return statusEffectPartyMemberCount * 6; - }, - 18, - ), - new WeightedModifierType( - modifierTypes.REVIVE, - (party: Pokemon[]) => { - const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3); - return faintedPartyMemberCount * 9; - }, - 27, - ), - new WeightedModifierType( - modifierTypes.MAX_REVIVE, - (party: Pokemon[]) => { - const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3); - return faintedPartyMemberCount * 3; - }, - 9, - ), - new WeightedModifierType( - modifierTypes.SACRED_ASH, - (party: Pokemon[]) => { - return party.filter(p => p.isFainted()).length >= Math.ceil(party.length / 2) ? 1 : 0; - }, - 1, - ), - new WeightedModifierType( - modifierTypes.HYPER_POTION, - (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min( - party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.625 && !p.isFainted()).length, - 3, - ); - return thresholdPartyMemberCount * 3; - }, - 9, - ), - new WeightedModifierType( - modifierTypes.MAX_POTION, - (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min( - party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.5 && !p.isFainted()).length, - 3, - ); - return thresholdPartyMemberCount; - }, - 3, - ), - new WeightedModifierType( - modifierTypes.FULL_RESTORE, - (party: Pokemon[]) => { - const statusEffectPartyMemberCount = Math.min( - party.filter( - p => - p.hp && - !!p.status && - !p.getHeldItems().some(i => { - if (i instanceof TurnStatusEffectModifier) { - return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect; - } - return false; - }), - ).length, - 3, - ); - const thresholdPartyMemberCount = Math.floor( - (Math.min(party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.5 && !p.isFainted()).length, 3) + - statusEffectPartyMemberCount) / - 2, - ); - return thresholdPartyMemberCount; - }, - 3, - ), - new WeightedModifierType( - modifierTypes.ELIXIR, - (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min( - party.filter( - p => - p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && - p - .getMoveset() - .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) - .length, - ).length, - 3, - ); - return thresholdPartyMemberCount * 3; - }, - 9, - ), - new WeightedModifierType( - modifierTypes.MAX_ELIXIR, - (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min( - party.filter( - p => - p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && - p - .getMoveset() - .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) - .length, - ).length, - 3, - ); - return thresholdPartyMemberCount; - }, - 3, - ), - new WeightedModifierType(modifierTypes.DIRE_HIT, 4), - new WeightedModifierType(modifierTypes.SUPER_LURE, lureWeightFunc(15, 4)), - new WeightedModifierType(modifierTypes.NUGGET, skipInLastClassicWaveOrDefault(5)), - new WeightedModifierType(modifierTypes.SPECIES_STAT_BOOSTER, 4), - new WeightedModifierType( - modifierTypes.EVOLUTION_ITEM, - () => { - return Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 15), 8); - }, - 8, - ), - new WeightedModifierType( - modifierTypes.MAP, - () => (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex < 180 ? 2 : 0), - 2, - ), - new WeightedModifierType(modifierTypes.SOOTHE_BELL, 2), - new WeightedModifierType(modifierTypes.TM_GREAT, 3), - new WeightedModifierType( - modifierTypes.MEMORY_MUSHROOM, - (party: Pokemon[]) => { - if (!party.find(p => p.getLearnableLevelMoves().length)) { - return 0; - } - const highestPartyLevel = party - .map(p => p.level) - .reduce((highestLevel: number, level: number) => Math.max(highestLevel, level), 1); - return Math.min(Math.ceil(highestPartyLevel / 20), 4); - }, - 4, - ), - new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3), - new WeightedModifierType(modifierTypes.TERA_SHARD, (party: Pokemon[]) => - party.filter( - p => - !(p.hasSpecies(SpeciesId.TERAPAGOS) || p.hasSpecies(SpeciesId.OGERPON) || p.hasSpecies(SpeciesId.SHEDINJA)), - ).length > 0 - ? 1 - : 0, - ), - new WeightedModifierType( - modifierTypes.DNA_SPLICERS, - (party: Pokemon[]) => { - if (party.filter(p => !p.fusionSpecies).length > 1) { - if (globalScene.gameMode.isSplicedOnly) { - return 4; - } - if (globalScene.gameMode.isClassic && timedEventManager.areFusionsBoosted()) { - return 2; - } - } - return 0; - }, - 4, - ), - new WeightedModifierType( - modifierTypes.VOUCHER, - (_party: Pokemon[], rerollCount: number) => (!globalScene.gameMode.isDaily ? Math.max(1 - rerollCount, 0) : 0), - 1, - ), - ].map(m => { - m.setTier(ModifierTier.GREAT); - return m; - }), - [ModifierTier.ULTRA]: [ - new WeightedModifierType(modifierTypes.ULTRA_BALL, () => (hasMaximumBalls(PokeballType.ULTRA_BALL) ? 0 : 15), 15), - new WeightedModifierType(modifierTypes.MAX_LURE, lureWeightFunc(30, 4)), - new WeightedModifierType(modifierTypes.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)), - new WeightedModifierType(modifierTypes.PP_MAX, 3), - new WeightedModifierType(modifierTypes.MINT, 4), - new WeightedModifierType( - modifierTypes.RARE_EVOLUTION_ITEM, - () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 15) * 4, 32), - 32, - ), - new WeightedModifierType( - modifierTypes.FORM_CHANGE_ITEM, - () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6, - 24, - ), - new WeightedModifierType(modifierTypes.AMULET_COIN, skipInLastClassicWaveOrDefault(3)), - new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => { - const { gameMode, gameData } = globalScene; - if (gameMode.isDaily || (!gameMode.isFreshStartChallenge() && gameData.isUnlocked(Unlockables.EVIOLITE))) { - return party.some(p => { - // Check if Pokemon's species (or fusion species, if applicable) can evolve or if they're G-Max'd - if ( - !p.isMax() && - (p.getSpeciesForm(true).speciesId in pokemonEvolutions || - (p.isFusion() && p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions)) - ) { - // Check if Pokemon is already holding an Eviolite - return !p.getHeldItems().some(i => i.type.id === "EVIOLITE"); - } - return false; - }) - ? 10 - : 0; - } - return 0; - }), - new WeightedModifierType(modifierTypes.RARE_SPECIES_STAT_BOOSTER, 12), - new WeightedModifierType( - modifierTypes.LEEK, - (party: Pokemon[]) => { - const checkedSpecies = [SpeciesId.FARFETCHD, SpeciesId.GALAR_FARFETCHD, SpeciesId.SIRFETCHD]; - // If a party member doesn't already have a Leek and is one of the relevant species, Leek can appear - return party.some( - p => - !p.getHeldItems().some(i => i instanceof SpeciesCritBoosterModifier) && - (checkedSpecies.includes(p.getSpeciesForm(true).speciesId) || - (p.isFusion() && checkedSpecies.includes(p.getFusionSpeciesForm(true).speciesId))), - ) - ? 12 - : 0; - }, - 12, - ), - new WeightedModifierType( - modifierTypes.TOXIC_ORB, - (party: Pokemon[]) => { - return party.some(p => { - const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); - - if (!isHoldingOrb) { - const moveset = p - .getMoveset(true) - .filter(m => !isNullOrUndefined(m)) - .map(m => m.moveId); - const canSetStatus = p.canSetStatus(StatusEffect.TOXIC, true, true, null, true); - - // Moves that take advantage of obtaining the actual status effect - const hasStatusMoves = [MoveId.FACADE, MoveId.PSYCHO_SHIFT].some(m => moveset.includes(m)); - // Moves that take advantage of being able to give the target a status orb - // TODO: Take moves (Trick, Fling, Switcheroo) from comment when they are implemented - const hasItemMoves = [ - /* MoveId.TRICK, MoveId.FLING, MoveId.SWITCHEROO */ - ].some(m => moveset.includes(m)); - - if (canSetStatus) { - // Abilities that take advantage of obtaining the actual status effect, separated based on specificity to the orb - const hasGeneralAbility = [ - AbilityId.QUICK_FEET, - AbilityId.GUTS, - AbilityId.MARVEL_SCALE, - AbilityId.MAGIC_GUARD, - ].some(a => p.hasAbility(a, false, true)); - const hasSpecificAbility = [AbilityId.TOXIC_BOOST, AbilityId.POISON_HEAL].some(a => - p.hasAbility(a, false, true), - ); - const hasOppositeAbility = [AbilityId.FLARE_BOOST].some(a => p.hasAbility(a, false, true)); - - return hasSpecificAbility || (hasGeneralAbility && !hasOppositeAbility) || hasStatusMoves; - } - return hasItemMoves; - } - - return false; - }) - ? 10 - : 0; - }, - 10, - ), - new WeightedModifierType( - modifierTypes.FLAME_ORB, - (party: Pokemon[]) => { - return party.some(p => { - const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); - - if (!isHoldingOrb) { - const moveset = p - .getMoveset(true) - .filter(m => !isNullOrUndefined(m)) - .map(m => m.moveId); - const canSetStatus = p.canSetStatus(StatusEffect.BURN, true, true, null, true); - - // Moves that take advantage of obtaining the actual status effect - const hasStatusMoves = [MoveId.FACADE, MoveId.PSYCHO_SHIFT].some(m => moveset.includes(m)); - // Moves that take advantage of being able to give the target a status orb - // TODO: Take moves (Trick, Fling, Switcheroo) from comment when they are implemented - const hasItemMoves = [ - /* MoveId.TRICK, MoveId.FLING, MoveId.SWITCHEROO */ - ].some(m => moveset.includes(m)); - - if (canSetStatus) { - // Abilities that take advantage of obtaining the actual status effect, separated based on specificity to the orb - const hasGeneralAbility = [ - AbilityId.QUICK_FEET, - AbilityId.GUTS, - AbilityId.MARVEL_SCALE, - AbilityId.MAGIC_GUARD, - ].some(a => p.hasAbility(a, false, true)); - const hasSpecificAbility = [AbilityId.FLARE_BOOST].some(a => p.hasAbility(a, false, true)); - const hasOppositeAbility = [AbilityId.TOXIC_BOOST, AbilityId.POISON_HEAL].some(a => - p.hasAbility(a, false, true), - ); - - return hasSpecificAbility || (hasGeneralAbility && !hasOppositeAbility) || hasStatusMoves; - } - return hasItemMoves; - } - - return false; - }) - ? 10 - : 0; - }, - 10, - ), - new WeightedModifierType( - modifierTypes.MYSTICAL_ROCK, - (party: Pokemon[]) => { - return party.some(p => { - let isHoldingMax = false; - for (const i of p.getHeldItems()) { - if (i.type.id === "MYSTICAL_ROCK") { - isHoldingMax = i.getStackCount() === i.getMaxStackCount(); - break; - } - } - - if (!isHoldingMax) { - const moveset = p.getMoveset(true).map(m => m.moveId); - - const hasAbility = [ - AbilityId.DROUGHT, - AbilityId.ORICHALCUM_PULSE, - AbilityId.DRIZZLE, - AbilityId.SAND_STREAM, - AbilityId.SAND_SPIT, - AbilityId.SNOW_WARNING, - AbilityId.ELECTRIC_SURGE, - AbilityId.HADRON_ENGINE, - AbilityId.PSYCHIC_SURGE, - AbilityId.GRASSY_SURGE, - AbilityId.SEED_SOWER, - AbilityId.MISTY_SURGE, - ].some(a => p.hasAbility(a, false, true)); - - const hasMoves = [ - MoveId.SUNNY_DAY, - MoveId.RAIN_DANCE, - MoveId.SANDSTORM, - MoveId.SNOWSCAPE, - MoveId.HAIL, - MoveId.CHILLY_RECEPTION, - MoveId.ELECTRIC_TERRAIN, - MoveId.PSYCHIC_TERRAIN, - MoveId.GRASSY_TERRAIN, - MoveId.MISTY_TERRAIN, - ].some(m => moveset.includes(m)); - - return hasAbility || hasMoves; - } - return false; - }) - ? 10 - : 0; - }, - 10, - ), - new WeightedModifierType(modifierTypes.REVIVER_SEED, 4), - new WeightedModifierType(modifierTypes.CANDY_JAR, skipInLastClassicWaveOrDefault(5)), - new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 9), - new WeightedModifierType(modifierTypes.TM_ULTRA, 11), - new WeightedModifierType(modifierTypes.RARER_CANDY, 4), - new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, skipInLastClassicWaveOrDefault(2)), - new WeightedModifierType(modifierTypes.IV_SCANNER, skipInLastClassicWaveOrDefault(4)), - new WeightedModifierType(modifierTypes.EXP_CHARM, skipInLastClassicWaveOrDefault(8)), - new WeightedModifierType(modifierTypes.EXP_SHARE, skipInLastClassicWaveOrDefault(10)), - new WeightedModifierType( - modifierTypes.TERA_ORB, - () => - !globalScene.gameMode.isClassic - ? Math.min(Math.max(Math.floor(globalScene.currentBattle.waveIndex / 50) * 2, 1), 4) - : 0, - 4, - ), - new WeightedModifierType(modifierTypes.QUICK_CLAW, 3), - new WeightedModifierType(modifierTypes.WIDE_LENS, 7), - ].map(m => { - m.setTier(ModifierTier.ULTRA); - return m; - }), - [ModifierTier.ROGUE]: [ - new WeightedModifierType(modifierTypes.ROGUE_BALL, () => (hasMaximumBalls(PokeballType.ROGUE_BALL) ? 0 : 16), 16), - new WeightedModifierType(modifierTypes.RELIC_GOLD, skipInLastClassicWaveOrDefault(2)), - new WeightedModifierType(modifierTypes.LEFTOVERS, 3), - new WeightedModifierType(modifierTypes.SHELL_BELL, 3), - new WeightedModifierType(modifierTypes.BERRY_POUCH, 4), - new WeightedModifierType(modifierTypes.GRIP_CLAW, 5), - new WeightedModifierType(modifierTypes.SCOPE_LENS, 4), - new WeightedModifierType(modifierTypes.BATON, 2), - new WeightedModifierType(modifierTypes.SOUL_DEW, 7), - new WeightedModifierType(modifierTypes.CATCHING_CHARM, () => (!globalScene.gameMode.isClassic ? 4 : 0), 4), - new WeightedModifierType(modifierTypes.ABILITY_CHARM, skipInClassicAfterWave(189, 6)), - new WeightedModifierType(modifierTypes.FOCUS_BAND, 5), - new WeightedModifierType(modifierTypes.KINGS_ROCK, 3), - new WeightedModifierType(modifierTypes.LOCK_CAPSULE, () => (globalScene.gameMode.isClassic ? 0 : 3)), - new WeightedModifierType(modifierTypes.SUPER_EXP_CHARM, skipInLastClassicWaveOrDefault(8)), - new WeightedModifierType( - modifierTypes.RARE_FORM_CHANGE_ITEM, - () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6, - 24, - ), - new WeightedModifierType( - modifierTypes.MEGA_BRACELET, - () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9, - 36, - ), - new WeightedModifierType( - modifierTypes.DYNAMAX_BAND, - () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9, - 36, - ), - new WeightedModifierType( - modifierTypes.VOUCHER_PLUS, - (_party: Pokemon[], rerollCount: number) => - !globalScene.gameMode.isDaily ? Math.max(3 - rerollCount * 1, 0) : 0, - 3, - ), - ].map(m => { - m.setTier(ModifierTier.ROGUE); - return m; - }), - [ModifierTier.MASTER]: [ - new WeightedModifierType(modifierTypes.MASTER_BALL, () => (hasMaximumBalls(PokeballType.MASTER_BALL) ? 0 : 24), 24), - new WeightedModifierType(modifierTypes.SHINY_CHARM, 14), - new WeightedModifierType(modifierTypes.HEALING_CHARM, 18), - new WeightedModifierType(modifierTypes.MULTI_LENS, 18), - new WeightedModifierType( - modifierTypes.VOUCHER_PREMIUM, - (_party: Pokemon[], rerollCount: number) => - !globalScene.gameMode.isDaily && !globalScene.gameMode.isEndless && !globalScene.gameMode.isSplicedOnly - ? Math.max(5 - rerollCount * 2, 0) - : 0, - 5, - ), - new WeightedModifierType( - modifierTypes.DNA_SPLICERS, - (party: Pokemon[]) => - !(globalScene.gameMode.isClassic && timedEventManager.areFusionsBoosted()) && - !globalScene.gameMode.isSplicedOnly && - party.filter(p => !p.fusionSpecies).length > 1 - ? 24 - : 0, - 24, - ), - new WeightedModifierType( - modifierTypes.MINI_BLACK_HOLE, - () => - globalScene.gameMode.isDaily || - (!globalScene.gameMode.isFreshStartChallenge() && globalScene.gameData.isUnlocked(Unlockables.MINI_BLACK_HOLE)) - ? 1 - : 0, - 1, - ), - ].map(m => { - m.setTier(ModifierTier.MASTER); - return m; - }), -}; - -const wildModifierPool: ModifierPool = { - [ModifierTier.COMMON]: [new WeightedModifierType(modifierTypes.BERRY, 1)].map(m => { - m.setTier(ModifierTier.COMMON); - return m; - }), - [ModifierTier.GREAT]: [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 1)].map(m => { - m.setTier(ModifierTier.GREAT); - return m; - }), - [ModifierTier.ULTRA]: [ - new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), - new WeightedModifierType(modifierTypes.WHITE_HERB, 0), - ].map(m => { - m.setTier(ModifierTier.ULTRA); - return m; - }), - [ModifierTier.ROGUE]: [new WeightedModifierType(modifierTypes.LUCKY_EGG, 4)].map(m => { - m.setTier(ModifierTier.ROGUE); - return m; - }), - [ModifierTier.MASTER]: [new WeightedModifierType(modifierTypes.GOLDEN_EGG, 1)].map(m => { - m.setTier(ModifierTier.MASTER); - return m; - }), -}; - -const trainerModifierPool: ModifierPool = { - [ModifierTier.COMMON]: [ - new WeightedModifierType(modifierTypes.BERRY, 8), - new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3), - ].map(m => { - m.setTier(ModifierTier.COMMON); - return m; - }), - [ModifierTier.GREAT]: [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3)].map(m => { - m.setTier(ModifierTier.GREAT); - return m; - }), - [ModifierTier.ULTRA]: [ - new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), - new WeightedModifierType(modifierTypes.WHITE_HERB, 0), - ].map(m => { - m.setTier(ModifierTier.ULTRA); - return m; - }), - [ModifierTier.ROGUE]: [ - new WeightedModifierType(modifierTypes.FOCUS_BAND, 2), - new WeightedModifierType(modifierTypes.LUCKY_EGG, 4), - new WeightedModifierType(modifierTypes.QUICK_CLAW, 1), - new WeightedModifierType(modifierTypes.GRIP_CLAW, 1), - new WeightedModifierType(modifierTypes.WIDE_LENS, 1), - ].map(m => { - m.setTier(ModifierTier.ROGUE); - return m; - }), - [ModifierTier.MASTER]: [ - new WeightedModifierType(modifierTypes.KINGS_ROCK, 1), - new WeightedModifierType(modifierTypes.LEFTOVERS, 1), - new WeightedModifierType(modifierTypes.SHELL_BELL, 1), - new WeightedModifierType(modifierTypes.SCOPE_LENS, 1), - ].map(m => { - m.setTier(ModifierTier.MASTER); - return m; - }), -}; - -const enemyBuffModifierPool: ModifierPool = { - [ModifierTier.COMMON]: [ - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 9), - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 9), - new WeightedModifierType(modifierTypes.ENEMY_ATTACK_POISON_CHANCE, 3), - new WeightedModifierType(modifierTypes.ENEMY_ATTACK_PARALYZE_CHANCE, 3), - new WeightedModifierType(modifierTypes.ENEMY_ATTACK_BURN_CHANCE, 3), - new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 9), - new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 4), - new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 1), - ].map(m => { - m.setTier(ModifierTier.COMMON); - return m; - }), - [ModifierTier.GREAT]: [ - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 5), - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 5), - new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 5), - new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 5), - new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 1), - ].map(m => { - m.setTier(ModifierTier.GREAT); - return m; - }), - [ModifierTier.ULTRA]: [ - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 10), - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 10), - new WeightedModifierType(modifierTypes.ENEMY_HEAL, 10), - new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 10), - new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 10), - new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 5), - ].map(m => { - m.setTier(ModifierTier.ULTRA); - return m; - }), - [ModifierTier.ROGUE]: [].map((m: WeightedModifierType) => { - m.setTier(ModifierTier.ROGUE); - return m; - }), - [ModifierTier.MASTER]: [].map((m: WeightedModifierType) => { - m.setTier(ModifierTier.MASTER); - return m; - }), -}; - -const dailyStarterModifierPool: ModifierPool = { - [ModifierTier.COMMON]: [ - new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 1), - new WeightedModifierType(modifierTypes.BERRY, 3), - ].map(m => { - m.setTier(ModifierTier.COMMON); - return m; - }), - [ModifierTier.GREAT]: [new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 5)].map(m => { - m.setTier(ModifierTier.GREAT); - return m; - }), - [ModifierTier.ULTRA]: [ - new WeightedModifierType(modifierTypes.REVIVER_SEED, 4), - new WeightedModifierType(modifierTypes.SOOTHE_BELL, 1), - new WeightedModifierType(modifierTypes.SOUL_DEW, 1), - new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, 1), - ].map(m => { - m.setTier(ModifierTier.ULTRA); - return m; - }), - [ModifierTier.ROGUE]: [ - new WeightedModifierType(modifierTypes.GRIP_CLAW, 5), - new WeightedModifierType(modifierTypes.BATON, 2), - new WeightedModifierType(modifierTypes.FOCUS_BAND, 5), - new WeightedModifierType(modifierTypes.QUICK_CLAW, 3), - new WeightedModifierType(modifierTypes.KINGS_ROCK, 3), - ].map(m => { - m.setTier(ModifierTier.ROGUE); - return m; - }), - [ModifierTier.MASTER]: [ - new WeightedModifierType(modifierTypes.LEFTOVERS, 1), - new WeightedModifierType(modifierTypes.SHELL_BELL, 1), - ].map(m => { - m.setTier(ModifierTier.MASTER); - return m; - }), -}; - -export function getModifierType(modifierTypeFunc: ModifierTypeFunc): ModifierType { - const modifierType = modifierTypeFunc(); - if (!modifierType.id) { - modifierType.id = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifierTypeFunc)!; // TODO: is this bang correct? - } - return modifierType; -} +const modifierPool: ModifierPool = {}; let modifierPoolThresholds = {}; let ignoredPoolIndexes = {}; @@ -3179,28 +2410,6 @@ let enemyBuffModifierPoolThresholds = {}; // biome-ignore lint/correctness/noUnusedVariables: TODO explain why this is marked as OK let enemyBuffIgnoredPoolIndexes = {}; -export function getModifierPoolForType(poolType: ModifierPoolType): ModifierPool { - let pool: ModifierPool; - switch (poolType) { - case ModifierPoolType.PLAYER: - pool = modifierPool; - break; - case ModifierPoolType.WILD: - pool = wildModifierPool; - break; - case ModifierPoolType.TRAINER: - pool = trainerModifierPool; - break; - case ModifierPoolType.ENEMY_BUFF: - pool = enemyBuffModifierPool; - break; - case ModifierPoolType.DAILY_STARTER: - pool = dailyStarterModifierPool; - break; - } - return pool; -} - const tierWeights = [768 / 1024, 195 / 1024, 48 / 1024, 12 / 1024, 1 / 1024]; /** * Allows a unit test to check if an item exists in the Modifier Pool. Checks the pool directly, rather than attempting to reroll for the item. @@ -3314,7 +2523,7 @@ export interface CustomModifierSettings { } export function getModifierTypeFuncById(id: string): ModifierTypeFunc { - return modifierTypes[id]; + return modifierTypeInitObj[id]; } /** @@ -3367,12 +2576,12 @@ export function getPlayerModifierTypeOptions( customModifierSettings.guaranteedModifierTypeFuncs.length > 0 ) { customModifierSettings.guaranteedModifierTypeFuncs!.forEach((mod, _i) => { - const modifierId = Object.keys(modifierTypes).find(k => modifierTypes[k] === mod) as string; - let guaranteedMod: ModifierType = modifierTypes[modifierId]?.(); + const modifierId = Object.keys(modifierTypeInitObj).find(k => modifierTypeInitObj[k] === mod) as string; + let guaranteedMod: ModifierType = modifierTypeInitObj[modifierId]?.(); // Populates item id and tier guaranteedMod = guaranteedMod - .withIdFromFunc(modifierTypes[modifierId]) + .withIdFromFunc(modifierTypeInitObj[modifierId]) .withTierFromPool(ModifierPoolType.PLAYER, party); const modType = @@ -3451,7 +2660,7 @@ export function overridePlayerModifierTypeOptions(options: ModifierTypeOption[], const minLength = Math.min(options.length, Overrides.ITEM_REWARD_OVERRIDE.length); for (let i = 0; i < minLength; i++) { const override: ModifierOverride = Overrides.ITEM_REWARD_OVERRIDE[i]; - const modifierFunc = modifierTypes[override.name]; + const modifierFunc = modifierTypeInitObj[override.name]; let modifierType: ModifierType | null = modifierFunc(); if (modifierType instanceof ModifierTypeGenerator) { @@ -3472,29 +2681,29 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: number, baseC const options = [ [ - new ModifierTypeOption(modifierTypes.POTION(), 0, baseCost * 0.2), - new ModifierTypeOption(modifierTypes.ETHER(), 0, baseCost * 0.4), - new ModifierTypeOption(modifierTypes.REVIVE(), 0, baseCost * 2), + new ModifierTypeOption(modifierTypeInitObj.POTION(), 0, baseCost * 0.2), + new ModifierTypeOption(modifierTypeInitObj.ETHER(), 0, baseCost * 0.4), + new ModifierTypeOption(modifierTypeInitObj.REVIVE(), 0, baseCost * 2), ], [ - new ModifierTypeOption(modifierTypes.SUPER_POTION(), 0, baseCost * 0.45), - new ModifierTypeOption(modifierTypes.FULL_HEAL(), 0, baseCost), + new ModifierTypeOption(modifierTypeInitObj.SUPER_POTION(), 0, baseCost * 0.45), + new ModifierTypeOption(modifierTypeInitObj.FULL_HEAL(), 0, baseCost), ], [ - new ModifierTypeOption(modifierTypes.ELIXIR(), 0, baseCost), - new ModifierTypeOption(modifierTypes.MAX_ETHER(), 0, baseCost), + new ModifierTypeOption(modifierTypeInitObj.ELIXIR(), 0, baseCost), + new ModifierTypeOption(modifierTypeInitObj.MAX_ETHER(), 0, baseCost), ], [ - new ModifierTypeOption(modifierTypes.HYPER_POTION(), 0, baseCost * 0.8), - new ModifierTypeOption(modifierTypes.MAX_REVIVE(), 0, baseCost * 2.75), - new ModifierTypeOption(modifierTypes.MEMORY_MUSHROOM(), 0, baseCost * 4), + new ModifierTypeOption(modifierTypeInitObj.HYPER_POTION(), 0, baseCost * 0.8), + new ModifierTypeOption(modifierTypeInitObj.MAX_REVIVE(), 0, baseCost * 2.75), + new ModifierTypeOption(modifierTypeInitObj.MEMORY_MUSHROOM(), 0, baseCost * 4), ], [ - new ModifierTypeOption(modifierTypes.MAX_POTION(), 0, baseCost * 1.5), - new ModifierTypeOption(modifierTypes.MAX_ELIXIR(), 0, baseCost * 2.5), + new ModifierTypeOption(modifierTypeInitObj.MAX_POTION(), 0, baseCost * 1.5), + new ModifierTypeOption(modifierTypeInitObj.MAX_ELIXIR(), 0, baseCost * 2.5), ], - [new ModifierTypeOption(modifierTypes.FULL_RESTORE(), 0, baseCost * 2.25)], - [new ModifierTypeOption(modifierTypes.SACRED_ASH(), 0, baseCost * 10)], + [new ModifierTypeOption(modifierTypeInitObj.FULL_RESTORE(), 0, baseCost * 2.25)], + [new ModifierTypeOption(modifierTypeInitObj.SACRED_ASH(), 0, baseCost * 10)], ]; return options.slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)).flat(); } @@ -3549,7 +2758,7 @@ export function getEnemyModifierTypesForWave( ?.type as PokemonHeldItemModifierType, ); if (!(waveIndex % 1000)) { - ret.push(getModifierType(modifierTypes.MINI_BLACK_HOLE) as PokemonHeldItemModifierType); + ret.push(getModifierType(modifierTypeInitObj.MINI_BLACK_HOLE) as PokemonHeldItemModifierType); } return ret; } @@ -3779,3 +2988,30 @@ export function getLuckTextTint(luckValue: number): number { } return getModifierTierTextTint(modifierTier); } + +export function initModifierTypes() { + for (const [key, value] of Object.entries(modifierTypeInitObj)) { + modifierTypes[key] = value; + } +} + +// TODO: If necessary, add the rest of the modifier types here. +// For now, doing the minimal work until the modifier rework lands. +const ModifierTypeConstructorMap = Object.freeze({ + ModifierTypeGenerator, + PokemonHeldItemModifierType, +}); + +/** + * Map of of modifier type strings to their constructor type + */ +export type ModifierTypeConstructorMap = typeof ModifierTypeConstructorMap; + +/** + * Map of modifier type strings to their instance type + */ +export type ModifierTypeInstanceMap = { + [K in keyof ModifierTypeConstructorMap]: InstanceType; +}; + +export type ModifierTypeString = keyof ModifierTypeConstructorMap; diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index bfea061b1e8..e11f2c07ce8 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1,12 +1,13 @@ import { FusionSpeciesFormEvolution, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry"; import { getLevelTotalExp } from "#app/data/exp"; -import { allMoves } from "#app/data/data-lists"; +import { allMoves, modifierTypes } from "#app/data/data-lists"; import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import type { FormChangeItem } from "#enums/form-change-item"; import { getStatusEffectHealText } from "#app/data/status-effect"; -import Pokemon, { type PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; import { LearnMoveType } from "#enums/learn-move-type"; @@ -24,33 +25,26 @@ import { type PermanentStat, type TempBattleStat, BATTLE_STATS, Stat, TEMP_BATTL import { StatusEffect } from "#enums/status-effect"; import type { PokemonType } from "#enums/pokemon-type"; import i18next from "i18next"; -import { - type DoubleBattleChanceBoosterModifierType, - type EvolutionItemModifierType, - type FormChangeItemModifierType, - type ModifierOverride, - type ModifierType, - type PokemonBaseStatTotalModifierType, - type PokemonExpBoosterModifierType, - type PokemonFriendshipBoosterModifierType, - type PokemonMoveAccuracyBoosterModifierType, - type PokemonMultiHitModifierType, - type TerastallizeModifierType, - type TmModifierType, - getModifierType, - ModifierTypeGenerator, - modifierTypes, - PokemonHeldItemModifierType, +import type { + DoubleBattleChanceBoosterModifierType, + EvolutionItemModifierType, + FormChangeItemModifierType, + ModifierOverride, + ModifierType, + PokemonBaseStatTotalModifierType, + PokemonExpBoosterModifierType, + PokemonFriendshipBoosterModifierType, + PokemonMoveAccuracyBoosterModifierType, + PokemonMultiHitModifierType, + TerastallizeModifierType, + TmModifierType, } from "./modifier-type"; +import { getModifierType } from "#app/utils/modifier-utils"; import { Color, ShadowColor } from "#enums/color"; import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters"; -import { - applyAbAttrs, - applyPostItemLostAbAttrs, - CommanderAbAttr, - PostItemLostAbAttr, -} from "#app/data/abilities/ability"; +import { applyAbAttrs, applyPostItemLostAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; +import type { ModifierInstanceMap, ModifierString } from "#app/@types/modifier-types"; export type ModifierPredicate = (modifier: Modifier) => boolean; @@ -164,6 +158,23 @@ export abstract class Modifier { this.type = type; } + /** + * Return whether this modifier is of the given class + * + * @remarks + * Used to avoid requiring the caller to have imported the specific modifier class, avoiding circular dependencies. + * + * @param modifier - The modifier to check against + * @returns Whether the modiifer is an instance of the given type + */ + public is(modifier: T): this is ModifierInstanceMap[T] { + const targetModifier = ModifierClassMap[modifier]; + if (!targetModifier) { + return false; + } + return this instanceof targetModifier; + } + match(_modifier: Modifier): boolean { return false; } @@ -188,6 +199,11 @@ export abstract class PersistentModifier extends Modifier { public stackCount: number; public virtualStackCount: number; + /** This field does not exist at runtime and must not be used. + * Its sole purpose is to ensure that typescript is able to properly narrow when the `is` method is called. + */ + private declare _: never; + constructor(type: ModifierType, stackCount = 1) { super(type); this.stackCount = stackCount; @@ -1593,7 +1609,7 @@ export class BypassSpeedChanceModifier extends PokemonHeldItemModifier { doBypassSpeed.value = true; const isCommandFight = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]?.command === Command.FIGHT; - const hasQuickClaw = this.type instanceof PokemonHeldItemModifierType && this.type.id === "QUICK_CLAW"; + const hasQuickClaw = this.type.is("PokemonHeldItemModifierType") && this.type.id === "QUICK_CLAW"; if (isCommandFight && hasQuickClaw) { globalScene.phaseManager.queueMessage( @@ -1877,7 +1893,7 @@ export class BerryModifier extends PokemonHeldItemModifier { // munch the berry and trigger unburden-like effects getBerryEffectFunc(this.berryType)(pokemon); - applyPostItemLostAbAttrs(PostItemLostAbAttr, pokemon, false); + applyPostItemLostAbAttrs("PostItemLostAbAttr", pokemon, false); // Update berry eaten trackers for Belch, Harvest, Cud Chew, etc. // Don't recover it if we proc berry pouch (no item duplication) @@ -1965,7 +1981,7 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier { // Reapply Commander on the Pokemon's side of the field, if applicable const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); for (const p of field) { - applyAbAttrs(CommanderAbAttr, p, null, false); + applyAbAttrs("CommanderAbAttr", p, null, false); } return true; } @@ -3215,7 +3231,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { * @returns the opponents of the source {@linkcode Pokemon} */ getTargets(pokemon?: Pokemon, ..._args: unknown[]): Pokemon[] { - return pokemon instanceof Pokemon ? pokemon.getOpponents() : []; + return pokemon?.getOpponents?.() ?? []; } /** @@ -3787,7 +3803,7 @@ export function overrideModifiers(isPlayer = true): void { const modifierFunc = modifierTypes[item.name]; let modifierType: ModifierType | null = modifierFunc(); - if (modifierType instanceof ModifierTypeGenerator) { + if (modifierType.is("ModifierTypeGenerator")) { const pregenArgs = "type" in item && item.type !== null ? [item.type] : undefined; modifierType = modifierType.generateType([], pregenArgs); } @@ -3829,7 +3845,7 @@ export function overrideHeldItems(pokemon: Pokemon, isPlayer = true): void { let modifierType: ModifierType | null = modifierFunc(); const qty = item.count || 1; - if (modifierType instanceof ModifierTypeGenerator) { + if (modifierType.is("ModifierTypeGenerator")) { const pregenArgs = "type" in item && item.type !== null ? [item.type] : undefined; modifierType = modifierType.generateType([], pregenArgs); } @@ -3847,3 +3863,102 @@ export function overrideHeldItems(pokemon: Pokemon, isPlayer = true): void { } } } + +/** + * Private map from modifier strings to their constructors. + * + * @remarks + * Used for {@linkcode Modifier.is} to check if a modifier is of a certain type without + * requiring modifier types to be imported in every file. + */ +const ModifierClassMap = Object.freeze({ + PersistentModifier, + ConsumableModifier, + AddPokeballModifier, + AddVoucherModifier, + LapsingPersistentModifier, + DoubleBattleChanceBoosterModifier, + TempStatStageBoosterModifier, + TempCritBoosterModifier, + MapModifier, + MegaEvolutionAccessModifier, + GigantamaxAccessModifier, + TerastallizeAccessModifier, + PokemonHeldItemModifier, + LapsingPokemonHeldItemModifier, + BaseStatModifier, + EvoTrackerModifier, + PokemonBaseStatTotalModifier, + PokemonBaseStatFlatModifier, + PokemonIncrementingStatModifier, + StatBoosterModifier, + SpeciesStatBoosterModifier, + CritBoosterModifier, + SpeciesCritBoosterModifier, + AttackTypeBoosterModifier, + SurviveDamageModifier, + BypassSpeedChanceModifier, + FlinchChanceModifier, + TurnHealModifier, + TurnStatusEffectModifier, + HitHealModifier, + LevelIncrementBoosterModifier, + BerryModifier, + PreserveBerryModifier, + PokemonInstantReviveModifier, + ResetNegativeStatStageModifier, + FieldEffectModifier, + ConsumablePokemonModifier, + TerrastalizeModifier, + PokemonHpRestoreModifier, + PokemonStatusHealModifier, + ConsumablePokemonMoveModifier, + PokemonPpRestoreModifier, + PokemonAllMovePpRestoreModifier, + PokemonPpUpModifier, + PokemonNatureChangeModifier, + PokemonLevelIncrementModifier, + TmModifier, + RememberMoveModifier, + EvolutionItemModifier, + FusePokemonModifier, + MultipleParticipantExpBonusModifier, + HealingBoosterModifier, + ExpBoosterModifier, + PokemonExpBoosterModifier, + ExpShareModifier, + ExpBalanceModifier, + PokemonFriendshipBoosterModifier, + PokemonNatureWeightModifier, + PokemonMoveAccuracyBoosterModifier, + PokemonMultiHitModifier, + PokemonFormChangeItemModifier, + MoneyRewardModifier, + DamageMoneyRewardModifier, + MoneyInterestModifier, + HiddenAbilityRateBoosterModifier, + ShinyRateBoosterModifier, + CriticalCatchChanceBoosterModifier, + LockModifierTiersModifier, + HealShopCostModifier, + BoostBugSpawnModifier, + SwitchEffectTransferModifier, + HeldItemTransferModifier, + TurnHeldItemTransferModifier, + ContactHeldItemTransferChanceModifier, + IvScannerModifier, + ExtraModifierModifier, + TempExtraModifierModifier, + EnemyPersistentModifier, + EnemyDamageMultiplierModifier, + EnemyDamageBoosterModifier, + EnemyDamageReducerModifier, + EnemyTurnHealModifier, + EnemyAttackStatusEffectChanceModifier, + EnemyStatusEffectHealChanceModifier, + EnemyEndureChanceModifier, + EnemyFusionChanceModifier, + MoneyMultiplierModifier, +}); + +export type ModifierConstructorMap = typeof ModifierClassMap; diff --git a/src/overrides.ts b/src/overrides.ts index 1fbca8e8de1..b5df1073c28 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -4,7 +4,7 @@ import { Gender } from "#app/data/gender"; import { FormChangeItem } from "#enums/form-change-item"; import { type ModifierOverride } from "#app/modifier/modifier-type"; import { Variant } from "#app/sprites/variant"; -import { Unlockables } from "#app/system/unlockables"; +import { Unlockables } from "#enums/unlockables"; import { AbilityId } from "#enums/ability-id"; import { BattleType } from "#enums/battle-type"; import { BerryType } from "#enums/berry-type"; diff --git a/src/phases/add-enemy-buff-modifier-phase.ts b/src/phases/add-enemy-buff-modifier-phase.ts index 28eaf0dc4df..185464670d3 100644 --- a/src/phases/add-enemy-buff-modifier-phase.ts +++ b/src/phases/add-enemy-buff-modifier-phase.ts @@ -1,9 +1,9 @@ -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { regenerateModifierPoolThresholds, - ModifierPoolType, getEnemyBuffModifierForWave, } from "#app/modifier/modifier-type"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import { EnemyPersistentModifier } from "#app/modifier/modifier"; import { Phase } from "#app/phase"; import { globalScene } from "#app/global-scene"; diff --git a/src/phases/attempt-run-phase.ts b/src/phases/attempt-run-phase.ts index 525be8c21ab..5e24f3474a6 100644 --- a/src/phases/attempt-run-phase.ts +++ b/src/phases/attempt-run-phase.ts @@ -1,9 +1,4 @@ -import { - applyAbAttrs, - applyPreLeaveFieldAbAttrs, - PreLeaveFieldAbAttr, - RunSuccessAbAttr, -} from "#app/data/abilities/ability"; +import { applyAbAttrs, applyPreLeaveFieldAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { Stat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon"; @@ -30,10 +25,10 @@ export class AttemptRunPhase extends PokemonPhase { this.attemptRunAway(playerField, enemyField, escapeChance); - applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, false, escapeChance); + applyAbAttrs("RunSuccessAbAttr", playerPokemon, null, false, escapeChance); if (playerPokemon.randBattleSeedInt(100) < escapeChance.value && !this.forceFailEscape) { - enemyField.forEach(enemyPokemon => applyPreLeaveFieldAbAttrs(PreLeaveFieldAbAttr, enemyPokemon)); + enemyField.forEach(enemyPokemon => applyPreLeaveFieldAbAttrs("PreLeaveFieldAbAttr", enemyPokemon)); globalScene.playSound("se/flee"); globalScene.phaseManager.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500); diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index e169de58cb3..e1bf4c2296c 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { applyPostBattleAbAttrs, PostBattleAbAttr } from "#app/data/abilities/ability"; +import { applyPostBattleAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier"; import { BattlePhase } from "./battle-phase"; @@ -65,7 +65,7 @@ export class BattleEndPhase extends BattlePhase { } for (const pokemon of globalScene.getPokemonAllowedInBattle()) { - applyPostBattleAbAttrs(PostBattleAbAttr, pokemon, false, this.isVictory); + applyPostBattleAbAttrs("PostBattleAbAttr", pokemon, false, this.isVictory); } if (globalScene.currentBattle.moneyScattered) { diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index cc990d1e2ae..c126f3306b9 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -1,9 +1,4 @@ -import { - applyAbAttrs, - PreventBerryUseAbAttr, - HealFromBerryUseAbAttr, - RepeatBerryNextTurnAbAttr, -} from "#app/data/abilities/ability"; +import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { CommonAnim } from "#enums/move-anims-common"; import { BerryUsedEvent } from "#app/events/battle-scene"; import { getPokemonNameWithAffix } from "#app/messages"; @@ -25,7 +20,7 @@ export class BerryPhase extends FieldPhase { this.executeForAll(pokemon => { this.eatBerries(pokemon); - applyAbAttrs(RepeatBerryNextTurnAbAttr, pokemon, null); + applyAbAttrs("RepeatBerryNextTurnAbAttr", pokemon, null); }); this.end(); @@ -47,7 +42,7 @@ export class BerryPhase extends FieldPhase { // TODO: If both opponents on field have unnerve, which one displays its message? const cancelled = new BooleanHolder(false); - pokemon.getOpponents().forEach(opp => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled)); + pokemon.getOpponents().forEach(opp => applyAbAttrs("PreventBerryUseAbAttr", opp, cancelled)); if (cancelled.value) { globalScene.phaseManager.queueMessage( i18next.t("abilityTriggers:preventBerryUse", { @@ -75,6 +70,6 @@ export class BerryPhase extends FieldPhase { globalScene.updateModifiers(pokemon.isPlayer()); // AbilityId.CHEEK_POUCH only works once per round of nom noms - applyAbAttrs(HealFromBerryUseAbAttr, pokemon, new BooleanHolder(false)); + applyAbAttrs("HealFromBerryUseAbAttr", pokemon, new BooleanHolder(false)); } } diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index 74623f947ee..f2c23384627 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -2,12 +2,7 @@ import { BattlerIndex } from "#enums/battler-index"; import { BattleType } from "#enums/battle-type"; import { globalScene } from "#app/global-scene"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; -import { - applyAbAttrs, - SyncEncounterNatureAbAttr, - applyPreSummonAbAttrs, - PreSummonAbAttr, -} from "#app/data/abilities/ability"; +import { applyAbAttrs, applyPreSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { initEncounterAnims, loadEncounterAnimAssets } from "#app/data/battle-anims"; import { getCharVariantFromDialogue } from "#app/data/dialogue"; import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; @@ -20,7 +15,8 @@ import type Pokemon from "#app/field/pokemon"; import { FieldPosition } from "#enums/field-position"; import { getPokemonNameWithAffix } from "#app/messages"; import { BoostBugSpawnModifier, IvScannerModifier, TurnHeldItemTransferModifier } from "#app/modifier/modifier"; -import { ModifierPoolType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; +import { regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import Overrides from "#app/overrides"; import { BattlePhase } from "#app/phases/battle-phase"; import { achvs } from "#app/system/achv"; @@ -34,7 +30,7 @@ import { PlayerGender } from "#enums/player-gender"; import { SpeciesId } from "#enums/species-id"; import { overrideHeldItems, overrideModifiers } from "#app/modifier/modifier"; import i18next from "i18next"; -import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters"; +import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/constants"; import { getNatureName } from "#app/data/nature"; export class EncounterPhase extends BattlePhase { @@ -132,7 +128,7 @@ export class EncounterPhase extends BattlePhase { .slice(0, !battle.double ? 1 : 2) .reverse() .forEach(playerPokemon => { - applyAbAttrs(SyncEncounterNatureAbAttr, playerPokemon, null, false, battle.enemyParty[e]); + applyAbAttrs("SyncEncounterNatureAbAttr", playerPokemon, null, false, battle.enemyParty[e]); }); } } @@ -253,7 +249,7 @@ export class EncounterPhase extends BattlePhase { if (e < (battle.double ? 2 : 1)) { if (battle.battleType === BattleType.WILD) { for (const pokemon of globalScene.getField()) { - applyPreSummonAbAttrs(PreSummonAbAttr, pokemon, []); + applyPreSummonAbAttrs("PreSummonAbAttr", pokemon, []); } globalScene.field.add(enemyPokemon); battle.seenEnemyPartyMemberIds.add(enemyPokemon.id); diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index ca23b20be12..675a198d096 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -5,10 +5,7 @@ import { applyPostFaintAbAttrs, applyPostKnockOutAbAttrs, applyPostVictoryAbAttrs, - PostFaintAbAttr, - PostKnockOutAbAttr, - PostVictoryAbAttr, -} from "#app/data/abilities/ability"; +} from "#app/data/abilities/apply-ab-attrs"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { battleSpecDialogue } from "#app/data/dialogue"; import { allMoves } from "#app/data/data-lists"; @@ -123,7 +120,7 @@ export class FaintPhase extends PokemonPhase { if (pokemon.turnData.attacksReceived?.length) { const lastAttack = pokemon.turnData.attacksReceived[0]; applyPostFaintAbAttrs( - PostFaintAbAttr, + "PostFaintAbAttr", pokemon, globalScene.getPokemonById(lastAttack.sourceId)!, new PokemonMove(lastAttack.move).getMove(), @@ -131,18 +128,18 @@ export class FaintPhase extends PokemonPhase { ); // TODO: is this bang correct? } else { //If killed by indirect damage, apply post-faint abilities without providing a last move - applyPostFaintAbAttrs(PostFaintAbAttr, pokemon); + applyPostFaintAbAttrs("PostFaintAbAttr", pokemon); } const alivePlayField = globalScene.getField(true); for (const p of alivePlayField) { - applyPostKnockOutAbAttrs(PostKnockOutAbAttr, p, pokemon); + applyPostKnockOutAbAttrs("PostKnockOutAbAttr", p, pokemon); } if (pokemon.turnData.attacksReceived?.length) { const defeatSource = this.source; if (defeatSource?.isOnField()) { - applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource); + applyPostVictoryAbAttrs("PostVictoryAbAttr", defeatSource); const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move]; const pvattrs = pvmove.getAttrs("PostVictoryStatStageChangeAttr"); if (pvattrs.length) { diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts index 5fabc5cee81..2cd8bf120b5 100644 --- a/src/phases/game-over-phase.ts +++ b/src/phases/game-over-phase.ts @@ -7,11 +7,11 @@ import type PokemonSpecies from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { trainerConfigs } from "#app/data/trainers/trainer-config"; import type Pokemon from "#app/field/pokemon"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { BattlePhase } from "#app/phases/battle-phase"; import type { EndCardPhase } from "#app/phases/end-card-phase"; import { achvs, ChallengeAchv } from "#app/system/achv"; -import { Unlockables } from "#app/system/unlockables"; +import { Unlockables } from "#enums/unlockables"; import { UiMode } from "#enums/ui-mode"; import { isLocal, isLocalServerConnected } from "#app/utils/common"; import { PlayerGender } from "#enums/player-gender"; diff --git a/src/phases/modifier-reward-phase.ts b/src/phases/modifier-reward-phase.ts index 83bd8704f59..29bbe215fc0 100644 --- a/src/phases/modifier-reward-phase.ts +++ b/src/phases/modifier-reward-phase.ts @@ -1,6 +1,7 @@ import { globalScene } from "#app/global-scene"; -import type { ModifierType, ModifierTypeFunc } from "#app/modifier/modifier-type"; -import { getModifierType } from "#app/modifier/modifier-type"; +import type { ModifierType } from "#app/modifier/modifier-type"; +import type { ModifierTypeFunc } from "#app/@types/modifier-types"; +import { getModifierType } from "#app/utils/modifier-utils"; import i18next from "i18next"; import { BattlePhase } from "./battle-phase"; diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 68c96ddce1e..770d9c79a2a 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -1,21 +1,12 @@ import { BattlerIndex } from "#enums/battler-index"; import { globalScene } from "#app/global-scene"; import { - AddSecondStrikeAbAttr, - AlwaysHitAbAttr, applyExecutedMoveAbAttrs, applyPostAttackAbAttrs, applyPostDamageAbAttrs, applyPostDefendAbAttrs, applyPreAttackAbAttrs, - ExecutedMoveAbAttr, - IgnoreMoveEffectsAbAttr, - MaxMultiHitAbAttr, - PostAttackAbAttr, - PostDamageAbAttr, - PostDefendAbAttr, - ReflectStatusMoveAbAttr, -} from "#app/data/abilities/ability"; +} from "#app/data/abilities/apply-ab-attrs"; import { ConditionalProtectTag } from "#app/data/arena-tag"; import { ArenaTagSide } from "#enums/arena-tag-side"; import { MoveAnim } from "#app/data/battle-anims"; @@ -179,7 +170,7 @@ export class MoveEffectPhase extends PokemonPhase { globalScene.phaseManager.create( "ShowAbilityPhase", target.getBattlerIndex(), - target.getPassiveAbility().hasAttr(ReflectStatusMoveAbAttr), + target.getPassiveAbility().hasAttr("ReflectStatusMoveAbAttr"), ), ); this.queuedPhases.push(globalScene.phaseManager.create("HideAbilityPhase")); @@ -317,7 +308,7 @@ export class MoveEffectPhase extends PokemonPhase { // Assume single target for multi hit applyMoveAttrs("MultiHitAttr", user, this.getFirstTarget() ?? null, move, hitCount); // If Parental Bond is applicable, add another hit - applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, false, hitCount, null); + applyPreAttackAbAttrs("AddSecondStrikeAbAttr", user, null, move, false, hitCount, null); // If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount); // Set the user's relevant turnData fields to reflect the final hit count @@ -370,7 +361,7 @@ export class MoveEffectPhase extends PokemonPhase { // Add to the move history entry if (this.firstHit) { user.pushMoveHistory(this.moveHistoryEntry); - applyExecutedMoveAbAttrs(ExecutedMoveAbAttr, user); + applyExecutedMoveAbAttrs("ExecutedMoveAbAttr", user); } try { @@ -434,7 +425,7 @@ export class MoveEffectPhase extends PokemonPhase { * @returns a `Promise` intended to be passed into a `then()` call. */ protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): void { - applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, hitResult); + applyPostDefendAbAttrs("PostDefendAbAttr", target, user, this.move, hitResult); target.lapseTags(BattlerTagLapseType.AFTER_HIT); } @@ -450,7 +441,11 @@ export class MoveEffectPhase extends PokemonPhase { return; } - if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && !this.move.hitsSubstitute(user, target)) { + if ( + dealsDamage && + !target.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") && + !this.move.hitsSubstitute(user, target) + ) { const flinched = new BooleanHolder(false); globalScene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); if (flinched.value) { @@ -580,7 +575,7 @@ export class MoveEffectPhase extends PokemonPhase { // Strikes after the first in a multi-strike move are guaranteed to hit, // unless the move is flagged to check all hits and the user does not have Skill Link. if (user.turnData.hitsLeft < user.turnData.hitCount) { - if (!move.hasFlag(MoveFlags.CHECK_ALL_HITS) || user.hasAbilityWithAttr(MaxMultiHitAbAttr)) { + if (!move.hasFlag(MoveFlags.CHECK_ALL_HITS) || user.hasAbilityWithAttr("MaxMultiHitAbAttr")) { return [HitCheckResult.HIT, effectiveness]; } } @@ -626,7 +621,7 @@ export class MoveEffectPhase extends PokemonPhase { if (!user) { return false; } - if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) { + if (user.hasAbilityWithAttr("AlwaysHitAbAttr") || target.hasAbilityWithAttr("AlwaysHitAbAttr")) { return true; } if (this.move.hasAttr("ToxicAccuracyAttr") && user.isOfType(PokemonType.POISON)) { @@ -789,7 +784,7 @@ export class MoveEffectPhase extends PokemonPhase { // Multi-hit check for Wimp Out/Emergency Exit if (user.turnData.hitCount > 1) { - applyPostDamageAbAttrs(PostDamageAbAttr, target, 0, target.hasPassive(), false, [], user); + applyPostDamageAbAttrs("PostDamageAbAttr", target, 0, target.hasPassive(), false, [], user); } } } @@ -983,7 +978,7 @@ export class MoveEffectPhase extends PokemonPhase { this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, false); this.applyHeldItemFlinchCheck(user, target, dealsDamage); this.applyOnGetHitAbEffects(user, target, hitResult); - applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move, hitResult); + applyPostAttackAbAttrs("PostAttackAbAttr", user, target, this.move, hitResult); // We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens if (!user.isPlayer() && this.move.is("AttackMove")) { diff --git a/src/phases/move-end-phase.ts b/src/phases/move-end-phase.ts index e5f87089fae..953f8eae4ce 100644 --- a/src/phases/move-end-phase.ts +++ b/src/phases/move-end-phase.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { PokemonPhase } from "./pokemon-phase"; import type { BattlerIndex } from "#enums/battler-index"; -import { applyPostSummonAbAttrs, PostSummonRemoveEffectAbAttr } from "#app/data/abilities/ability"; +import { applyPostSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import type Pokemon from "#app/field/pokemon"; export class MoveEndPhase extends PokemonPhase { @@ -30,7 +30,7 @@ export class MoveEndPhase extends PokemonPhase { // Remove effects which were set on a Pokemon which removes them on summon (i.e. via Mold Breaker) for (const target of this.targets) { if (target) { - applyPostSummonAbAttrs(PostSummonRemoveEffectAbAttr, target); + applyPostSummonAbAttrs("PostSummonRemoveEffectAbAttr", target); } } diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index ca66ca745e7..d72c7396f1f 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -1,16 +1,6 @@ import { BattlerIndex } from "#enums/battler-index"; import { globalScene } from "#app/global-scene"; -import { - applyAbAttrs, - applyPostMoveUsedAbAttrs, - applyPreAttackAbAttrs, - BlockRedirectAbAttr, - IncreasePpAbAttr, - PokemonTypeChangeAbAttr, - PostMoveUsedAbAttr, - RedirectMoveAbAttr, - ReduceStatusEffectDurationAbAttr, -} from "#app/data/abilities/ability"; +import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import type { DelayedAttackTag } from "#app/data/arena-tag"; import { CommonAnim } from "#enums/move-anims-common"; import { CenterOfAttentionTag } from "#app/data/battler-tags"; @@ -228,7 +218,7 @@ export class MovePhase extends BattlePhase { applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove()); const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0); applyAbAttrs( - ReduceStatusEffectDurationAbAttr, + "ReduceStatusEffectDurationAbAttr", this.pokemon, null, false, @@ -396,7 +386,7 @@ export class MovePhase extends BattlePhase { */ if (success) { const move = this.move.getMove(); - applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, move); + applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, move); globalScene.phaseManager.unshiftNew( "MoveEffectPhase", this.pokemon.getBattlerIndex(), @@ -407,7 +397,7 @@ export class MovePhase extends BattlePhase { ); } else { if ([MoveId.ROAR, MoveId.WHIRLWIND, MoveId.TRICK_OR_TREAT, MoveId.FORESTS_CURSE].includes(this.move.moveId)) { - applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove()); + applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, this.move.getMove()); } this.pokemon.pushMoveHistory({ @@ -437,7 +427,7 @@ export class MovePhase extends BattlePhase { // Note that the `!this.followUp` check here prevents an infinite Dancer loop. if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !this.followUp) { globalScene.getField(true).forEach(pokemon => { - applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets); + applyPostMoveUsedAbAttrs("PostMoveUsedAbAttr", pokemon, this.move, this.pokemon, this.targets); }); } } @@ -449,7 +439,7 @@ export class MovePhase extends BattlePhase { if (move.applyConditions(this.pokemon, targets[0], move)) { // Protean and Libero apply on the charging turn of charge moves - applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove()); + applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, this.move.getMove()); this.showMoveText(); globalScene.phaseManager.unshiftNew( @@ -498,7 +488,7 @@ export class MovePhase extends BattlePhase { public getPpIncreaseFromPressure(targets: Pokemon[]): number { const foesWithPressure = this.pokemon .getOpponents() - .filter(o => targets.includes(o) && o.isActive(true) && o.hasAbilityWithAttr(IncreasePpAbAttr)); + .filter(o => targets.includes(o) && o.isActive(true) && o.hasAbilityWithAttr("IncreasePpAbAttr")); return foesWithPressure.length; } @@ -516,7 +506,9 @@ export class MovePhase extends BattlePhase { globalScene .getField(true) .filter(p => p !== this.pokemon) - .forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, redirectTarget, this.pokemon)); + .forEach(p => + applyAbAttrs("RedirectMoveAbAttr", p, null, false, this.move.moveId, redirectTarget, this.pokemon), + ); /** `true` if an Ability is responsible for redirecting the move to another target; `false` otherwise */ let redirectedByAbility = currentTarget !== redirectTarget.value; @@ -545,17 +537,17 @@ export class MovePhase extends BattlePhase { } }); - if (this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr)) { + if (this.pokemon.hasAbilityWithAttr("BlockRedirectAbAttr")) { redirectTarget.value = currentTarget; // TODO: Ability displays should be handled by the ability globalScene.phaseManager.queueAbilityDisplay( this.pokemon, - this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr), + this.pokemon.getPassiveAbility().hasAttr("BlockRedirectAbAttr"), true, ); globalScene.phaseManager.queueAbilityDisplay( this.pokemon, - this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr), + this.pokemon.getPassiveAbility().hasAttr("BlockRedirectAbAttr"), false, ); } diff --git a/src/phases/new-biome-encounter-phase.ts b/src/phases/new-biome-encounter-phase.ts index 29ba67cb797..5aad607764f 100644 --- a/src/phases/new-biome-encounter-phase.ts +++ b/src/phases/new-biome-encounter-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { applyAbAttrs, PostBiomeChangeAbAttr } from "#app/data/abilities/ability"; +import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { getRandomWeatherType } from "#app/data/weather"; import { NextEncounterPhase } from "./next-encounter-phase"; @@ -14,7 +14,7 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase { if (pokemon) { pokemon.resetBattleAndWaveData(); if (pokemon.isOnField()) { - applyAbAttrs(PostBiomeChangeAbAttr, pokemon, null); + applyAbAttrs("PostBiomeChangeAbAttr", pokemon, null); } } } diff --git a/src/phases/obtain-status-effect-phase.ts b/src/phases/obtain-status-effect-phase.ts index bf172269d5f..dc26d070029 100644 --- a/src/phases/obtain-status-effect-phase.ts +++ b/src/phases/obtain-status-effect-phase.ts @@ -8,7 +8,7 @@ import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonPhase } from "./pokemon-phase"; import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms/form-change-triggers"; -import { applyPostSetStatusAbAttrs, PostSetStatusAbAttr } from "#app/data/abilities/ability"; +import { applyPostSetStatusAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { isNullOrUndefined } from "#app/utils/common"; export class ObtainStatusEffectPhase extends PokemonPhase { @@ -53,7 +53,7 @@ export class ObtainStatusEffectPhase extends PokemonPhase { globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeStatusEffectTrigger, true); // If mold breaker etc was used to set this status, it shouldn't apply to abilities activated afterwards globalScene.arena.setIgnoreAbilities(false); - applyPostSetStatusAbAttrs(PostSetStatusAbAttr, pokemon, this.statusEffect, this.sourcePokemon); + applyPostSetStatusAbAttrs("PostSetStatusAbAttr", pokemon, this.statusEffect, this.sourcePokemon); } this.end(); }); diff --git a/src/phases/post-summon-phase.ts b/src/phases/post-summon-phase.ts index a7faf614292..3acd7ca24e9 100644 --- a/src/phases/post-summon-phase.ts +++ b/src/phases/post-summon-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { applyAbAttrs, applyPostSummonAbAttrs, CommanderAbAttr, PostSummonAbAttr } from "#app/data/abilities/ability"; +import { applyAbAttrs, applyPostSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { ArenaTrapTag } from "#app/data/arena-tag"; import { StatusEffect } from "#app/enums/status-effect"; import { PokemonPhase } from "./pokemon-phase"; @@ -26,10 +26,10 @@ export class PostSummonPhase extends PokemonPhase { pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON); } - applyPostSummonAbAttrs(PostSummonAbAttr, pokemon); + applyPostSummonAbAttrs("PostSummonAbAttr", pokemon); const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); for (const p of field) { - applyAbAttrs(CommanderAbAttr, p, null, false); + applyAbAttrs("CommanderAbAttr", p, null, false); } this.end(); diff --git a/src/phases/post-turn-status-effect-phase.ts b/src/phases/post-turn-status-effect-phase.ts index c868b963f39..e0a3bb5c00b 100644 --- a/src/phases/post-turn-status-effect-phase.ts +++ b/src/phases/post-turn-status-effect-phase.ts @@ -1,13 +1,6 @@ import { globalScene } from "#app/global-scene"; import type { BattlerIndex } from "#enums/battler-index"; -import { - applyAbAttrs, - applyPostDamageAbAttrs, - BlockNonDirectDamageAbAttr, - BlockStatusDamageAbAttr, - PostDamageAbAttr, - ReduceBurnDamageAbAttr, -} from "#app/data/abilities/ability"; +import { applyAbAttrs, applyPostDamageAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { CommonBattleAnim } from "#app/data/battle-anims"; import { CommonAnim } from "#enums/move-anims-common"; import { getStatusEffectActivationText } from "#app/data/status-effect"; @@ -29,8 +22,8 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn() && !pokemon.switchOutStatus) { pokemon.status.incrementTurn(); const cancelled = new BooleanHolder(false); - applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); - applyAbAttrs(BlockStatusDamageAbAttr, pokemon, cancelled); + applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled); + applyAbAttrs("BlockStatusDamageAbAttr", pokemon, cancelled); if (!cancelled.value) { globalScene.phaseManager.queueMessage( @@ -46,14 +39,14 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { break; case StatusEffect.BURN: damage.value = Math.max(pokemon.getMaxHp() >> 4, 1); - applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, false, damage); + applyAbAttrs("ReduceBurnDamageAbAttr", pokemon, null, false, damage); break; } if (damage.value) { // Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ... globalScene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true)); pokemon.updateInfo(); - applyPostDamageAbAttrs(PostDamageAbAttr, pokemon, damage.value, pokemon.hasPassive(), false, []); + applyPostDamageAbAttrs("PostDamageAbAttr", pokemon, damage.value, pokemon.hasPassive(), false, []); } new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(false, () => this.end()); } else { diff --git a/src/phases/quiet-form-change-phase.ts b/src/phases/quiet-form-change-phase.ts index cfd9c521e2b..e6a00c73756 100644 --- a/src/phases/quiet-form-change-phase.ts +++ b/src/phases/quiet-form-change-phase.ts @@ -12,12 +12,7 @@ import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { BattlePhase } from "./battle-phase"; import type { MovePhase } from "./move-phase"; -import { - applyAbAttrs, - ClearTerrainAbAttr, - ClearWeatherAbAttr, - PostTeraFormChangeStatChangeAbAttr, -} from "#app/data/abilities/ability"; +import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; export class QuietFormChangePhase extends BattlePhase { public readonly phaseName = "QuietFormChangePhase"; @@ -185,9 +180,9 @@ export class QuietFormChangePhase extends BattlePhase { } } if (this.formChange.trigger instanceof SpeciesFormChangeTeraTrigger) { - applyAbAttrs(PostTeraFormChangeStatChangeAbAttr, this.pokemon, null); - applyAbAttrs(ClearWeatherAbAttr, this.pokemon, null); - applyAbAttrs(ClearTerrainAbAttr, this.pokemon, null); + applyAbAttrs("PostTeraFormChangeStatChangeAbAttr", this.pokemon, null); + applyAbAttrs("ClearWeatherAbAttr", this.pokemon, null); + applyAbAttrs("ClearTerrainAbAttr", this.pokemon, null); } super.end(); diff --git a/src/phases/ribbon-modifier-reward-phase.ts b/src/phases/ribbon-modifier-reward-phase.ts index 949f7af0302..10d63ba707f 100644 --- a/src/phases/ribbon-modifier-reward-phase.ts +++ b/src/phases/ribbon-modifier-reward-phase.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; import type PokemonSpecies from "#app/data/pokemon-species"; -import type { ModifierTypeFunc } from "#app/modifier/modifier-type"; +import type { ModifierTypeFunc } from "#app/@types/modifier-types"; import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; import { ModifierRewardPhase } from "./modifier-reward-phase"; diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index f99c921412f..34a905f64c6 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import type { ModifierTier } from "#app/modifier/modifier-tier"; +import type { ModifierTier } from "#enums/modifier-tier"; import type { ModifierTypeOption, ModifierType } from "#app/modifier/modifier-type"; import { regenerateModifierPoolThresholds, @@ -11,9 +11,9 @@ import { RememberMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, - ModifierPoolType, getPlayerModifierTypeOptions, } from "#app/modifier/modifier-type"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import type { Modifier } from "#app/modifier/modifier"; import { ExtraModifierModifier, diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index 9c351096180..e73f72f7a63 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -4,13 +4,7 @@ import { applyAbAttrs, applyPostStatStageChangeAbAttrs, applyPreStatStageChangeAbAttrs, - ConditionalUserFieldProtectStatAbAttr, - PostStatStageChangeAbAttr, - ProtectStatAbAttr, - ReflectStatStageChangeAbAttr, - StatStageChangeCopyAbAttr, - StatStageChangeMultiplierAbAttr, -} from "#app/data/abilities/ability"; +} from "#app/data/abilities/apply-ab-attrs"; import { MistTag } from "#app/data/arena-tag"; import { ArenaTagSide } from "#enums/arena-tag-side"; import type { ArenaTag } from "#app/data/arena-tag"; @@ -132,7 +126,7 @@ export class StatStageChangePhase extends PokemonPhase { const stages = new NumberHolder(this.stages); if (!this.ignoreAbilities) { - applyAbAttrs(StatStageChangeMultiplierAbAttr, pokemon, null, false, stages); + applyAbAttrs("StatStageChangeMultiplierAbAttr", pokemon, null, false, stages); } let simulate = false; @@ -152,9 +146,9 @@ export class StatStageChangePhase extends PokemonPhase { } if (!cancelled.value && !this.selfTarget && stages.value < 0) { - applyPreStatStageChangeAbAttrs(ProtectStatAbAttr, pokemon, stat, cancelled, simulate); + applyPreStatStageChangeAbAttrs("ProtectStatAbAttr", pokemon, stat, cancelled, simulate); applyPreStatStageChangeAbAttrs( - ConditionalUserFieldProtectStatAbAttr, + "ConditionalUserFieldProtectStatAbAttr", pokemon, stat, cancelled, @@ -164,7 +158,7 @@ export class StatStageChangePhase extends PokemonPhase { const ally = pokemon.getAlly(); if (!isNullOrUndefined(ally)) { applyPreStatStageChangeAbAttrs( - ConditionalUserFieldProtectStatAbAttr, + "ConditionalUserFieldProtectStatAbAttr", ally, stat, cancelled, @@ -180,7 +174,7 @@ export class StatStageChangePhase extends PokemonPhase { !this.comingFromMirrorArmorUser ) { applyPreStatStageChangeAbAttrs( - ReflectStatStageChangeAbAttr, + "ReflectStatStageChangeAbAttr", pokemon, stat, cancelled, @@ -228,11 +222,17 @@ export class StatStageChangePhase extends PokemonPhase { if (stages.value > 0 && this.canBeCopied) { for (const opponent of pokemon.getOpponents()) { - applyAbAttrs(StatStageChangeCopyAbAttr, opponent, null, false, this.stats, stages.value); + applyAbAttrs("StatStageChangeCopyAbAttr", opponent, null, false, this.stats, stages.value); } } - applyPostStatStageChangeAbAttrs(PostStatStageChangeAbAttr, pokemon, filteredStats, this.stages, this.selfTarget); + applyPostStatStageChangeAbAttrs( + "PostStatStageChangeAbAttr", + pokemon, + filteredStats, + this.stages, + this.selfTarget, + ); // Look for any other stat change phases; if this is the last one, do White Herb check const existingPhase = globalScene.phaseManager.findPhase( diff --git a/src/phases/summon-phase.ts b/src/phases/summon-phase.ts index e902fd0183e..ad93452331f 100644 --- a/src/phases/summon-phase.ts +++ b/src/phases/summon-phase.ts @@ -10,7 +10,7 @@ import { getPokemonNameWithAffix } from "#app/messages"; import i18next from "i18next"; import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; -import { applyPreSummonAbAttrs, PreSummonAbAttr } from "#app/data/abilities/ability"; +import { applyPreSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; export class SummonPhase extends PartyMemberPokemonPhase { @@ -27,7 +27,7 @@ export class SummonPhase extends PartyMemberPokemonPhase { start() { super.start(); - applyPreSummonAbAttrs(PreSummonAbAttr, this.getPokemon()); + applyPreSummonAbAttrs("PreSummonAbAttr", this.getPokemon()); this.preSummon(); } diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index 71136be0c93..6b76d4e8926 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -1,11 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { - applyPreSummonAbAttrs, - applyPreSwitchOutAbAttrs, - PostDamageForceSwitchAbAttr, - PreSummonAbAttr, - PreSwitchOutAbAttr, -} from "#app/data/abilities/ability"; +import { applyPreSummonAbAttrs, applyPreSwitchOutAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { allMoves } from "#app/data/data-lists"; import { getPokeballTintColor } from "#app/data/pokeball"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers"; @@ -130,8 +124,8 @@ export class SwitchSummonPhase extends SummonPhase { switchedInPokemon.resetSummonData(); switchedInPokemon.loadAssets(true); - applyPreSummonAbAttrs(PreSummonAbAttr, switchedInPokemon); - applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon); + applyPreSummonAbAttrs("PreSummonAbAttr", switchedInPokemon); + applyPreSwitchOutAbAttrs("PreSwitchOutAbAttr", this.lastPokemon); if (!switchedInPokemon) { this.end(); return; @@ -215,7 +209,7 @@ export class SwitchSummonPhase extends SummonPhase { const lastPokemonIsForceSwitchedAndNotFainted = lastUsedMove?.hasAttr("ForceSwitchOutAttr") && !this.lastPokemon.isFainted(); const lastPokemonHasForceSwitchAbAttr = - this.lastPokemon.hasAbilityWithAttr(PostDamageForceSwitchAbAttr) && !this.lastPokemon.isFainted(); + this.lastPokemon.hasAbilityWithAttr("PostDamageForceSwitchAbAttr") && !this.lastPokemon.isFainted(); // Compensate for turn spent summoning/forced switch if switched out pokemon is not fainted. // Needed as we increment turn counters in `TurnEndPhase`. diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 26311d52ab8..5e36081b899 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -6,15 +6,12 @@ import { getBiomeKey } from "#app/field/arena"; import { GameMode, getGameMode } from "#app/game-mode"; import { GameModes } from "#enums/game-modes"; import type { Modifier } from "#app/modifier/modifier"; -import { - getDailyRunStarterModifiers, - ModifierPoolType, - modifierTypes, - regenerateModifierPoolThresholds, -} from "#app/modifier/modifier-type"; +import { getDailyRunStarterModifiers, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; +import { ModifierPoolType } from "#enums/modifier-pool-type"; import { Phase } from "#app/phase"; import type { SessionSaveData } from "#app/system/game-data"; -import { Unlockables } from "#app/system/unlockables"; +import { Unlockables } from "#enums/unlockables"; import { vouchers } from "#app/system/voucher"; import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { SaveSlotUiMode } from "#app/ui/save-slot-select-ui-handler"; diff --git a/src/phases/trainer-victory-phase.ts b/src/phases/trainer-victory-phase.ts index 5d35dd5d375..554b8109f02 100644 --- a/src/phases/trainer-victory-phase.ts +++ b/src/phases/trainer-victory-phase.ts @@ -1,6 +1,6 @@ import { getCharVariantFromDialogue } from "#app/data/dialogue"; import { TrainerType } from "#app/enums/trainer-type"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { vouchers } from "#app/system/voucher"; import i18next from "i18next"; import { randSeedItem } from "#app/utils/common"; diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index a539b234a18..ab46292c1d2 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -1,4 +1,4 @@ -import { applyPostTurnAbAttrs, PostTurnAbAttr } from "#app/data/abilities/ability"; +import { applyPostTurnAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { TerrainType } from "#app/data/terrain"; import { WeatherType } from "#app/enums/weather-type"; @@ -49,7 +49,7 @@ export class TurnEndPhase extends FieldPhase { globalScene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon); } - applyPostTurnAbAttrs(PostTurnAbAttr, pokemon); + applyPostTurnAbAttrs("PostTurnAbAttr", pokemon); } globalScene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon); diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index e9a8a82afdc..6f062cb5fbe 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -1,4 +1,4 @@ -import { applyAbAttrs, BypassSpeedChanceAbAttr, PreventBypassSpeedChanceAbAttr } from "#app/data/abilities/ability"; +import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { allMoves } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { Stat } from "#app/enums/stat"; @@ -66,8 +66,8 @@ export class TurnStartPhase extends FieldPhase { globalScene.getField(true).map(p => { const bypassSpeed = new BooleanHolder(false); const canCheckHeldItems = new BooleanHolder(true); - applyAbAttrs(BypassSpeedChanceAbAttr, p, null, false, bypassSpeed); - applyAbAttrs(PreventBypassSpeedChanceAbAttr, p, null, false, bypassSpeed, canCheckHeldItems); + applyAbAttrs("BypassSpeedChanceAbAttr", p, null, false, bypassSpeed); + applyAbAttrs("PreventBypassSpeedChanceAbAttr", p, null, false, bypassSpeed, canCheckHeldItems); if (canCheckHeldItems.value) { globalScene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed); } diff --git a/src/phases/unlock-phase.ts b/src/phases/unlock-phase.ts index 839ac31dc5d..76719847f92 100644 --- a/src/phases/unlock-phase.ts +++ b/src/phases/unlock-phase.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; -import type { Unlockables } from "#app/system/unlockables"; +import type { Unlockables } from "#enums/unlockables"; import { getUnlockableName } from "#app/system/unlockables"; import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts index bf6ea6d4a43..ae5b727c2a6 100644 --- a/src/phases/victory-phase.ts +++ b/src/phases/victory-phase.ts @@ -2,7 +2,7 @@ import type { BattlerIndex } from "#enums/battler-index"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; import { BattleType } from "#enums/battle-type"; import type { CustomModifierSettings } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { PokemonPhase } from "./pokemon-phase"; import { handleMysteryEncounterVictory } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { globalScene } from "#app/global-scene"; diff --git a/src/phases/weather-effect-phase.ts b/src/phases/weather-effect-phase.ts index 0873283652e..d9239220376 100644 --- a/src/phases/weather-effect-phase.ts +++ b/src/phases/weather-effect-phase.ts @@ -1,13 +1,9 @@ import { globalScene } from "#app/global-scene"; import { applyPreWeatherEffectAbAttrs, - SuppressWeatherEffectAbAttr, - PreWeatherDamageAbAttr, applyAbAttrs, - BlockNonDirectDamageAbAttr, applyPostWeatherLapseAbAttrs, - PostWeatherLapseAbAttr, -} from "#app/data/abilities/ability"; +} from "#app/data/abilities/apply-ab-attrs"; import { CommonAnim } from "#enums/move-anims-common"; import type { Weather } from "#app/data/weather"; import { getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weather"; @@ -45,15 +41,15 @@ export class WeatherEffectPhase extends CommonAnimPhase { const cancelled = new BooleanHolder(false); this.executeForAll((pokemon: Pokemon) => - applyPreWeatherEffectAbAttrs(SuppressWeatherEffectAbAttr, pokemon, this.weather, cancelled), + applyPreWeatherEffectAbAttrs("SuppressWeatherEffectAbAttr", pokemon, this.weather, cancelled), ); if (!cancelled.value) { const inflictDamage = (pokemon: Pokemon) => { const cancelled = new BooleanHolder(false); - applyPreWeatherEffectAbAttrs(PreWeatherDamageAbAttr, pokemon, this.weather, cancelled); - applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + applyPreWeatherEffectAbAttrs("PreWeatherDamageAbAttr", pokemon, this.weather, cancelled); + applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled); if ( cancelled.value || @@ -84,7 +80,7 @@ export class WeatherEffectPhase extends CommonAnimPhase { globalScene.ui.showText(getWeatherLapseMessage(this.weather.weatherType) ?? "", null, () => { this.executeForAll((pokemon: Pokemon) => { if (!pokemon.switchOutStatus) { - applyPostWeatherLapseAbAttrs(PostWeatherLapseAbAttr, pokemon, this.weather); + applyPostWeatherLapseAbAttrs("PostWeatherLapseAbAttr", pokemon, this.weather); } }); diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 31c7ad7e41c..00b46b9e5f4 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -13,7 +13,7 @@ import Overrides from "#app/overrides"; import PokemonData from "#app/system/pokemon-data"; import PersistentModifierData from "#app/system/modifier-data"; import ArenaData from "#app/system/arena-data"; -import { Unlockables } from "#app/system/unlockables"; +import { Unlockables } from "#enums/unlockables"; import { getGameMode } from "#app/game-mode"; import { GameModes } from "#enums/game-modes"; import { BattleType } from "#enums/battle-type"; diff --git a/src/system/unlockables.ts b/src/system/unlockables.ts index f64d4d33966..72588858eae 100644 --- a/src/system/unlockables.ts +++ b/src/system/unlockables.ts @@ -1,13 +1,7 @@ import i18next from "i18next"; import { GameMode } from "../game-mode"; import { GameModes } from "#enums/game-modes"; - -export enum Unlockables { - ENDLESS_MODE, - MINI_BLACK_HOLE, - SPLICED_ENDLESS_MODE, - EVIOLITE, -} +import { Unlockables } from "#enums/unlockables"; export function getUnlockableName(unlockable: Unlockables) { switch (unlockable) { diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index f60bfa808ca..a2dfe83e996 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -7,11 +7,7 @@ import { Command } from "#enums/command"; import MessageUiHandler from "#app/ui/message-ui-handler"; import { UiMode } from "#enums/ui-mode"; import { BooleanHolder, toReadableString, randInt, getLocalizedSpriteKey } from "#app/utils/common"; -import { - PokemonFormChangeItemModifier, - PokemonHeldItemModifier, - SwitchEffectTransferModifier, -} from "#app/modifier/modifier"; +import type { PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { allMoves } from "#app/data/data-lists"; import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender"; import { StatusEffect } from "#enums/status-effect"; @@ -226,7 +222,7 @@ export default class PartyUiHandler extends MessageUiHandler { public static FilterItemMaxStacks = (pokemon: PlayerPokemon, modifier: PokemonHeldItemModifier) => { const matchingModifier = globalScene.findModifier( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id && m.matchType(modifier), + m => m.is("PokemonHeldItemModifier") && m.pokemonId === pokemon.id && m.matchType(modifier), ) as PokemonHeldItemModifier; if (matchingModifier && matchingModifier.stackCount === matchingModifier.getMaxStackCount()) { return i18next.t("partyUiHandler:tooManyItems", { pokemonName: getPokemonNameWithAffix(pokemon, false) }); @@ -554,7 +550,7 @@ export default class PartyUiHandler extends MessageUiHandler { // this returns `undefined` if the new pokemon doesn't have the item at all, otherwise it returns the `pokemonHeldItemModifier` for that item const matchingModifier = globalScene.findModifier( m => - m instanceof PokemonHeldItemModifier && + m.is("PokemonHeldItemModifier") && m.pokemonId === newPokemon.id && m.matchType(this.getTransferrableItemsFromPokemon(pokemon)[this.transferOptionCursor]), ) as PokemonHeldItemModifier; @@ -687,7 +683,7 @@ export default class PartyUiHandler extends MessageUiHandler { private getTransferrableItemsFromPokemon(pokemon: PlayerPokemon) { return globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === pokemon.id, + m => m.is("PokemonHeldItemModifier") && m.isTransferable && m.pokemonId === pokemon.id, ) as PokemonHeldItemModifier[]; } @@ -928,7 +924,7 @@ export default class PartyUiHandler extends MessageUiHandler { /** Initialize item quantities for the selected Pokemon */ const itemModifiers = globalScene.findModifiers( m => - m instanceof PokemonHeldItemModifier && + m.is("PokemonHeldItemModifier") && m.isTransferable && m.pokemonId === globalScene.getPlayerParty()[this.cursor].id, ) as PokemonHeldItemModifier[]; @@ -1165,8 +1161,7 @@ export default class PartyUiHandler extends MessageUiHandler { return !!( this.partyUiMode !== PartyUiMode.FAINT_SWITCH && globalScene.findModifier( - m => - m instanceof SwitchEffectTransferModifier && m.pokemonId === globalScene.getPlayerField()[this.fieldIndex].id, + m => m.is("SwitchEffectTransferModifier") && m.pokemonId === globalScene.getPlayerField()[this.fieldIndex].id, ) ); } @@ -1185,7 +1180,7 @@ export default class PartyUiHandler extends MessageUiHandler { private getItemModifiers(pokemon: Pokemon): PokemonHeldItemModifier[] { return ( (globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === pokemon.id, + m => m.is("PokemonHeldItemModifier") && m.isTransferable && m.pokemonId === pokemon.id, ) as PokemonHeldItemModifier[]) ?? [] ); } @@ -1565,7 +1560,7 @@ export default class PartyUiHandler extends MessageUiHandler { getFormChangeItemsModifiers(pokemon: Pokemon) { let formChangeItemModifiers = globalScene.findModifiers( - m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id, + m => m.is("PokemonFormChangeItemModifier") && m.pokemonId === pokemon.id, ) as PokemonFormChangeItemModifier[]; const ultraNecrozmaModifiers = formChangeItemModifiers.filter( m => m.active && m.formChangeItem === FormChangeItem.ULTRANECROZIUM_Z, diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 0b1e690a918..88f881746bb 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -8,7 +8,7 @@ import i18next from "i18next"; import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { starterColors } from "#app/global-vars/starter-colors"; import { globalScene } from "#app/global-scene"; -import type { Ability } from "#app/data/abilities/ability-class"; +import type { Ability } from "#app/data/abilities/ability"; import { allAbilities } from "#app/data/data-lists"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { GrowthRate, getGrowthRateColor } from "#app/data/exp"; diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 05b0ea2097f..d30322de293 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -33,7 +33,7 @@ import { loggedInUser } from "#app/account"; import type { Variant } from "#app/sprites/variant"; import { getVariantTint } from "#app/sprites/variant"; import { Button } from "#enums/buttons"; -import type { Ability } from "#app/data/abilities/ability-class"; +import type { Ability } from "#app/data/abilities/ability"; import i18next from "i18next"; import { modifierSortFunc } from "#app/modifier/modifier"; import { PlayerGender } from "#enums/player-gender"; diff --git a/src/ui/text.ts b/src/ui/text.ts index d3afdef666f..8812d8ee4a8 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -4,7 +4,7 @@ import type Phaser from "phaser"; import BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText"; import InputText from "phaser3-rex-plugins/plugins/inputtext"; import { globalScene } from "#app/global-scene"; -import { ModifierTier } from "../modifier/modifier-tier"; +import { ModifierTier } from "../enums/modifier-tier"; import i18next from "#app/plugins/i18n"; export enum TextStyle { diff --git a/src/utils/modifier-utils.ts b/src/utils/modifier-utils.ts new file mode 100644 index 00000000000..3be4af3730c --- /dev/null +++ b/src/utils/modifier-utils.ts @@ -0,0 +1,35 @@ +import { ModifierPoolType } from "#enums/modifier-pool-type"; +import { + dailyStarterModifierPool, + enemyBuffModifierPool, + modifierPool, + trainerModifierPool, + wildModifierPool, +} from "#app/modifier/modifier-pools"; +import type { ModifierPool, ModifierTypeFunc } from "#app/@types/modifier-types"; +import { modifierTypes } from "#app/data/data-lists"; +import type { ModifierType } from "#app/modifier/modifier-type"; + +export function getModifierPoolForType(poolType: ModifierPoolType): ModifierPool { + switch (poolType) { + case ModifierPoolType.PLAYER: + return modifierPool; + case ModifierPoolType.WILD: + return wildModifierPool; + case ModifierPoolType.TRAINER: + return trainerModifierPool; + case ModifierPoolType.ENEMY_BUFF: + return enemyBuffModifierPool; + case ModifierPoolType.DAILY_STARTER: + return dailyStarterModifierPool; + } +} + +// TODO: document this +export function getModifierType(modifierTypeFunc: ModifierTypeFunc): ModifierType { + const modifierType = modifierTypeFunc(); + if (!modifierType.id) { + modifierType.id = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifierTypeFunc)!; // TODO: is this bang correct? + } + return modifierType; +} diff --git a/test/abilities/healer.test.ts b/test/abilities/healer.test.ts index 7f71be7b86e..9d252523cc8 100644 --- a/test/abilities/healer.test.ts +++ b/test/abilities/healer.test.ts @@ -6,9 +6,9 @@ import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi, type MockInstance } from "vitest"; import { isNullOrUndefined } from "#app/utils/common"; -import { PostTurnResetStatusAbAttr } from "#app/data/abilities/ability"; import { allAbilities } from "#app/data/data-lists"; import type Pokemon from "#app/field/pokemon"; +import type { PostTurnResetStatusAbAttr } from "#app/@types/ability-types"; describe("Abilities - Healer", () => { let phaserGame: Phaser.Game; @@ -38,7 +38,7 @@ describe("Abilities - Healer", () => { .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); - healerAttr = allAbilities[AbilityId.HEALER].getAttrs(PostTurnResetStatusAbAttr)[0]; + healerAttr = allAbilities[AbilityId.HEALER].getAttrs("PostTurnResetStatusAbAttr")[0]; healerAttrSpy = vi .spyOn(healerAttr, "getCondition") .mockReturnValue((pokemon: Pokemon) => !isNullOrUndefined(pokemon.getAlly())); diff --git a/test/abilities/neutralizing_gas.test.ts b/test/abilities/neutralizing_gas.test.ts index 067d164e835..51d2bed3ff0 100644 --- a/test/abilities/neutralizing_gas.test.ts +++ b/test/abilities/neutralizing_gas.test.ts @@ -1,7 +1,6 @@ import { BattlerIndex } from "#enums/battler-index"; import type { CommandPhase } from "#app/phases/command-phase"; import { Command } from "#enums/command"; -import { PostSummonWeatherChangeAbAttr } from "#app/data/abilities/ability"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#enums/arena-tag-type"; import { MoveId } from "#enums/move-id"; @@ -178,7 +177,7 @@ describe("Abilities - Neutralizing Gas", () => { await game.classicMode.startBattle([SpeciesId.MAGIKARP]); const enemy = game.scene.getEnemyPokemon()!; - const weatherChangeAttr = enemy.getAbilityAttrs(PostSummonWeatherChangeAbAttr, false)[0]; + const weatherChangeAttr = enemy.getAbilityAttrs("PostSummonWeatherChangeAbAttr", false)[0]; vi.spyOn(weatherChangeAttr, "applyPostSummon"); expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeDefined(); diff --git a/test/abilities/normal-move-type-change.test.ts b/test/abilities/normal-move-type-change.test.ts index 03fb5b2e7be..d9e39b32a7c 100644 --- a/test/abilities/normal-move-type-change.test.ts +++ b/test/abilities/normal-move-type-change.test.ts @@ -9,7 +9,6 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; import { allAbilities } from "#app/data/data-lists"; -import { MoveTypeChangeAbAttr } from "#app/data/abilities/ability"; import { toDmgValue } from "#app/utils/common"; /** @@ -160,7 +159,7 @@ describe.each([ // get the power boost from the ability so we can compare it to the item // @ts-expect-error power multiplier is private - const boost = allAbilities[ab]?.getAttrs(MoveTypeChangeAbAttr)[0]?.powerMultiplier; + const boost = allAbilities[ab]?.getAttrs("MoveTypeChangeAbAttr")[0]?.powerMultiplier; expect(boost, "power boost should be defined").toBeDefined(); const powerSpy = vi.spyOn(testMoveInstance, "calculateBattlePower"); @@ -177,7 +176,7 @@ describe.each([ // get the power boost from the ability so we can compare it to the item // @ts-expect-error power multiplier is private - const boost = allAbilities[ab]?.getAttrs(MoveTypeChangeAbAttr)[0]?.powerMultiplier; + const boost = allAbilities[ab]?.getAttrs("MoveTypeChangeAbAttr")[0]?.powerMultiplier; expect(boost, "power boost should be defined").toBeDefined(); const tackle = allMoves[MoveId.TACKLE]; diff --git a/test/abilities/quick_draw.test.ts b/test/abilities/quick_draw.test.ts index 70b8637aa37..5e5e57fb056 100644 --- a/test/abilities/quick_draw.test.ts +++ b/test/abilities/quick_draw.test.ts @@ -1,4 +1,3 @@ -import { BypassSpeedChanceAbAttr } from "#app/data/abilities/ability"; import { allAbilities } from "#app/data/data-lists"; import { FaintPhase } from "#app/phases/faint-phase"; import { AbilityId } from "#enums/ability-id"; @@ -35,9 +34,11 @@ describe("Abilities - Quick Draw", () => { game.override.enemyAbility(AbilityId.BALL_FETCH); game.override.enemyMoveset([MoveId.TACKLE]); - vi.spyOn(allAbilities[AbilityId.QUICK_DRAW].getAttrs(BypassSpeedChanceAbAttr)[0], "chance", "get").mockReturnValue( - 100, - ); + vi.spyOn( + allAbilities[AbilityId.QUICK_DRAW].getAttrs("BypassSpeedChanceAbAttr")[0], + "chance", + "get", + ).mockReturnValue(100); }); test("makes pokemon going first in its priority bracket", async () => { diff --git a/test/abilities/sand_veil.test.ts b/test/abilities/sand_veil.test.ts index f4b322dc2e9..35a0a3347ff 100644 --- a/test/abilities/sand_veil.test.ts +++ b/test/abilities/sand_veil.test.ts @@ -1,4 +1,3 @@ -import { StatMultiplierAbAttr } from "#app/data/abilities/ability"; import { allAbilities } from "#app/data/data-lists"; import { CommandPhase } from "#app/phases/command-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; @@ -46,7 +45,7 @@ describe("Abilities - Sand Veil", () => { vi.spyOn(leadPokemon[0], "getAbility").mockReturnValue(allAbilities[AbilityId.SAND_VEIL]); - const sandVeilAttr = allAbilities[AbilityId.SAND_VEIL].getAttrs(StatMultiplierAbAttr)[0]; + const sandVeilAttr = allAbilities[AbilityId.SAND_VEIL].getAttrs("StatMultiplierAbAttr")[0]; vi.spyOn(sandVeilAttr, "applyStatStage").mockImplementation( (_pokemon, _passive, _simulated, stat, statValue, _args) => { if (stat === Stat.EVA && game.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) { diff --git a/test/abilities/shield_dust.test.ts b/test/abilities/shield_dust.test.ts index e071cf7a245..db1266d3088 100644 --- a/test/abilities/shield_dust.test.ts +++ b/test/abilities/shield_dust.test.ts @@ -1,10 +1,5 @@ import { BattlerIndex } from "#enums/battler-index"; -import { - applyAbAttrs, - applyPreDefendAbAttrs, - IgnoreMoveEffectsAbAttr, - MoveEffectChanceMultiplierAbAttr, -} from "#app/data/abilities/ability"; +import { applyAbAttrs, applyPreDefendAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { NumberHolder } from "#app/utils/common"; import { AbilityId } from "#enums/ability-id"; @@ -57,7 +52,7 @@ describe("Abilities - Shield Dust", () => { const chance = new NumberHolder(move.chance); await applyAbAttrs( - MoveEffectChanceMultiplierAbAttr, + "MoveEffectChanceMultiplierAbAttr", phase.getUserPokemon()!, null, false, @@ -67,7 +62,7 @@ describe("Abilities - Shield Dust", () => { false, ); await applyPreDefendAbAttrs( - IgnoreMoveEffectsAbAttr, + "IgnoreMoveEffectsAbAttr", phase.getFirstTarget()!, phase.getUserPokemon()!, null, diff --git a/test/abilities/unburden.test.ts b/test/abilities/unburden.test.ts index 9748b6340f0..6e24e10d168 100644 --- a/test/abilities/unburden.test.ts +++ b/test/abilities/unburden.test.ts @@ -1,5 +1,4 @@ import { BattlerIndex } from "#enums/battler-index"; -import { PostItemLostAbAttr } from "#app/data/abilities/ability"; import { StealHeldItemChanceAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import type Pokemon from "#app/field/pokemon"; @@ -277,7 +276,7 @@ describe("Abilities - Unburden", () => { const [treecko, purrloin] = game.scene.getPlayerParty(); const initialTreeckoSpeed = treecko.getStat(Stat.SPD); const initialPurrloinSpeed = purrloin.getStat(Stat.SPD); - const unburdenAttr = treecko.getAbilityAttrs(PostItemLostAbAttr)[0]; + const unburdenAttr = treecko.getAbilityAttrs("PostItemLostAbAttr")[0]; vi.spyOn(unburdenAttr, "applyPostItemLost"); // Player uses Baton Pass, which also passes the Baton item diff --git a/test/battle/damage_calculation.test.ts b/test/battle/damage_calculation.test.ts index a054ad0f468..555b3a9c554 100644 --- a/test/battle/damage_calculation.test.ts +++ b/test/battle/damage_calculation.test.ts @@ -1,6 +1,6 @@ import { allMoves } from "#app/data/data-lists"; import type { EnemyPersistentModifier } from "#app/modifier/modifier"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#enums/arena-tag-type"; import { MoveId } from "#enums/move-id"; diff --git a/test/items/light_ball.test.ts b/test/items/light_ball.test.ts index 84a1689260f..a0269506909 100644 --- a/test/items/light_ball.test.ts +++ b/test/items/light_ball.test.ts @@ -1,6 +1,6 @@ import { Stat } from "#enums/stat"; import { SpeciesStatBoosterModifier } from "#app/modifier/modifier"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import i18next from "#app/plugins/i18n"; import { NumberHolder } from "#app/utils/common"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/items/lock_capsule.test.ts b/test/items/lock_capsule.test.ts index 292031a2bf0..640da4a299e 100644 --- a/test/items/lock_capsule.test.ts +++ b/test/items/lock_capsule.test.ts @@ -1,6 +1,6 @@ import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { UiMode } from "#enums/ui-mode"; import GameManager from "#test/testUtils/gameManager"; diff --git a/test/items/metal_powder.test.ts b/test/items/metal_powder.test.ts index 20b0b90a766..576b4923d6d 100644 --- a/test/items/metal_powder.test.ts +++ b/test/items/metal_powder.test.ts @@ -1,6 +1,6 @@ import { Stat } from "#enums/stat"; import { SpeciesStatBoosterModifier } from "#app/modifier/modifier"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import i18next from "#app/plugins/i18n"; import { NumberHolder } from "#app/utils/common"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/items/quick_powder.test.ts b/test/items/quick_powder.test.ts index 0192dec4635..af99f51273d 100644 --- a/test/items/quick_powder.test.ts +++ b/test/items/quick_powder.test.ts @@ -1,6 +1,6 @@ import { Stat } from "#enums/stat"; import { SpeciesStatBoosterModifier } from "#app/modifier/modifier"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import i18next from "#app/plugins/i18n"; import { NumberHolder } from "#app/utils/common"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/items/thick_club.test.ts b/test/items/thick_club.test.ts index cff080d0e42..bc019ee99f8 100644 --- a/test/items/thick_club.test.ts +++ b/test/items/thick_club.test.ts @@ -1,6 +1,6 @@ import { Stat } from "#enums/stat"; import { SpeciesStatBoosterModifier } from "#app/modifier/modifier"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import i18next from "#app/plugins/i18n"; import { NumberHolder, randInt } from "#app/utils/common"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/moves/safeguard.test.ts b/test/moves/safeguard.test.ts index 2ba53016833..6cd90bf8ac6 100644 --- a/test/moves/safeguard.test.ts +++ b/test/moves/safeguard.test.ts @@ -1,5 +1,4 @@ import { BattlerIndex } from "#enums/battler-index"; -import { PostDefendContactApplyStatusEffectAbAttr } from "#app/data/abilities/ability"; import { allAbilities } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { StatusEffect } from "#app/enums/status-effect"; @@ -139,7 +138,7 @@ describe("Moves - Safeguard", () => { it("protects from ability-inflicted status", async () => { game.override.ability(AbilityId.STATIC); vi.spyOn( - allAbilities[AbilityId.STATIC].getAttrs(PostDefendContactApplyStatusEffectAbAttr)[0], + allAbilities[AbilityId.STATIC].getAttrs("PostDefendContactApplyStatusEffectAbAttr")[0], "chance", "get", ).mockReturnValue(100); diff --git a/test/moves/secret_power.test.ts b/test/moves/secret_power.test.ts index d555431656f..39498782b58 100644 --- a/test/moves/secret_power.test.ts +++ b/test/moves/secret_power.test.ts @@ -11,7 +11,6 @@ import { StatusEffect } from "#enums/status-effect"; import { BattlerIndex } from "#enums/battler-index"; import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagSide } from "#enums/arena-tag-side"; -import { MoveEffectChanceMultiplierAbAttr } from "#app/data/abilities/ability"; import { allAbilities } from "#app/data/data-lists"; describe("Moves - Secret Power", () => { @@ -68,7 +67,7 @@ describe("Moves - Secret Power", () => { .battleStyle("double"); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); - const sereneGraceAttr = allAbilities[AbilityId.SERENE_GRACE].getAttrs(MoveEffectChanceMultiplierAbAttr)[0]; + const sereneGraceAttr = allAbilities[AbilityId.SERENE_GRACE].getAttrs("MoveEffectChanceMultiplierAbAttr")[0]; vi.spyOn(sereneGraceAttr, "canApply"); game.move.select(MoveId.WATER_PLEDGE, 0, BattlerIndex.ENEMY); diff --git a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index 778f8397417..526a3a0ab67 100644 --- a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -20,7 +20,7 @@ import { UiMode } from "#enums/ui-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { ClowningAroundEncounter } from "#app/data/mystery-encounters/encounters/clowning-around-encounter"; import { TrainerType } from "#enums/trainer-type"; import { AbilityId } from "#enums/ability-id"; @@ -29,7 +29,7 @@ import { Button } from "#enums/buttons"; import type PartyUiHandler from "#app/ui/party-ui-handler"; import type OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { BerryType } from "#enums/berry-type"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { PokemonType } from "#enums/pokemon-type"; diff --git a/test/mystery-encounter/encounters/delibirdy-encounter.test.ts b/test/mystery-encounter/encounters/delibirdy-encounter.test.ts index 0d1094831bc..e569c1d7789 100644 --- a/test/mystery-encounter/encounters/delibirdy-encounter.test.ts +++ b/test/mystery-encounter/encounters/delibirdy-encounter.test.ts @@ -26,7 +26,7 @@ import { } from "#app/modifier/modifier"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { BerryType } from "#enums/berry-type"; const namespace = "mysteryEncounters/delibirdy"; diff --git a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts index 96c4adf67c2..0aa1886b8e1 100644 --- a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts +++ b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts @@ -11,13 +11,13 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; import { PokemonNatureWeightModifier } from "#app/modifier/modifier"; import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encounters/global-trade-system-encounter"; import { CIVILIZATION_ENCOUNTER_BIOMES } from "#app/data/mystery-encounters/mystery-encounters"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { UiMode } from "#enums/ui-mode"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import * as Utils from "#app/utils/common"; const namespace = "mysteryEncounters/globalTradeSystem"; diff --git a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts index 948e42547de..9c5660cb25c 100644 --- a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts +++ b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts @@ -14,7 +14,7 @@ import { UiMode } from "#enums/ui-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import { MysteriousChallengersEncounter } from "#app/data/mystery-encounters/encounters/mysterious-challengers-encounter"; import { TrainerConfig } from "#app/data/trainers/trainer-config"; import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate"; diff --git a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index 47c75eb19fc..2014cb215a4 100644 --- a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -14,8 +14,9 @@ import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import { HealShopCostModifier, HitHealModifier, TurnHealModifier } from "#app/modifier/modifier"; -import { ModifierTier } from "#app/modifier/modifier-tier"; -import { modifierTypes, type PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import { ModifierTier } from "#enums/modifier-tier"; +import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; diff --git a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts index 12545b8d70a..60ca87d3ae2 100644 --- a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts +++ b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts @@ -26,7 +26,7 @@ import { BerryType } from "#enums/berry-type"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { Stat } from "#enums/stat"; import type { BerryModifier } from "#app/modifier/modifier"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; const namespace = "mysteryEncounters/uncommonBreed"; diff --git a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts index 2ad74b48540..dc5c53a75e7 100644 --- a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -19,7 +19,7 @@ import { WeirdDreamEncounter } from "#app/data/mystery-encounters/encounters/wei import * as EncounterTransformationSequence from "#app/data/mystery-encounters/utils/encounter-transformation-sequence"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { CommandPhase } from "#app/phases/command-phase"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; const namespace = "mysteryEncounters/weirdDream"; const defaultParty = [SpeciesId.MAGBY, SpeciesId.HAUNTER, SpeciesId.ABRA]; diff --git a/test/phases/form-change-phase.test.ts b/test/phases/form-change-phase.test.ts index 8531375a48b..99c072bdafe 100644 --- a/test/phases/form-change-phase.test.ts +++ b/test/phases/form-change-phase.test.ts @@ -6,7 +6,7 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { PokemonType } from "#enums/pokemon-type"; import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; describe("Form Change Phase", () => { let phaserGame: Phaser.Game; diff --git a/test/phases/game-over-phase.test.ts b/test/phases/game-over-phase.test.ts index c430223b774..d45330697fc 100644 --- a/test/phases/game-over-phase.test.ts +++ b/test/phases/game-over-phase.test.ts @@ -6,7 +6,7 @@ import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { achvs } from "#app/system/achv"; -import { Unlockables } from "#app/system/unlockables"; +import { Unlockables } from "#enums/unlockables"; describe("Game Over Phase", () => { let phaserGame: Phaser.Game; diff --git a/test/phases/select-modifier-phase.test.ts b/test/phases/select-modifier-phase.test.ts index 083b7d16f10..3639d34d25e 100644 --- a/test/phases/select-modifier-phase.test.ts +++ b/test/phases/select-modifier-phase.test.ts @@ -1,9 +1,10 @@ import type BattleScene from "#app/battle-scene"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { PlayerPokemon } from "#app/field/pokemon"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { ModifierTier } from "#enums/modifier-tier"; import type { CustomModifierSettings } from "#app/modifier/modifier-type"; -import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type"; +import { ModifierTypeOption } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { UiMode } from "#enums/ui-mode"; diff --git a/test/testUtils/gameManager.ts b/test/testUtils/gameManager.ts index 128217a6472..5d3ed3b6c8c 100644 --- a/test/testUtils/gameManager.ts +++ b/test/testUtils/gameManager.ts @@ -6,7 +6,8 @@ import Trainer from "#app/field/trainer"; import { getGameMode } from "#app/game-mode"; import { GameModes } from "#enums/game-modes"; import { globalScene } from "#app/global-scene"; -import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type"; +import { ModifierTypeOption } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/data/data-lists"; import overrides from "#app/overrides"; import { CheckSwitchPhase } from "#app/phases/check-switch-phase"; import { CommandPhase } from "#app/phases/command-phase"; diff --git a/test/testUtils/helpers/field-helper.ts b/test/testUtils/helpers/field-helper.ts index 72498fd8e16..2866c01209f 100644 --- a/test/testUtils/helpers/field-helper.ts +++ b/test/testUtils/helpers/field-helper.ts @@ -4,7 +4,7 @@ import type { globalScene } from "#app/global-scene"; // -- end tsdoc imports -- import type { BattlerIndex } from "#enums/battler-index"; -import type { Ability } from "#app/data/abilities/ability-class"; +import type { Ability } from "#app/data/abilities/ability"; import { allAbilities } from "#app/data/data-lists"; import type Pokemon from "#app/field/pokemon"; import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; diff --git a/test/testUtils/helpers/overridesHelper.ts b/test/testUtils/helpers/overridesHelper.ts index 32219fa833c..c01acbe0f2e 100644 --- a/test/testUtils/helpers/overridesHelper.ts +++ b/test/testUtils/helpers/overridesHelper.ts @@ -4,7 +4,7 @@ import { AbilityId } from "#enums/ability-id"; import type { ModifierOverride } from "#app/modifier/modifier-type"; import type { BattleStyle } from "#app/overrides"; import Overrides, { defaultOverrides } from "#app/overrides"; -import type { Unlockables } from "#app/system/unlockables"; +import type { Unlockables } from "#enums/unlockables"; import { BiomeId } from "#enums/biome-id"; import { MoveId } from "#enums/move-id"; import type { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; diff --git a/test/testUtils/testFileInitialization.ts b/test/testUtils/testFileInitialization.ts index ba90cba7d5b..da390870300 100644 --- a/test/testUtils/testFileInitialization.ts +++ b/test/testUtils/testFileInitialization.ts @@ -5,6 +5,7 @@ import { initBiomes } from "#app/data/balance/biomes"; import { initEggMoves } from "#app/data/balance/egg-moves"; import { initPokemonPrevolutions, initPokemonStarters } from "#app/data/balance/pokemon-evolutions"; import { initMoves } from "#app/data/moves/move"; +import { initModifierPools } from "#app/modifier/init-modifier-pools"; import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters"; import { initPokemonForms } from "#app/data/pokemon-forms"; import { initSpecies } from "#app/data/pokemon-species"; @@ -22,6 +23,7 @@ import InputText from "phaser3-rex-plugins/plugins/inputtext"; import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { manageListeners } from "./listenersManager"; import { initI18n } from "#app/plugins/i18n"; +import { initModifierTypes } from "#app/modifier/modifier-type"; let wasInitialized = false; /** @@ -88,6 +90,8 @@ export function initTestFile() { if (!wasInitialized) { wasInitialized = true; initI18n(); + initModifierTypes(); + initModifierPools(); initVouchers(); initAchievements(); initStatsKeys(); From ff9aefb0e549aa225ac7259cfd48888592665d06 Mon Sep 17 00:00:00 2001 From: Dean <69436131+emdeann@users.noreply.github.com> Date: Wed, 11 Jun 2025 22:28:27 -0700 Subject: [PATCH 34/51] [Bug] Activate `PostSummon` Abilities in Speed and Priority Order https://github.com/pagefaultgames/pokerogue/pull/5513 * Add prependToPhaseWithCondition and use it in SummonPhase to determine speed order * Move logic to PostSummonPhase * Add test base * Pivot to using sort strategy instead * Add and update tests * Support priority ability activations * Ensure priority abilities are still activated on switch in * Add test for priority * Update to use priority numbers instead of a boolean * Add ability priorities to constructors * Move sorting to BattleScene * Rename phase file * Update import * Move application to applyPostSummonAbAttrs and stop assuming no other phases in queue * Ensure all PostSummonPhases from encounters are added at the same time * Switch to priority queue approach * Ensure that zero/negative priority activations happen after postsummonphase * Revert 07646fe (not needed due to stable sort) * Always create separate ability phases for passive and use boolean instead of priority number when applying * Add test for dynamic updates * Add BattlerIndex import * Clear queues for testing * Benjie suggestion * Split files * Update import in battlescene * Remove extra spaces added by VSCode * Fix other conflicts * Update PhaseManager * Update to use PhaseManager * Immediately start postsummons * Fix test * Fix BattlerIndex import * Remove unused imports * Fix postsummon application * Make priority readonly --- src/data/abilities/ability.ts | 36 +++--- src/data/abilities/apply-ab-attrs.ts | 7 +- src/data/phase-priority-queue.ts | 97 ++++++++++++++++ src/enums/dynamic-phase-type.ts | 6 + src/field/pokemon.ts | 4 + src/phase-manager.ts | 108 ++++++++++++++++-- src/phases/activate-priority-queue-phase.ts | 23 ++++ .../post-summon-activate-ability-phase.ts | 27 +++++ src/phases/post-summon-phase.ts | 7 +- src/phases/switch-summon-phase.ts | 2 +- .../ability_activation_order.test.ts | 95 +++++++++++++++ 11 files changed, 382 insertions(+), 30 deletions(-) create mode 100644 src/data/phase-priority-queue.ts create mode 100644 src/enums/dynamic-phase-type.ts create mode 100644 src/phases/activate-priority-queue-phase.ts create mode 100644 src/phases/post-summon-activate-ability-phase.ts create mode 100644 test/abilities/ability_activation_order.test.ts diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 2efe3607b4f..d8743a0effe 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -86,6 +86,7 @@ export class Ability implements Localizable { public name: string; public description: string; public generation: number; + public readonly postSummonPriority: number; public isBypassFaint: boolean; public isIgnorable: boolean; public isSuppressable = true; @@ -94,11 +95,12 @@ export class Ability implements Localizable { public attrs: AbAttr[]; public conditions: AbAttrCondition[]; - constructor(id: AbilityId, generation: number) { + constructor(id: AbilityId, generation: number, postSummonPriority = 0) { this.id = id; this.nameAppend = ""; this.generation = generation; + this.postSummonPriority = postSummonPriority; this.attrs = []; this.conditions = []; @@ -8104,7 +8106,7 @@ export function initAbilities() { .conditionalAttr(p => globalScene.currentBattle.double && [ AbilityId.PLUS, AbilityId.MINUS ].some(a => (p.getAlly()?.hasAbility(a) ?? false)), StatMultiplierAbAttr, Stat.SPATK, 1.5), new Ability(AbilityId.MINUS, 3) .conditionalAttr(p => globalScene.currentBattle.double && [ AbilityId.PLUS, AbilityId.MINUS ].some(a => (p.getAlly()?.hasAbility(a) ?? false)), StatMultiplierAbAttr, Stat.SPATK, 1.5), - new Ability(AbilityId.FORECAST, 3) + new Ability(AbilityId.FORECAST, 3, -2) .uncopiable() .unreplaceable() .attr(NoFusionAbilityAbAttr) @@ -8238,7 +8240,7 @@ export function initAbilities() { .attr(StatusEffectImmunityAbAttr) .condition(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN)) .ignorable(), - new Ability(AbilityId.KLUTZ, 4) + new Ability(AbilityId.KLUTZ, 4, 1) .unimplemented(), new Ability(AbilityId.MOLD_BREAKER, 4) .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonMoldBreaker", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) @@ -8290,7 +8292,7 @@ export function initAbilities() { .uncopiable() .unsuppressable() .unreplaceable(), - new Ability(AbilityId.FLOWER_GIFT, 4) + new Ability(AbilityId.FLOWER_GIFT, 4, -2) .conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), StatMultiplierAbAttr, Stat.ATK, 1.5) .conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), StatMultiplierAbAttr, Stat.SPDEF, 1.5) .conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), AllyStatMultiplierAbAttr, Stat.ATK, 1.5) @@ -8312,7 +8314,7 @@ export function initAbilities() { new Ability(AbilityId.CONTRARY, 5) .attr(StatStageChangeMultiplierAbAttr, -1) .ignorable(), - new Ability(AbilityId.UNNERVE, 5) + new Ability(AbilityId.UNNERVE, 5, 1) .attr(PreventBerryUseAbAttr), new Ability(AbilityId.DEFIANT, 5) .attr(PostStatStageChangeStatStageChangeAbAttr, (_target, _statsChanged, stages) => stages < 0, [ Stat.ATK ], 2), @@ -8554,7 +8556,7 @@ export function initAbilities() { .attr(PostDefendStatStageChangeAbAttr, (_target, user, move) => user.getMoveType(move) === PokemonType.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2), new Ability(AbilityId.MERCILESS, 7) .attr(ConditionalCritAbAttr, (_user, target, _move) => target?.status?.effect === StatusEffect.TOXIC || target?.status?.effect === StatusEffect.POISON), - new Ability(AbilityId.SHIELDS_DOWN, 7) + new Ability(AbilityId.SHIELDS_DOWN, 7, -1) .attr(PostBattleInitFormChangeAbAttr, () => 0) .attr(PostSummonFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0)) .attr(PostTurnFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0)) @@ -8592,7 +8594,7 @@ export function initAbilities() { .attr(MoveTypeChangeAbAttr, PokemonType.ELECTRIC, 1.2, (_user, _target, move) => move.type === PokemonType.NORMAL), new Ability(AbilityId.SURGE_SURFER, 7) .conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), StatMultiplierAbAttr, Stat.SPD, 2), - new Ability(AbilityId.SCHOOLING, 7) + new Ability(AbilityId.SCHOOLING, 7, -1) .attr(PostBattleInitFormChangeAbAttr, () => 0) .attr(PostSummonFormChangeAbAttr, p => p.level < 20 || p.getHpRatio() <= 0.25 ? 0 : 1) .attr(PostTurnFormChangeAbAttr, p => p.level < 20 || p.getHpRatio() <= 0.25 ? 0 : 1) @@ -8761,7 +8763,7 @@ export function initAbilities() { .ignorable(), new Ability(AbilityId.RIPEN, 8) .attr(DoubleBerryEffectAbAttr), - new Ability(AbilityId.ICE_FACE, 8) + new Ability(AbilityId.ICE_FACE, 8, -2) .attr(NoTransformAbilityAbAttr) .attr(NoFusionAbilityAbAttr) // Add BattlerTagType.ICE_FACE if the pokemon is in ice face form @@ -8781,7 +8783,7 @@ export function initAbilities() { .ignorable(), new Ability(AbilityId.POWER_SPOT, 8) .attr(AllyMoveCategoryPowerBoostAbAttr, [ MoveCategory.SPECIAL, MoveCategory.PHYSICAL ], 1.3), - new Ability(AbilityId.MIMICRY, 8) + new Ability(AbilityId.MIMICRY, 8, -1) .attr(TerrainEventTypeChangeAbAttr), new Ability(AbilityId.SCREEN_CLEANER, 8) .attr(PostSummonRemoveArenaTagAbAttr, [ ArenaTagType.AURORA_VEIL, ArenaTagType.LIGHT_SCREEN, ArenaTagType.REFLECT ]), @@ -8796,7 +8798,7 @@ export function initAbilities() { .edgeCase(), // interacts incorrectly with rock head. It's meant to switch abilities before recoil would apply so that a pokemon with rock head would lose rock head first and still take the recoil new Ability(AbilityId.GORILLA_TACTICS, 8) .attr(GorillaTacticsAbAttr), - new Ability(AbilityId.NEUTRALIZING_GAS, 8) + new Ability(AbilityId.NEUTRALIZING_GAS, 8, 2) .attr(PostSummonAddArenaTagAbAttr, true, ArenaTagType.NEUTRALIZING_GAS, 0) .attr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr) .uncopiable() @@ -8828,14 +8830,14 @@ export function initAbilities() { .attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1), new Ability(AbilityId.GRIM_NEIGH, 8) .attr(PostVictoryStatStageChangeAbAttr, Stat.SPATK, 1), - new Ability(AbilityId.AS_ONE_GLASTRIER, 8) + new Ability(AbilityId.AS_ONE_GLASTRIER, 8, 1) .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonAsOneGlastrier", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) .attr(PreventBerryUseAbAttr) .attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1) .uncopiable() .unreplaceable() .unsuppressable(), - new Ability(AbilityId.AS_ONE_SPECTRIER, 8) + new Ability(AbilityId.AS_ONE_SPECTRIER, 8, 1) .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonAsOneSpectrier", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) .attr(PreventBerryUseAbAttr) .attr(PostVictoryStatStageChangeAbAttr, Stat.SPATK, 1) @@ -8893,12 +8895,12 @@ export function initAbilities() { .edgeCase(), // Encore, Frenzy, and other non-`TURN_END` tags don't lapse correctly on the commanding Pokemon. new Ability(AbilityId.ELECTROMORPHOSIS, 9) .attr(PostDefendApplyBattlerTagAbAttr, (_target, _user, move) => move.category !== MoveCategory.STATUS, BattlerTagType.CHARGED), - new Ability(AbilityId.PROTOSYNTHESIS, 9) + new Ability(AbilityId.PROTOSYNTHESIS, 9, -2) .conditionalAttr(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN), PostSummonAddBattlerTagAbAttr, BattlerTagType.PROTOSYNTHESIS, 0, true) .attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.PROTOSYNTHESIS, 0, WeatherType.SUNNY, WeatherType.HARSH_SUN) .uncopiable() .attr(NoTransformAbilityAbAttr), - new Ability(AbilityId.QUARK_DRIVE, 9) + new Ability(AbilityId.QUARK_DRIVE, 9, -2) .conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), PostSummonAddBattlerTagAbAttr, BattlerTagType.QUARK_DRIVE, 0, true) .attr(PostTerrainChangeAddBattlerTagAttr, BattlerTagType.QUARK_DRIVE, 0, TerrainType.ELECTRIC) .uncopiable() @@ -8942,7 +8944,7 @@ export function initAbilities() { new Ability(AbilityId.SUPREME_OVERLORD, 9) .attr(VariableMovePowerBoostAbAttr, (user, _target, _move) => 1 + 0.1 * Math.min(user.isPlayer() ? globalScene.arena.playerFaints : globalScene.currentBattle.enemyFaints, 5)) .partial(), // Should only boost once, on summon - new Ability(AbilityId.COSTAR, 9) + new Ability(AbilityId.COSTAR, 9, -2) .attr(PostSummonCopyAllyStatsAbAttr), new Ability(AbilityId.TOXIC_DEBRIS, 9) .attr(PostDefendApplyArenaTrapTagAbAttr, (_target, _user, move) => move.category === MoveCategory.PHYSICAL, ArenaTagType.TOXIC_SPIKES) @@ -8964,7 +8966,7 @@ export function initAbilities() { .ignorable(), new Ability(AbilityId.SUPERSWEET_SYRUP, 9) .attr(PostSummonStatStageChangeAbAttr, [ Stat.EVA ], -1), - new Ability(AbilityId.HOSPITALITY, 9) + new Ability(AbilityId.HOSPITALITY, 9, -2) .attr(PostSummonAllyHealAbAttr, 4, true), new Ability(AbilityId.TOXIC_CHAIN, 9) .attr(PostAttackApplyStatusEffectAbAttr, false, 30, StatusEffect.TOXIC), @@ -8988,7 +8990,7 @@ export function initAbilities() { .uncopiable() .unreplaceable() .attr(NoTransformAbilityAbAttr), - new Ability(AbilityId.TERA_SHIFT, 9) + new Ability(AbilityId.TERA_SHIFT, 9, 2) .attr(PostSummonFormChangeAbAttr, p => p.getFormKey() ? 0 : 1) .uncopiable() .unreplaceable() diff --git a/src/data/abilities/apply-ab-attrs.ts b/src/data/abilities/apply-ab-attrs.ts index e2f8ec9c14c..fdbd2652698 100644 --- a/src/data/abilities/apply-ab-attrs.ts +++ b/src/data/abilities/apply-ab-attrs.ts @@ -470,15 +470,18 @@ export function applyPostVictoryAbAttrs( export function applyPostSummonAbAttrs( attrType: AbAttrMap[K] extends PostSummonAbAttr ? K : never, pokemon: Pokemon, + passive = false, simulated = false, ...args: any[] ): void { - applyAbAttrsInternal( - attrType, + applySingleAbAttrs( pokemon, + passive, + attrType, (attr, passive) => (attr as PostSummonAbAttr).applyPostSummon(pokemon, passive, simulated, args), (attr, passive) => (attr as PostSummonAbAttr).canApplyPostSummon(pokemon, passive, simulated, args), args, + false, simulated, ); } diff --git a/src/data/phase-priority-queue.ts b/src/data/phase-priority-queue.ts new file mode 100644 index 00000000000..b815a6ac34f --- /dev/null +++ b/src/data/phase-priority-queue.ts @@ -0,0 +1,97 @@ +import { globalScene } from "#app/global-scene"; +import type { Phase } from "#app/phase"; +import { ActivatePriorityQueuePhase } from "#app/phases/activate-priority-queue-phase"; +import type { PostSummonPhase } from "#app/phases/post-summon-phase"; +import { PostSummonActivateAbilityPhase } from "#app/phases/post-summon-activate-ability-phase"; +import { Stat } from "#enums/stat"; +import { BooleanHolder } from "#app/utils/common"; +import { TrickRoomTag } from "#app/data/arena-tag"; +import { DynamicPhaseType } from "#enums/dynamic-phase-type"; + +/** + * Stores a list of {@linkcode Phase}s + * + * Dynamically updates ordering to always pop the highest "priority", based on implementation of {@linkcode reorder} + */ +export abstract class PhasePriorityQueue { + protected abstract queue: Phase[]; + + /** + * Sorts the elements in the queue + */ + public abstract reorder(): void; + + /** + * Calls {@linkcode reorder} and shifts the queue + * @returns The front element of the queue after sorting + */ + public pop(): Phase | undefined { + this.reorder(); + return this.queue.shift(); + } + + /** + * Adds a phase to the queue + * @param phase The phase to add + */ + public push(phase: Phase): void { + this.queue.push(phase); + } + + /** + * Removes all phases from the queue + */ + public clear(): void { + this.queue.splice(0, this.queue.length); + } +} + +/** + * Priority Queue for {@linkcode PostSummonPhase} and {@linkcode PostSummonActivateAbilityPhase} + * + * Orders phases first by ability priority, then by the {@linkcode Pokemon}'s effective speed + */ +export class PostSummonPhasePriorityQueue extends PhasePriorityQueue { + protected override queue: PostSummonPhase[] = []; + + public override reorder(): void { + this.queue.sort((phaseA: PostSummonPhase, phaseB: PostSummonPhase) => { + if (phaseA.getPriority() === phaseB.getPriority()) { + return ( + (phaseB.getPokemon().getEffectiveStat(Stat.SPD) - phaseA.getPokemon().getEffectiveStat(Stat.SPD)) * + (isTrickRoom() ? -1 : 1) + ); + } + + return phaseB.getPriority() - phaseA.getPriority(); + }); + } + + public override push(phase: PostSummonPhase): void { + super.push(phase); + this.queueAbilityPhase(phase); + } + + /** + * Queues all necessary {@linkcode PostSummonActivateAbilityPhase}s for each pushed {@linkcode PostSummonPhase} + * @param phase The {@linkcode PostSummonPhase} that was pushed onto the queue + */ + private queueAbilityPhase(phase: PostSummonPhase): void { + const phasePokemon = phase.getPokemon(); + + phasePokemon.getAbilityPriorities().forEach((priority, idx) => { + this.queue.push(new PostSummonActivateAbilityPhase(phasePokemon.getBattlerIndex(), priority, !!idx)); + globalScene.phaseManager.appendToPhase( + new ActivatePriorityQueuePhase(DynamicPhaseType.POST_SUMMON), + "ActivatePriorityQueuePhase", + (p: ActivatePriorityQueuePhase) => p.getType() === DynamicPhaseType.POST_SUMMON, + ); + }); + } +} + +function isTrickRoom(): boolean { + const speedReversed = new BooleanHolder(false); + globalScene.arena.applyTags(TrickRoomTag, false, speedReversed); + return speedReversed.value; +} diff --git a/src/enums/dynamic-phase-type.ts b/src/enums/dynamic-phase-type.ts new file mode 100644 index 00000000000..a34ac371668 --- /dev/null +++ b/src/enums/dynamic-phase-type.ts @@ -0,0 +1,6 @@ +/** + * Enum representation of the phase types held by implementations of {@linkcode PhasePriorityQueue} + */ +export enum DynamicPhaseType { + POST_SUMMON +} diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 6a34d936a51..964d66d352e 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2181,6 +2181,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().hasAttr(attrType); } + public getAbilityPriorities(): [number, number] { + return [this.getAbility().postSummonPriority, this.getPassiveAbility().postSummonPriority]; + } + /** * Gets the weight of the Pokemon with subtractive modifiers (Autotomize) happening first * and then multiplicative modifiers happening after (Heavy Metal and Light Metal) diff --git a/src/phase-manager.ts b/src/phase-manager.ts index 230e0331caf..b4fefe3f2d6 100644 --- a/src/phase-manager.ts +++ b/src/phase-manager.ts @@ -2,6 +2,7 @@ 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 { ActivatePriorityQueuePhase } from "#app/phases/activate-priority-queue-phase"; 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"; @@ -11,7 +12,9 @@ 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 type { Constructor } from "#app/utils/common"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; +import type { DynamicPhaseType } from "#enums/dynamic-phase-type"; import { EggHatchPhase } from "#app/phases/egg-hatch-phase"; import { EggLapsePhase } from "#app/phases/egg-lapse-phase"; import { EggSummaryPhase } from "#app/phases/egg-summary-phase"; @@ -55,6 +58,7 @@ 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 { type PhasePriorityQueue, PostSummonPhasePriorityQueue } from "#app/data/phase-priority-queue"; import { PokemonAnimPhase } from "#app/phases/pokemon-anim-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase"; @@ -111,6 +115,7 @@ import { WeatherEffectPhase } from "#app/phases/weather-effect-phase"; * This allows for easy creation of new phases without needing to import each phase individually. */ const PHASES = Object.freeze({ + ActivatePriorityQueuePhase, AddEnemyBuffModifierPhase, AttemptCapturePhase, AttemptRunPhase, @@ -222,9 +227,19 @@ export class PhaseManager { private phaseQueuePrependSpliceIndex = -1; private nextCommandPhaseQueue: Phase[] = []; + /** Storage for {@linkcode PhasePriorityQueue}s which hold phases whose order dynamically changes */ + private dynamicPhaseQueues: PhasePriorityQueue[]; + /** Parallel array to {@linkcode dynamicPhaseQueues} - matches phase types to their queues */ + private dynamicPhaseTypes: Constructor[]; + private currentPhase: Phase | null = null; private standbyPhase: Phase | null = null; + constructor() { + this.dynamicPhaseQueues = [new PostSummonPhasePriorityQueue()]; + this.dynamicPhaseTypes = [PostSummonPhase]; + } + /* Phase Functions */ getCurrentPhase(): Phase | null { return this.currentPhase; @@ -254,7 +269,11 @@ export class PhaseManager { * @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); + if (this.getDynamicPhaseType(phase) !== undefined) { + this.pushDynamicPhase(phase); + } else { + (!defer ? this.phaseQueue : this.nextCommandPhaseQueue).push(phase); + } } /** @@ -283,6 +302,7 @@ export class PhaseManager { for (const queue of [this.phaseQueue, this.phaseQueuePrepend, this.conditionalQueue, this.nextCommandPhaseQueue]) { queue.splice(0, queue.length); } + this.dynamicPhaseQueues.forEach(queue => queue.clear()); this.currentPhase = null; this.standbyPhase = null; this.clearPhaseQueueSplice(); @@ -333,8 +353,9 @@ export class PhaseManager { this.currentPhase = this.phaseQueue.shift() ?? null; + const unactivatedConditionalPhases: [() => boolean, Phase][] = []; // Check if there are any conditional phases queued - if (this.conditionalQueue?.length) { + while (this.conditionalQueue?.length) { // Retrieve the first conditional phase from the queue const conditionalPhase = this.conditionalQueue.shift(); // Evaluate the condition associated with the phase @@ -343,11 +364,12 @@ export class PhaseManager { 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); + unactivatedConditionalPhases.push(conditionalPhase); } else { console.warn("condition phase is undefined/null!", conditionalPhase); } } + this.conditionalQueue.push(...unactivatedConditionalPhases); if (this.currentPhase) { console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;"); @@ -431,17 +453,18 @@ export class PhaseManager { } /** - * 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 + * 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} + * @param condition Condition the target phase must meet to be appended to * @returns `true` if a `targetPhase` was found to append to */ - appendToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean { + appendToPhase(phase: Phase | Phase[], targetPhase: PhaseString, condition?: (p: Phase) => boolean): boolean { if (!Array.isArray(phase)) { phase = [phase]; } const target = PHASES[targetPhase]; - const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target); + const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target && (!condition || condition(ph))); if (targetIndex !== -1 && this.phaseQueue.length > targetIndex) { this.phaseQueue.splice(targetIndex + 1, 0, ...phase); @@ -451,6 +474,68 @@ export class PhaseManager { return false; } + /** + * Checks a phase and returns the matching {@linkcode DynamicPhaseType}, or undefined if it does not match one + * @param phase The phase to check + * @returns The corresponding {@linkcode DynamicPhaseType} or `undefined` + */ + public getDynamicPhaseType(phase: Phase | null): DynamicPhaseType | undefined { + let phaseType: DynamicPhaseType | undefined; + this.dynamicPhaseTypes.forEach((cls, index) => { + if (phase instanceof cls) { + phaseType = index; + } + }); + + return phaseType; + } + + /** + * Pushes a phase onto its corresponding dynamic queue and marks the activation point in {@linkcode phaseQueue} + * + * The {@linkcode ActivatePriorityQueuePhase} will run the top phase in the dynamic queue (not necessarily {@linkcode phase}) + * @param phase The phase to push + */ + public pushDynamicPhase(phase: Phase): void { + const type = this.getDynamicPhaseType(phase); + if (type === undefined) { + return; + } + + this.pushPhase(new ActivatePriorityQueuePhase(type)); + this.dynamicPhaseQueues[type].push(phase); + } + + /** + * Unshifts the top phase from the corresponding dynamic queue onto {@linkcode phaseQueue} + * @param type {@linkcode DynamicPhaseType} The type of dynamic phase to start + */ + public startDynamicPhaseType(type: DynamicPhaseType): void { + const phase = this.dynamicPhaseQueues[type].pop(); + if (phase) { + this.unshiftPhase(phase); + } + } + + /** + * Unshifts an {@linkcode ActivatePriorityQueuePhase} for {@linkcode phase}, then pushes {@linkcode phase} to its dynamic queue + * + * This is the same as {@linkcode pushDynamicPhase}, except the activation phase is unshifted + * + * {@linkcode phase} is not guaranteed to be the next phase from the queue to run (if the queue is not empty) + * @param phase The phase to add + * @returns + */ + public startDynamicPhase(phase: Phase): void { + const type = this.getDynamicPhaseType(phase); + if (type === undefined) { + return; + } + + this.unshiftPhase(new ActivatePriorityQueuePhase(type)); + this.dynamicPhaseQueues[type].push(phase); + } + /** * Adds a MessagePhase, either to PhaseQueuePrepend or nextCommandPhaseQueue * @param message - string for MessagePhase @@ -578,4 +663,11 @@ export class PhaseManager { ): boolean { return this.appendToPhase(this.create(phase, ...args), targetPhase); } + + public startNewDynamicPhase( + phase: T, + ...args: ConstructorParameters + ): void { + this.startDynamicPhase(this.create(phase, ...args)); + } } diff --git a/src/phases/activate-priority-queue-phase.ts b/src/phases/activate-priority-queue-phase.ts new file mode 100644 index 00000000000..df42c491676 --- /dev/null +++ b/src/phases/activate-priority-queue-phase.ts @@ -0,0 +1,23 @@ +import type { DynamicPhaseType } from "#enums/dynamic-phase-type"; +import { globalScene } from "#app/global-scene"; +import { Phase } from "#app/phase"; + +export class ActivatePriorityQueuePhase extends Phase { + public readonly phaseName = "ActivatePriorityQueuePhase"; + private type: DynamicPhaseType; + + constructor(type: DynamicPhaseType) { + super(); + this.type = type; + } + + override start() { + super.start(); + globalScene.phaseManager.startDynamicPhaseType(this.type); + this.end(); + } + + public getType(): DynamicPhaseType { + return this.type; + } +} diff --git a/src/phases/post-summon-activate-ability-phase.ts b/src/phases/post-summon-activate-ability-phase.ts new file mode 100644 index 00000000000..ba6c80d4ee0 --- /dev/null +++ b/src/phases/post-summon-activate-ability-phase.ts @@ -0,0 +1,27 @@ +import { applyPostSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs"; +import { PostSummonPhase } from "#app/phases/post-summon-phase"; +import type { BattlerIndex } from "#enums/battler-index"; + +/** + * Helper to {@linkcode PostSummonPhase} which applies abilities + */ +export class PostSummonActivateAbilityPhase extends PostSummonPhase { + private priority: number; + private passive: boolean; + + constructor(battlerIndex: BattlerIndex, priority: number, passive: boolean) { + super(battlerIndex); + this.priority = priority; + this.passive = passive; + } + + start() { + applyPostSummonAbAttrs("PostSummonAbAttr", this.getPokemon(), this.passive, false); + + this.end(); + } + + public override getPriority() { + return this.priority; + } +} diff --git a/src/phases/post-summon-phase.ts b/src/phases/post-summon-phase.ts index 3acd7ca24e9..26fffd1b024 100644 --- a/src/phases/post-summon-phase.ts +++ b/src/phases/post-summon-phase.ts @@ -1,10 +1,10 @@ import { globalScene } from "#app/global-scene"; -import { applyAbAttrs, applyPostSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { ArenaTrapTag } from "#app/data/arena-tag"; import { StatusEffect } from "#app/enums/status-effect"; import { PokemonPhase } from "./pokemon-phase"; import { MysteryEncounterPostSummonTag } from "#app/data/battler-tags"; import { BattlerTagType } from "#enums/battler-tag-type"; +import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; export class PostSummonPhase extends PokemonPhase { public readonly phaseName = "PostSummonPhase"; @@ -26,7 +26,6 @@ export class PostSummonPhase extends PokemonPhase { pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON); } - applyPostSummonAbAttrs("PostSummonAbAttr", pokemon); const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); for (const p of field) { applyAbAttrs("CommanderAbAttr", p, null, false); @@ -34,4 +33,8 @@ export class PostSummonPhase extends PokemonPhase { this.end(); } + + public getPriority() { + return 0; + } } diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index 6b76d4e8926..af03cc42b54 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -245,7 +245,7 @@ export class SwitchSummonPhase extends SummonPhase { } queuePostSummon(): void { - globalScene.phaseManager.unshiftNew("PostSummonPhase", this.getPokemon().getBattlerIndex()); + globalScene.phaseManager.startNewDynamicPhase("PostSummonPhase", this.getPokemon().getBattlerIndex()); } /** diff --git a/test/abilities/ability_activation_order.test.ts b/test/abilities/ability_activation_order.test.ts new file mode 100644 index 00000000000..04adf40b623 --- /dev/null +++ b/test/abilities/ability_activation_order.test.ts @@ -0,0 +1,95 @@ +import { AbilityId } from "#enums/ability-id"; +import { MoveId } from "#enums/move-id"; +import { SpeciesId } from "#enums/species-id"; +import { Stat } from "#enums/stat"; +import { WeatherType } from "#enums/weather-type"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Ability Activation Order", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([MoveId.SPLASH]) + .ability(AbilityId.BALL_FETCH) + .battleStyle("single") + .disableCrits() + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyMoveset(MoveId.SPLASH); + }); + + it("should activate the ability of the faster Pokemon first", async () => { + game.override.enemyLevel(100).ability(AbilityId.DRIZZLE).enemyAbility(AbilityId.DROUGHT); + await game.classicMode.startBattle([SpeciesId.SLOWPOKE]); + + // Enemy's ability should activate first, so sun ends up replaced with rain + expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.RAIN); + }); + + it("should consider base stat boosting items in determining order", async () => { + game.override + .startingLevel(25) + .enemyLevel(50) + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.DROUGHT) + .ability(AbilityId.DRIZZLE) + .startingHeldItems([{ name: "BASE_STAT_BOOSTER", type: Stat.SPD, count: 100 }]); + + await game.classicMode.startBattle([SpeciesId.MAGIKARP]); + expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SUNNY); + }); + + it("should consider stat boosting items in determining order", async () => { + game.override + .startingLevel(35) + .enemyLevel(50) + .enemySpecies(SpeciesId.DITTO) + .enemyAbility(AbilityId.DROUGHT) + .ability(AbilityId.DRIZZLE) + .startingHeldItems([{ name: "SPECIES_STAT_BOOSTER", type: "QUICK_POWDER" }]); + + await game.classicMode.startBattle([SpeciesId.DITTO]); + expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SUNNY); + }); + + it("should activate priority abilities first", async () => { + game.override + .startingLevel(1) + .enemyLevel(100) + .enemySpecies(SpeciesId.ACCELGOR) + .enemyAbility(AbilityId.DROUGHT) + .ability(AbilityId.NEUTRALIZING_GAS); + + await game.classicMode.startBattle([SpeciesId.SLOWPOKE]); + expect(game.scene.arena.weather).toBeUndefined(); + }); + + it("should update dynamically based on speed order", async () => { + game.override + .startingLevel(35) + .enemyLevel(50) + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.SLOW_START) + .enemyPassiveAbility(AbilityId.DROUGHT) + .ability(AbilityId.DRIZZLE); + + await game.classicMode.startBattle([SpeciesId.MAGIKARP]); + // Slow start activates and makes enemy slower, so drought activates after drizzle + expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SUNNY); + }); +}); From 425985a05687896fa72e48ae252b43c25b81ae50 Mon Sep 17 00:00:00 2001 From: Mourouh <61661226+Mourouh@users.noreply.github.com> Date: Thu, 12 Jun 2025 23:56:37 +0200 Subject: [PATCH 35/51] [Balance] Add wild encounter chance to Maushold and Dudunsparce forms (#5975) Added wild encounter chance to Maushold and Dudunsparce forms --- src/battle-scene.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index d5cb5dcae42..81c65d85e06 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1635,6 +1635,9 @@ export default class BattleScene extends SceneBase { case SpeciesId.TATSUGIRI: case SpeciesId.PALDEA_TAUROS: return randSeedInt(species.forms.length); + case SpeciesId.MAUSHOLD: + case SpeciesId.DUDUNSPARCE: + return !randSeedInt(4) ? 1 : 0; case SpeciesId.PIKACHU: if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30) { return 0; // Ban Cosplay and Partner Pika from Trainers before wave 30 From 7c6189e8126242c1b9dcec5b0ca274ee4d85758c Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 12 Jun 2025 21:30:01 -0700 Subject: [PATCH 36/51] [Refactor] Create utility function `coerceArray` (#5723) * [Refactor] Create utility function `makeArray` This replaces the `if(!Array.isArray(var)) { var = [var] }` pattern * Replace `if` with ternary, rename to `coerceArray` * Add TSDocs * Improve type inferencing * Replace missed `Array.isArray` checks * Apply Biome * Re-apply changes to phase manager * Re-apply to `SpeciesFormChangeStatusEffectTrigger` constructor Apply to new instances in test mocks --- src/data/abilities/ability.ts | 5 +-- src/data/battle-anims.ts | 4 +-- src/data/battler-tags.ts | 4 +-- .../mystery-encounter-requirements.ts | 30 +++++++++--------- .../mystery-encounters/mystery-encounter.ts | 8 ++--- .../can-learn-move-requirement.ts | 4 +-- .../utils/encounter-phase-utils.ts | 6 ++-- .../pokemon-forms/form-change-triggers.ts | 7 ++--- src/data/trainers/trainer-config.ts | 31 +++++++------------ src/field/pokemon-sprite-sparkle-handler.ts | 10 ++---- src/field/pokemon.ts | 5 ++- src/phase-manager.ts | 10 ++---- src/plugins/cache-busted-loader-plugin.ts | 10 ++---- src/plugins/vite/vite-minify-json-plugin.ts | 4 +-- src/scene-base.ts | 6 ++-- src/ui/pokemon-icon-anim-handler.ts | 10 ++---- src/utils/common.ts | 9 ++++++ test/testUtils/helpers/moveHelper.ts | 5 ++- test/testUtils/helpers/overridesHelper.ts | 10 ++---- .../mocks/mocksContainer/mockContainer.ts | 17 +++------- .../mocks/mocksContainer/mockRectangle.ts | 7 ++--- .../mocks/mocksContainer/mockSprite.ts | 10 +++--- 22 files changed, 88 insertions(+), 124 deletions(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index d8743a0effe..ef4529c361e 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -9,6 +9,7 @@ import { randSeedInt, type Constructor, randSeedFloat, + coerceArray, } from "#app/utils/common"; import { getPokemonNameWithAffix } from "#app/messages"; import { GroundedTag } from "#app/data/battler-tags"; @@ -4689,7 +4690,7 @@ export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr { constructor(immuneTagTypes: BattlerTagType | BattlerTagType[]) { super(true); - this.immuneTagTypes = Array.isArray(immuneTagTypes) ? immuneTagTypes : [immuneTagTypes]; + this.immuneTagTypes = coerceArray(immuneTagTypes); } override canApplyPreApplyBattlerTag( @@ -6694,7 +6695,7 @@ export class FlinchStatStageChangeAbAttr extends FlinchEffectAbAttr { constructor(stats: BattleStat[], stages: number) { super(); - this.stats = Array.isArray(stats) ? stats : [stats]; + this.stats = stats; this.stages = stages; } diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index 7aa419ca470..131086625df 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import { allMoves } from "./data-lists"; import { MoveFlags } from "#enums/MoveFlags"; import type Pokemon from "../field/pokemon"; -import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils/common"; +import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName, coerceArray } from "../utils/common"; import type { BattlerIndex } from "#enums/battler-index"; import { MoveId } from "#enums/move-id"; import { SubstituteTag } from "./battler-tags"; @@ -520,7 +520,7 @@ function logMissingMoveAnim(move: MoveId, ...optionalParams: any[]) { * @param encounterAnim one or more animations to fetch */ export async function initEncounterAnims(encounterAnim: EncounterAnim | EncounterAnim[]): Promise { - const anims = Array.isArray(encounterAnim) ? encounterAnim : [encounterAnim]; + const anims = coerceArray(encounterAnim); const encounterAnimNames = getEnumKeys(EncounterAnim); const encounterAnimFetches: Promise>[] = []; for (const anim of anims) { diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 0daf1913737..89d5a76159f 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -21,7 +21,7 @@ import type { MoveEffectPhase } from "#app/phases/move-effect-phase"; import type { MovePhase } from "#app/phases/move-phase"; import type { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase"; import i18next from "#app/plugins/i18n"; -import { BooleanHolder, getFrameMs, NumberHolder, toDmgValue } from "#app/utils/common"; +import { BooleanHolder, coerceArray, getFrameMs, NumberHolder, toDmgValue } from "#app/utils/common"; import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#enums/battler-tag-type"; import { MoveId } from "#enums/move-id"; @@ -50,7 +50,7 @@ export class BattlerTag { isBatonPassable = false, ) { this.tagType = tagType; - this.lapseTypes = Array.isArray(lapseType) ? lapseType : [lapseType]; + this.lapseTypes = coerceArray(lapseType); this.turnCount = turnCount; this.sourceMove = sourceMove!; // TODO: is this bang correct? this.sourceId = sourceId; diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index bca34be723b..a6e6e84846f 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -11,7 +11,7 @@ import { WeatherType } from "#enums/weather-type"; import type { PlayerPokemon } from "#app/field/pokemon"; import { AttackTypeBoosterModifier } from "#app/modifier/modifier"; import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; -import { isNullOrUndefined } from "#app/utils/common"; +import { coerceArray, isNullOrUndefined } from "#app/utils/common"; import type { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; @@ -272,7 +272,7 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement { constructor(timeOfDay: TimeOfDay | TimeOfDay[]) { super(); - this.requiredTimeOfDay = Array.isArray(timeOfDay) ? timeOfDay : [timeOfDay]; + this.requiredTimeOfDay = coerceArray(timeOfDay); } override meetsRequirement(): boolean { @@ -294,7 +294,7 @@ export class WeatherRequirement extends EncounterSceneRequirement { constructor(weather: WeatherType | WeatherType[]) { super(); - this.requiredWeather = Array.isArray(weather) ? weather : [weather]; + this.requiredWeather = coerceArray(weather); } override meetsRequirement(): boolean { @@ -360,7 +360,7 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement { constructor(heldItem: string | string[], minNumberOfItems = 1) { super(); this.minNumberOfItems = minNumberOfItems; - this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem]; + this.requiredHeldItemModifiers = coerceArray(heldItem); } override meetsRequirement(): boolean { @@ -426,7 +426,7 @@ export class SpeciesRequirement extends EncounterPokemonRequirement { super(); this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; - this.requiredSpecies = Array.isArray(species) ? species : [species]; + this.requiredSpecies = coerceArray(species); } override meetsRequirement(): boolean { @@ -466,7 +466,7 @@ export class NatureRequirement extends EncounterPokemonRequirement { super(); this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; - this.requiredNature = Array.isArray(nature) ? nature : [nature]; + this.requiredNature = coerceArray(nature); } override meetsRequirement(): boolean { @@ -504,7 +504,7 @@ export class TypeRequirement extends EncounterPokemonRequirement { this.excludeFainted = excludeFainted; this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; - this.requiredType = Array.isArray(type) ? type : [type]; + this.requiredType = coerceArray(type); } override meetsRequirement(): boolean { @@ -558,7 +558,7 @@ export class MoveRequirement extends EncounterPokemonRequirement { this.excludeDisallowedPokemon = excludeDisallowedPokemon; this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; - this.requiredMoves = Array.isArray(moves) ? moves : [moves]; + this.requiredMoves = coerceArray(moves); } override meetsRequirement(): boolean { @@ -609,7 +609,7 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement { super(); this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; - this.requiredMoves = Array.isArray(learnableMove) ? learnableMove : [learnableMove]; + this.requiredMoves = coerceArray(learnableMove); } override meetsRequirement(): boolean { @@ -665,7 +665,7 @@ export class AbilityRequirement extends EncounterPokemonRequirement { this.excludeDisallowedPokemon = excludeDisallowedPokemon; this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; - this.requiredAbilities = Array.isArray(abilities) ? abilities : [abilities]; + this.requiredAbilities = coerceArray(abilities); } override meetsRequirement(): boolean { @@ -710,7 +710,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement { super(); this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; - this.requiredStatusEffect = Array.isArray(statusEffect) ? statusEffect : [statusEffect]; + this.requiredStatusEffect = coerceArray(statusEffect); } override meetsRequirement(): boolean { @@ -785,7 +785,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen super(); this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; - this.requiredFormChangeItem = Array.isArray(formChangeItem) ? formChangeItem : [formChangeItem]; + this.requiredFormChangeItem = coerceArray(formChangeItem); } override meetsRequirement(): boolean { @@ -843,7 +843,7 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement { super(); this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; - this.requiredEvolutionItem = Array.isArray(evolutionItems) ? evolutionItems : [evolutionItems]; + this.requiredEvolutionItem = coerceArray(evolutionItems); } override meetsRequirement(): boolean { @@ -908,7 +908,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement { super(); this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; - this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem]; + this.requiredHeldItemModifiers = coerceArray(heldItem); this.requireTransferable = requireTransferable; } @@ -972,7 +972,7 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe super(); this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; - this.requiredHeldItemTypes = Array.isArray(heldItemTypes) ? heldItemTypes : [heldItemTypes]; + this.requiredHeldItemTypes = coerceArray(heldItemTypes); this.requireTransferable = requireTransferable; } diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index 6510e32fe76..bd3082afe19 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -2,7 +2,7 @@ import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encoun import type { PlayerPokemon } from "#app/field/pokemon"; import type { PokemonMove } from "../moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; -import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils/common"; +import { capitalizeFirstLetter, coerceArray, isNullOrUndefined } from "#app/utils/common"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro"; import MysteryEncounterIntroVisuals from "#app/field/mystery-encounter-intro"; @@ -717,7 +717,7 @@ export class MysteryEncounterBuilder implements Partial { withAnimations( ...encounterAnimations: EncounterAnim[] ): this & Required> { - const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [encounterAnimations]; + const animations = coerceArray(encounterAnimations); return Object.assign(this, { encounterAnimations: animations }); } @@ -729,7 +729,7 @@ export class MysteryEncounterBuilder implements Partial { withDisallowedGameModes( ...disallowedGameModes: GameModes[] ): this & Required> { - const gameModes = Array.isArray(disallowedGameModes) ? disallowedGameModes : [disallowedGameModes]; + const gameModes = coerceArray(disallowedGameModes); return Object.assign(this, { disallowedGameModes: gameModes }); } @@ -741,7 +741,7 @@ export class MysteryEncounterBuilder implements Partial { withDisallowedChallenges( ...disallowedChallenges: Challenges[] ): this & Required> { - const challenges = Array.isArray(disallowedChallenges) ? disallowedChallenges : [disallowedChallenges]; + const challenges = coerceArray(disallowedChallenges); return Object.assign(this, { disallowedChallenges: challenges }); } diff --git a/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts b/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts index c0e45d5401c..0123ea7d6ba 100644 --- a/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts +++ b/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts @@ -1,7 +1,7 @@ import type { MoveId } from "#enums/move-id"; import type { PlayerPokemon } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; -import { isNullOrUndefined } from "#app/utils/common"; +import { coerceArray, isNullOrUndefined } from "#app/utils/common"; import { EncounterPokemonRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { globalScene } from "#app/global-scene"; @@ -29,7 +29,7 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement { constructor(requiredMoves: MoveId | MoveId[], options: CanLearnMoveRequirementOptions = {}) { super(); - this.requiredMoves = Array.isArray(requiredMoves) ? requiredMoves : [requiredMoves]; + this.requiredMoves = coerceArray(requiredMoves); this.excludeLevelMoves = options.excludeLevelMoves ?? false; this.excludeTmMoves = options.excludeTmMoves ?? false; diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 599edb11628..e2b92230985 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -25,7 +25,7 @@ import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-optio import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler"; import { PartyUiMode } from "#app/ui/party-ui-handler"; import { UiMode } from "#enums/ui-mode"; -import { isNullOrUndefined, randSeedInt, randomString, randSeedItem } from "#app/utils/common"; +import { isNullOrUndefined, randSeedInt, randomString, randSeedItem, coerceArray } from "#app/utils/common"; import type { BattlerTagType } from "#enums/battler-tag-type"; import { BiomeId } from "#enums/biome-id"; import type { TrainerType } from "#enums/trainer-type"; @@ -449,7 +449,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): * @param moves */ export function loadCustomMovesForEncounter(moves: MoveId | MoveId[]) { - moves = Array.isArray(moves) ? moves : [moves]; + moves = coerceArray(moves); return Promise.all(moves.map(move => initMoveAnim(move))).then(() => loadMoveAnimAssets(moves)); } @@ -792,7 +792,7 @@ export function setEncounterRewards( * @param useWaveIndex - set to false when directly passing the the full exp value instead of baseExpValue */ export function setEncounterExp(participantId: number | number[], baseExpValue: number, useWaveIndex = true) { - const participantIds = Array.isArray(participantId) ? participantId : [participantId]; + const participantIds = coerceArray(participantId); globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => { globalScene.phaseManager.unshiftNew("PartyExpPhase", baseExpValue, useWaveIndex, new Set(participantIds)); diff --git a/src/data/pokemon-forms/form-change-triggers.ts b/src/data/pokemon-forms/form-change-triggers.ts index eb2c0a557c2..3726781d9e3 100644 --- a/src/data/pokemon-forms/form-change-triggers.ts +++ b/src/data/pokemon-forms/form-change-triggers.ts @@ -1,5 +1,5 @@ import i18next from "i18next"; -import type { Constructor } from "#app/utils/common"; +import { coerceArray, type Constructor } from "#app/utils/common"; import type { TimeOfDay } from "#enums/time-of-day"; import type Pokemon from "#app/field/pokemon"; import type { SpeciesFormChange } from "#app/data/pokemon-forms"; @@ -125,10 +125,7 @@ export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigg constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) { super(); - if (!Array.isArray(statusEffects)) { - statusEffects = [statusEffects]; - } - this.statusEffects = statusEffects; + this.statusEffects = coerceArray(statusEffects); this.invert = invert; // this.description = i18next.t("pokemonEvolutions:Forms.statusEffect"); } diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 8c065666202..063dddafee8 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -1,7 +1,14 @@ import { globalScene } from "#app/global-scene"; import { modifierTypes } from "../data-lists"; import { PokemonMove } from "../moves/pokemon-move"; -import { toReadableString, isNullOrUndefined, randSeedItem, randSeedInt, randSeedIntRange } from "#app/utils/common"; +import { + toReadableString, + isNullOrUndefined, + randSeedItem, + randSeedInt, + coerceArray, + randSeedIntRange, +} from "#app/utils/common"; import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { tmSpecies } from "#app/data/balance/tms"; @@ -554,10 +561,7 @@ export class TrainerConfig { this.speciesPools = evilAdminTrainerPools[poolName]; signatureSpecies.forEach((speciesPool, s) => { - if (!Array.isArray(speciesPool)) { - speciesPool = [speciesPool]; - } - this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool)); + this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool))); }); const nameForCall = this.name.toLowerCase().replace(/\s/g, "_"); @@ -620,10 +624,7 @@ export class TrainerConfig { this.setPartyTemplates(trainerPartyTemplates.RIVAL_5); } signatureSpecies.forEach((speciesPool, s) => { - if (!Array.isArray(speciesPool)) { - speciesPool = [speciesPool]; - } - this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool)); + this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool))); }); if (!isNullOrUndefined(specialtyType)) { this.setSpeciesFilter(p => p.isOfType(specialtyType)); @@ -668,12 +669,8 @@ export class TrainerConfig { // Set up party members with their corresponding species. signatureSpecies.forEach((speciesPool, s) => { - // Ensure speciesPool is an array. - if (!Array.isArray(speciesPool)) { - speciesPool = [speciesPool]; - } // Set a function to get a random party member from the species pool. - this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool)); + this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool))); }); // If specialty type is provided, set species filter and specialty type. @@ -729,12 +726,8 @@ export class TrainerConfig { // Set up party members with their corresponding species. signatureSpecies.forEach((speciesPool, s) => { - // Ensure speciesPool is an array. - if (!Array.isArray(speciesPool)) { - speciesPool = [speciesPool]; - } // Set a function to get a random party member from the species pool. - this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool)); + this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool))); }); // Set species filter and specialty type if provided, otherwise filter by base total. diff --git a/src/field/pokemon-sprite-sparkle-handler.ts b/src/field/pokemon-sprite-sparkle-handler.ts index cceb0bd7717..bda9414bb92 100644 --- a/src/field/pokemon-sprite-sparkle-handler.ts +++ b/src/field/pokemon-sprite-sparkle-handler.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; import Pokemon from "./pokemon"; -import { fixedInt, randInt } from "#app/utils/common"; +import { fixedInt, coerceArray, randInt } from "#app/utils/common"; export default class PokemonSpriteSparkleHandler { private sprites: Set; @@ -57,9 +57,7 @@ export default class PokemonSpriteSparkleHandler { } add(sprites: Phaser.GameObjects.Sprite | Phaser.GameObjects.Sprite[]): void { - if (!Array.isArray(sprites)) { - sprites = [sprites]; - } + sprites = coerceArray(sprites); for (const s of sprites) { if (this.sprites.has(s)) { continue; @@ -69,9 +67,7 @@ export default class PokemonSpriteSparkleHandler { } remove(sprites: Phaser.GameObjects.Sprite | Phaser.GameObjects.Sprite[]): void { - if (!Array.isArray(sprites)) { - sprites = [sprites]; - } + sprites = coerceArray(sprites); for (const s of sprites) { this.sprites.delete(s); } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 964d66d352e..834c65437af 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -41,6 +41,7 @@ import { type nil, type Constructor, randSeedIntRange, + coerceArray, } from "#app/utils/common"; import type { TypeDamageMultiplier } from "#app/data/type"; import { getTypeDamageMultiplier, getTypeRgb } from "#app/data/type"; @@ -1774,9 +1775,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let overrideArray: MoveId | Array = this.isPlayer() ? Overrides.MOVESET_OVERRIDE : Overrides.OPP_MOVESET_OVERRIDE; - if (!Array.isArray(overrideArray)) { - overrideArray = [overrideArray]; - } + overrideArray = coerceArray(overrideArray); if (overrideArray.length > 0) { if (!this.isPlayer()) { this.moveset = []; diff --git a/src/phase-manager.ts b/src/phase-manager.ts index b4fefe3f2d6..8c22a45758c 100644 --- a/src/phase-manager.ts +++ b/src/phase-manager.ts @@ -12,7 +12,7 @@ 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 type { Constructor } from "#app/utils/common"; +import { coerceArray, type Constructor } from "#app/utils/common"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import type { DynamicPhaseType } from "#enums/dynamic-phase-type"; import { EggHatchPhase } from "#app/phases/egg-hatch-phase"; @@ -438,9 +438,7 @@ export class PhaseManager { * @returns boolean if a targetPhase was found and added */ prependToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean { - if (!Array.isArray(phase)) { - phase = [phase]; - } + phase = coerceArray(phase); const target = PHASES[targetPhase]; const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target); @@ -460,9 +458,7 @@ export class PhaseManager { * @returns `true` if a `targetPhase` was found to append to */ appendToPhase(phase: Phase | Phase[], targetPhase: PhaseString, condition?: (p: Phase) => boolean): boolean { - if (!Array.isArray(phase)) { - phase = [phase]; - } + phase = coerceArray(phase); const target = PHASES[targetPhase]; const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target && (!condition || condition(ph))); diff --git a/src/plugins/cache-busted-loader-plugin.ts b/src/plugins/cache-busted-loader-plugin.ts index e5b1abb5903..4ae9b352ae3 100644 --- a/src/plugins/cache-busted-loader-plugin.ts +++ b/src/plugins/cache-busted-loader-plugin.ts @@ -1,10 +1,8 @@ +import { coerceArray } from "#app/utils/common"; + let manifest: object; export default class CacheBustedLoaderPlugin extends Phaser.Loader.LoaderPlugin { - constructor(scene: Phaser.Scene) { - super(scene); - } - get manifest() { return manifest; } @@ -14,9 +12,7 @@ export default class CacheBustedLoaderPlugin extends Phaser.Loader.LoaderPlugin } addFile(file): void { - if (!Array.isArray(file)) { - file = [file]; - } + file = coerceArray(file); file.forEach(item => { if (manifest) { diff --git a/src/plugins/vite/vite-minify-json-plugin.ts b/src/plugins/vite/vite-minify-json-plugin.ts index f14fdf7042d..38f299eea50 100644 --- a/src/plugins/vite/vite-minify-json-plugin.ts +++ b/src/plugins/vite/vite-minify-json-plugin.ts @@ -41,9 +41,9 @@ export function minifyJsonPlugin(basePath: string | string[], recursive?: boolea }, async closeBundle() { console.log("Minifying JSON files..."); - const basePathes = Array.isArray(basePath) ? basePath : [basePath]; + const basePaths = Array.isArray(basePath) ? basePath : [basePath]; - basePathes.forEach(basePath => { + basePaths.forEach(basePath => { const baseDir = path.resolve(buildDir, basePath); if (fs.existsSync(baseDir)) { applyToDir(baseDir, recursive); diff --git a/src/scene-base.ts b/src/scene-base.ts index 430a9bc8aac..ccea373fca0 100644 --- a/src/scene-base.ts +++ b/src/scene-base.ts @@ -1,3 +1,5 @@ +import { coerceArray } from "#app/utils/common"; + export const legacyCompatibleImages: string[] = []; export class SceneBase extends Phaser.Scene { @@ -88,9 +90,7 @@ export class SceneBase extends Phaser.Scene { } else { folder += "/"; } - if (!Array.isArray(filenames)) { - filenames = [filenames]; - } + filenames = coerceArray(filenames); for (const f of filenames as string[]) { this.load.audio(folder + key, this.getCachedUrl(`audio/${folder}${f}`)); } diff --git a/src/ui/pokemon-icon-anim-handler.ts b/src/ui/pokemon-icon-anim-handler.ts index 253ccbe3623..8a206167a94 100644 --- a/src/ui/pokemon-icon-anim-handler.ts +++ b/src/ui/pokemon-icon-anim-handler.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { fixedInt } from "#app/utils/common"; +import { fixedInt, coerceArray } from "#app/utils/common"; export enum PokemonIconAnimMode { NONE, @@ -49,9 +49,7 @@ export default class PokemonIconAnimHandler { } addOrUpdate(icons: PokemonIcon | PokemonIcon[], mode: PokemonIconAnimMode): void { - if (!Array.isArray(icons)) { - icons = [icons]; - } + icons = coerceArray(icons); for (const i of icons) { if (this.icons.has(i) && this.icons.get(i) === mode) { continue; @@ -66,9 +64,7 @@ export default class PokemonIconAnimHandler { } remove(icons: PokemonIcon | PokemonIcon[]): void { - if (!Array.isArray(icons)) { - icons = [icons]; - } + icons = coerceArray(icons); for (const i of icons) { if (this.toggled) { const icon = this.icons.get(i); diff --git a/src/utils/common.ts b/src/utils/common.ts index 56fa3b5c698..c8b37c4e3fd 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -611,3 +611,12 @@ export function getShinyDescriptor(variant: Variant): string { return i18next.t("common:commonShiny"); } } + +/** + * If the input isn't already an array, turns it into one. + * @returns An array with the same type as the type of the input + */ +export function coerceArray(input: T): T extends any[] ? T : [T]; +export function coerceArray(input: T): T | [T] { + return Array.isArray(input) ? input : [input]; +} diff --git a/test/testUtils/helpers/moveHelper.ts b/test/testUtils/helpers/moveHelper.ts index 1b799e12da7..0f87fa9a4c1 100644 --- a/test/testUtils/helpers/moveHelper.ts +++ b/test/testUtils/helpers/moveHelper.ts @@ -12,6 +12,7 @@ import { UiMode } from "#enums/ui-mode"; import { getMovePosition } from "#test/testUtils/gameManagerUtils"; import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper"; import { vi } from "vitest"; +import { coerceArray } from "#app/utils/common"; /** * Helper to handle a Pokemon's move @@ -157,9 +158,7 @@ export class MoveHelper extends GameManagerHelper { * @param moveset - The {@linkcode MoveId} (single or array) to change the Pokemon's moveset to. */ public changeMoveset(pokemon: Pokemon, moveset: MoveId | MoveId[]): void { - if (!Array.isArray(moveset)) { - moveset = [moveset]; - } + moveset = coerceArray(moveset); pokemon.moveset = []; moveset.forEach(move => { pokemon.moveset.push(new PokemonMove(move)); diff --git a/test/testUtils/helpers/overridesHelper.ts b/test/testUtils/helpers/overridesHelper.ts index c01acbe0f2e..9869c7450e2 100644 --- a/test/testUtils/helpers/overridesHelper.ts +++ b/test/testUtils/helpers/overridesHelper.ts @@ -14,7 +14,7 @@ import { StatusEffect } from "#enums/status-effect"; import type { WeatherType } from "#enums/weather-type"; import { expect, vi } from "vitest"; import { GameManagerHelper } from "./gameManagerHelper"; -import { shiftCharCodes } from "#app/utils/common"; +import { coerceArray, shiftCharCodes } from "#app/utils/common"; import type { RandomTrainerOverride } from "#app/overrides"; import type { BattleType } from "#enums/battle-type"; @@ -202,9 +202,7 @@ export class OverridesHelper extends GameManagerHelper { */ public moveset(moveset: MoveId | MoveId[]): this { vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue(moveset); - if (!Array.isArray(moveset)) { - moveset = [moveset]; - } + moveset = coerceArray(moveset); const movesetStr = moveset.map(moveId => MoveId[moveId]).join(", "); this.log(`Player Pokemon moveset set to ${movesetStr} (=[${moveset.join(", ")}])!`); return this; @@ -382,9 +380,7 @@ export class OverridesHelper extends GameManagerHelper { */ public enemyMoveset(moveset: MoveId | MoveId[]): this { vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue(moveset); - if (!Array.isArray(moveset)) { - moveset = [moveset]; - } + moveset = coerceArray(moveset); const movesetStr = moveset.map(moveId => MoveId[moveId]).join(", "); this.log(`Enemy Pokemon moveset set to ${movesetStr} (=[${moveset.join(", ")}])!`); return this; diff --git a/test/testUtils/mocks/mocksContainer/mockContainer.ts b/test/testUtils/mocks/mocksContainer/mockContainer.ts index f1371643ce3..d31165bb847 100644 --- a/test/testUtils/mocks/mocksContainer/mockContainer.ts +++ b/test/testUtils/mocks/mocksContainer/mockContainer.ts @@ -1,3 +1,4 @@ +import { coerceArray } from "#app/utils/common"; import type MockTextureManager from "#test/testUtils/mocks/mockTextureManager"; import type { MockGameObject } from "../mockGameObject"; @@ -216,11 +217,7 @@ export default class MockContainer implements MockGameObject { } add(obj: MockGameObject | MockGameObject[]): this { - if (Array.isArray(obj)) { - this.list.push(...obj); - } else { - this.list.push(obj); - } + this.list.push(...coerceArray(obj)); return this; } @@ -232,18 +229,12 @@ export default class MockContainer implements MockGameObject { addAt(obj: MockGameObject | MockGameObject[], index = 0): this { // Adds a Game Object to this Container at the given index. - if (!Array.isArray(obj)) { - obj = [obj]; - } - this.list.splice(index, 0, ...obj); + this.list.splice(index, 0, ...coerceArray(obj)); return this; } remove(obj: MockGameObject | MockGameObject[], destroyChild = false): this { - if (!Array.isArray(obj)) { - obj = [obj]; - } - for (const item of obj) { + for (const item of coerceArray(obj)) { const index = this.list.indexOf(item); if (index !== -1) { this.list.splice(index, 1); diff --git a/test/testUtils/mocks/mocksContainer/mockRectangle.ts b/test/testUtils/mocks/mocksContainer/mockRectangle.ts index 7f54a0e255f..a8eeb370115 100644 --- a/test/testUtils/mocks/mocksContainer/mockRectangle.ts +++ b/test/testUtils/mocks/mocksContainer/mockRectangle.ts @@ -1,3 +1,4 @@ +import { coerceArray } from "#app/utils/common"; import type { MockGameObject } from "../mockGameObject"; export default class MockRectangle implements MockGameObject { @@ -50,11 +51,7 @@ export default class MockRectangle implements MockGameObject { add(obj: MockGameObject | MockGameObject[]): this { // Adds a child to this Game Object. - if (Array.isArray(obj)) { - this.list.push(...obj); - } else { - this.list.push(obj); - } + this.list.push(...coerceArray(obj)); return this; } diff --git a/test/testUtils/mocks/mocksContainer/mockSprite.ts b/test/testUtils/mocks/mocksContainer/mockSprite.ts index df36b3a29fd..ed1f1df6609 100644 --- a/test/testUtils/mocks/mocksContainer/mockSprite.ts +++ b/test/testUtils/mocks/mocksContainer/mockSprite.ts @@ -1,6 +1,8 @@ +import { coerceArray } from "#app/utils/common"; import Phaser from "phaser"; import type { MockGameObject } from "../mockGameObject"; -import Frame = Phaser.Textures.Frame; + +type Frame = Phaser.Textures.Frame; export default class MockSprite implements MockGameObject { private phaserSprite; @@ -204,11 +206,7 @@ export default class MockSprite implements MockGameObject { add(obj: MockGameObject | MockGameObject[]): this { // Adds a child to this Game Object. - if (Array.isArray(obj)) { - this.list.push(...obj); - } else { - this.list.push(obj); - } + this.list.push(...coerceArray(obj)); return this; } From 718d6f61cf435e69cb6dd9d070d4a8d0d69db089 Mon Sep 17 00:00:00 2001 From: damocleas Date: Fri, 13 Jun 2025 21:10:38 -0400 Subject: [PATCH 37/51] [Bug] Fix Fog interactions with Morning Sun/Synthesis/Moonlight and Solar Beam/Blade (#5987) Fix Weather based Healing Moves and Solar Beam/Blade not interacting with Fog Update move.ts --- src/data/moves/move.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index e713020cf9c..b46f109db9e 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -2156,6 +2156,7 @@ export class PlantHealAttr extends WeatherHealAttr { case WeatherType.SANDSTORM: case WeatherType.HAIL: case WeatherType.SNOW: + case WeatherType.FOG: case WeatherType.HEAVY_RAIN: return 0.25; default: @@ -4157,6 +4158,7 @@ export class AntiSunlightPowerDecreaseAttr extends VariablePowerAttr { case WeatherType.SANDSTORM: case WeatherType.HAIL: case WeatherType.SNOW: + case WeatherType.FOG: case WeatherType.HEAVY_RAIN: power.value *= 0.5; return true; From d91a6ee11ee2436cc477b1e47616291ef57af70f Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Fri, 13 Jun 2025 21:38:54 -0400 Subject: [PATCH 38/51] [Github] Update `pull_request_template.md` with more labels https://github.com/pagefaultgames/pokerogue/pull/5974 * Update pull_request_template.md with more labels More better * Fix create test script name post refactor Forgot to change it earlier --- .github/pull_request_template.md | 37 +++++++++++++++++--------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a25a2f807f3..c7d8b1e4d9c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,25 +2,28 @@ + ## What are the changes the user will see? @@ -66,11 +69,11 @@ Do the reviewers need to do something special in order to test your changes? - [ ] Have I provided a clear explanation of the changes? - [ ] Have I tested the changes manually? - [ ] Are all unit tests still passing? (`npm run test:silent`) - - [ ] Have I created new automated tests (`npm run create-test`) or updated existing tests related to the PR's changes? + - [ ] Have I created new automated tests (`npm run test:create`) or updated existing tests related to the PR's changes? - [ ] Have I provided screenshots/videos of the changes (if applicable)? - [ ] Have I made sure that any UI change works for both UI themes (default and legacy)? Are there any localization additions or changes? If so: - [ ] Has a locales PR been created on the [locales](https://github.com/pagefaultgames/pokerogue-locales) repo? - [ ] If so, please leave a link to it here: -- [ ] Has the translation team been contacted for proofreading/translation? \ No newline at end of file +- [ ] Has the translation team been contacted for proofreading/translation? From 9a525ac8fd89743e9c8d4c8f4c96d369af5a9763 Mon Sep 17 00:00:00 2001 From: fabske0 <192151969+fabske0@users.noreply.github.com> Date: Sat, 14 Jun 2025 05:19:05 +0200 Subject: [PATCH 39/51] [UI/UX] Position setting Icons dynamically (#5969) * position setting icons dynamically * add comment for potential overlap --- src/ui/settings/abstract-control-settings-ui-handler.ts | 9 +++++++-- src/ui/settings/abstract-settings-ui-handler.ts | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ui/settings/abstract-control-settings-ui-handler.ts b/src/ui/settings/abstract-control-settings-ui-handler.ts index 495a0f68540..e3631c062df 100644 --- a/src/ui/settings/abstract-control-settings-ui-handler.ts +++ b/src/ui/settings/abstract-control-settings-ui-handler.ts @@ -126,6 +126,11 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler ); this.actionsBg.setOrigin(0, 0); + /* + * If there isn't enough space to fit all the icons and texts, there will be an overlap + * This currently doesn't happen, but it's something to keep in mind. + */ + const iconAction = globalScene.add.sprite(0, 0, "keyboard"); iconAction.setOrigin(0, -0.1); iconAction.setPositionRelative(this.actionsBg, this.navigationContainer.width - 32, 4); @@ -137,7 +142,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler const iconCancel = globalScene.add.sprite(0, 0, "keyboard"); iconCancel.setOrigin(0, -0.1); - iconCancel.setPositionRelative(this.actionsBg, this.navigationContainer.width - 100, 4); + iconCancel.setPositionRelative(this.actionsBg, actionText.x - 28, 4); this.navigationIcons["BUTTON_CANCEL"] = iconCancel; const cancelText = addTextObject(0, 0, i18next.t("settings:back"), TextStyle.SETTINGS_LABEL); @@ -146,7 +151,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler const iconReset = globalScene.add.sprite(0, 0, "keyboard"); iconReset.setOrigin(0, -0.1); - iconReset.setPositionRelative(this.actionsBg, this.navigationContainer.width - 180, 4); + iconReset.setPositionRelative(this.actionsBg, cancelText.x - 28, 4); this.navigationIcons["BUTTON_HOME"] = iconReset; const resetText = addTextObject(0, 0, i18next.t("settings:reset"), TextStyle.SETTINGS_LABEL); diff --git a/src/ui/settings/abstract-settings-ui-handler.ts b/src/ui/settings/abstract-settings-ui-handler.ts index 1fb4b6d34dc..a621b056e28 100644 --- a/src/ui/settings/abstract-settings-ui-handler.ts +++ b/src/ui/settings/abstract-settings-ui-handler.ts @@ -94,7 +94,7 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler { const iconCancel = globalScene.add.sprite(0, 0, "keyboard"); iconCancel.setOrigin(0, -0.1); - iconCancel.setPositionRelative(actionsBg, this.navigationContainer.width - 100, 4); + iconCancel.setPositionRelative(actionsBg, actionText.x - 28, 4); this.navigationIcons["BUTTON_CANCEL"] = iconCancel; const cancelText = addTextObject(0, 0, i18next.t("settings:back"), TextStyle.SETTINGS_LABEL); From a41de39d4f0d7a6bc287a10de5837a219e53c4d7 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sat, 14 Jun 2025 03:59:34 -0400 Subject: [PATCH 40/51] [Misc] Run biome on all files; update some rules to be errors now https://github.com/pagefaultgames/pokerogue/pull/5962/ * Fixed lint issue; ran biome on entire repository * Fixed biome.jsonc * Trimmed trailing whitespace... again... * Fixed PR template md * Fixed package json * Fixed void return issues + ran biome again * ran biome --- .dependency-cruiser.cjs | 10 +++---- .github/CODEOWNERS | 2 +- .github/workflows/deploy.yml | 2 +- .github/workflows/quality.yml | 2 +- biome.jsonc | 6 ++-- global.d.ts | 2 +- package.json | 2 +- src/@types/modifier-types.ts | 2 +- src/data/battler-tags.ts | 20 ------------- .../encounters/fight-or-flight-encounter.ts | 5 +--- .../global-trade-system-encounter.ts | 5 +--- src/field/arena.ts | 3 +- src/field/pokemon.ts | 9 ++++-- src/overrides.ts | 4 +-- src/phases/add-enemy-buff-modifier-phase.ts | 5 +--- src/phases/command-phase.ts | 9 ++++-- src/phases/move-phase.ts | 6 ++-- src/phases/pokemon-anim-phase.ts | 30 ++++++++++++------- src/phases/pokemon-transform-phase.ts | 3 +- src/phases/quiet-form-change-phase.ts | 3 +- src/pipelines/glsl/fieldSpriteFragShader.frag | 14 ++++----- src/pipelines/glsl/spriteFragShader.frag | 12 ++++---- src/plugins/i18n.ts | 19 +++++++++++- src/system/settings/settings-gamepad.ts | 5 +++- src/system/settings/settings.ts | 6 ++-- src/ui/abstact-option-select-ui-handler.ts | 4 --- src/ui/admin-ui-handler.ts | 3 +- src/ui/daily-run-scoreboard.ts | 3 +- src/ui/egg-gacha-ui-handler.ts | 6 ++-- src/ui/menu-ui-handler.ts | 3 +- src/ui/party-ui-handler.ts | 6 ++-- src/ui/run-info-ui-handler.ts | 9 ++++-- .../settings/abstract-settings-ui-handler.ts | 3 +- .../settings/move-touch-controls-handler.ts | 2 +- src/ui/starter-select-ui-handler.ts | 3 +- src/ui/test-dialogue-ui-handler.ts | 4 --- 36 files changed, 124 insertions(+), 108 deletions(-) diff --git a/.dependency-cruiser.cjs b/.dependency-cruiser.cjs index 40a9785aeaf..eccec089f1e 100644 --- a/.dependency-cruiser.cjs +++ b/.dependency-cruiser.cjs @@ -218,7 +218,7 @@ module.exports = { module systems it knows of. It's the default because it's the safe option It might come at a performance penalty, though. moduleSystems: ['amd', 'cjs', 'es6', 'tsd'] - + As in practice only commonjs ('cjs') and ecmascript modules ('es6') are widely used, you can limit the moduleSystems to those. */ @@ -226,7 +226,7 @@ module.exports = { // moduleSystems: ['cjs', 'es6'], /* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/main/' - to open it on your online repo or `vscode://file/${process.cwd()}/` to + to open it on your online repo or `vscode://file/${process.cwd()}/` to open it in visual studio code), */ // prefix: `vscode://file/${process.cwd()}/`, @@ -271,7 +271,7 @@ module.exports = { to './webpack.conf.js'. The (optional) `env` and `arguments` attributes contain the parameters - to be passed if your webpack config is a function and takes them (see + to be passed if your webpack config is a function and takes them (see webpack documentation for details) */ // webpackConfig: { @@ -322,8 +322,8 @@ module.exports = { A list of alias fields in package.jsons See [this specification](https://github.com/defunctzombie/package-browser-field-spec) and the webpack [resolve.alias](https://webpack.js.org/configuration/resolve/#resolvealiasfields) - documentation - + documentation + Defaults to an empty array (= don't use alias fields). */ // aliasFields: ["browser"], diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 79ab1bdc38a..979b94f84d6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,7 +8,7 @@ # Art Team /public/**/*.png @pagefaultgames/art-team -/public/**/*.json @pagefaultgames/art-team +/public/**/*.json @pagefaultgames/art-team /public/images @pagefaultgames/art-team /public/battle-anims @pagefaultgames/art-team diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 00190e477d5..a233a2fccab 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -35,7 +35,7 @@ jobs: ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts - name: Deploy build on server if: github.event_name == 'push' && github.ref_name == 'main' - run: | + run: | rsync --del --no-times --checksum -vrm dist/* ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:${{ secrets.DESTINATION_DIR }} ssh -t ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "~/prmanifest --inpath ${{ secrets.DESTINATION_DIR }} --outpath ${{ secrets.DESTINATION_DIR }}/manifest.json" - name: Purge Cloudflare Cache diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index d9592662998..36233248472 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -33,7 +33,7 @@ jobs: - name: Install Node.js dependencies # Step to install Node.js dependencies run: npm ci # Use 'npm ci' to install dependencies - + - name: eslint # Step to run linters run: npm run eslint-ci diff --git a/biome.jsonc b/biome.jsonc index 82ce7c308dc..f427debb198 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -47,8 +47,8 @@ "correctness": { "noUndeclaredVariables": "off", "noUnusedVariables": "error", - "noSwitchDeclarations": "warn", // TODO: refactor and make this an error - "noVoidTypeReturn": "warn", // TODO: Refactor and make this an error + "noSwitchDeclarations": "error", + "noVoidTypeReturn": "error", "noUnusedImports": "error" }, "style": { @@ -85,7 +85,7 @@ "useLiteralKeys": "off", "noForEach": "off", // Foreach vs for of is not that simple. "noUselessSwitchCase": "off", // Explicit > Implicit - "noUselessConstructor": "warn", // TODO: Refactor and make this an error + "noUselessConstructor": "error", "noBannedTypes": "warn" // TODO: Refactor and make this an error }, "nursery": { diff --git a/global.d.ts b/global.d.ts index c896a4983e4..d2ed6438c0b 100644 --- a/global.d.ts +++ b/global.d.ts @@ -7,7 +7,7 @@ declare global { * Only used in testing. * Can technically be undefined/null but for ease of use we are going to assume it is always defined. * Used to load i18n files exclusively. - * + * * To set up your own server in a test see `game_data.test.ts` */ var server: SetupServerApi; diff --git a/package.json b/package.json index 36c3c2b919f..b927542788d 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "eslint": "eslint --fix .", "eslint-ci": "eslint .", "biome": "biome check --write --changed --no-errors-on-unmatched", - "biome-ci": "biome ci --diagnostic-level=error --reporter=github --changed --no-errors-on-unmatched", + "biome-ci": "biome ci --diagnostic-level=error --reporter=github --no-errors-on-unmatched", "docs": "typedoc", "depcruise": "depcruise src", "depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg", diff --git a/src/@types/modifier-types.ts b/src/@types/modifier-types.ts index 6c0136e655e..80b92c35622 100644 --- a/src/@types/modifier-types.ts +++ b/src/@types/modifier-types.ts @@ -29,4 +29,4 @@ export type ModifierString = keyof ModifierConstructorMap; export type ModifierPool = { [tier: string]: WeightedModifierType[]; -} \ No newline at end of file +}; diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 89d5a76159f..98cefb78bd5 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -125,16 +125,6 @@ export interface TerrainBattlerTag { * Players and enemies should not be allowed to select restricted moves. */ export abstract class MoveRestrictionBattlerTag extends BattlerTag { - constructor( - tagType: BattlerTagType, - lapseType: BattlerTagLapseType | BattlerTagLapseType[], - turnCount: number, - sourceMove?: MoveId, - sourceId?: number, - ) { - super(tagType, lapseType, turnCount, sourceMove, sourceId); - } - /** @override */ override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.PRE_MOVE) { @@ -1470,16 +1460,6 @@ export class WrapTag extends DamagingTrapTag { } export abstract class VortexTrapTag extends DamagingTrapTag { - constructor( - tagType: BattlerTagType, - commonAnim: CommonAnim, - turnCount: number, - sourceMove: MoveId, - sourceId: number, - ) { - super(tagType, commonAnim, turnCount, sourceMove, sourceId); - } - getTrapMessage(pokemon: Pokemon): string { return i18next.t("battlerTags:vortexOnTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index f925452e143..c53ff610c48 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -11,10 +11,7 @@ import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requir import type Pokemon from "#app/field/pokemon"; import { ModifierTier } from "#enums/modifier-tier"; import type { ModifierTypeOption } from "#app/modifier/modifier-type"; -import { - getPlayerModifierTypeOptions, - regenerateModifierPoolThresholds, -} from "#app/modifier/modifier-type"; +import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { ModifierPoolType } from "#enums/modifier-pool-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 1b188915de7..0393bcdaa62 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -7,10 +7,7 @@ import { TrainerSlot } from "#enums/trainer-slot"; import { ModifierTier } from "#enums/modifier-tier"; import { MusicPreference } from "#app/system/settings/settings"; import type { ModifierTypeOption } from "#app/modifier/modifier-type"; -import { - getPlayerModifierTypeOptions, - regenerateModifierPoolThresholds, -} from "#app/modifier/modifier-type"; +import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { ModifierPoolType } from "#enums/modifier-pool-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; diff --git a/src/field/arena.ts b/src/field/arena.ts index c77c9a5b3ce..22cb9e32863 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -262,7 +262,7 @@ export class Arena { return 5; } break; - case SpeciesId.LYCANROC: + case SpeciesId.LYCANROC: { const timeOfDay = this.getTimeOfDay(); switch (timeOfDay) { case TimeOfDay.DAY: @@ -274,6 +274,7 @@ export class Arena { return 1; } break; + } } return 0; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 834c65437af..0205b5bf4b2 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -4385,14 +4385,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // biome-ignore lint: there are a ton of issues.. faintCry(callback: Function): void { if (this.fusionSpecies && this.getSpeciesForm() !== this.getFusionSpeciesForm()) { - return this.fusionFaintCry(callback); + this.fusionFaintCry(callback); + return; } const key = this.species.getCryKey(this.formIndex); let rate = 0.85; const cry = globalScene.playSound(key, { rate: rate }) as AnySound; if (!cry || globalScene.fieldVolume === 0) { - return callback(); + callback(); + return; } const sprite = this.getSprite(); const tintSprite = this.getTintSprite(); @@ -4460,7 +4462,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { rate: rate, }) as AnySound; if (!cry || !fusionCry || globalScene.fieldVolume === 0) { - return callback(); + callback(); + return; } fusionCry.stop(); duration = Math.min(duration, fusionCry.totalDuration * 1000); diff --git a/src/overrides.ts b/src/overrides.ts index b5df1073c28..6086f75a58e 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -272,7 +272,7 @@ class DefaultOverrides { /** * Set all non-scripted waves to use the selected battle type. - * + * * Ignored if set to {@linkcode BattleType.TRAINER} and `DISABLE_STANDARD_TRAINERS_OVERRIDE` is `true`. */ readonly BATTLE_TYPE_OVERRIDE: Exclude | null = null; @@ -298,4 +298,4 @@ export type RandomTrainerOverride = { } /** The type of the {@linkcode DefaultOverrides} class */ -export type OverridesType = typeof DefaultOverrides; \ No newline at end of file +export type OverridesType = typeof DefaultOverrides; diff --git a/src/phases/add-enemy-buff-modifier-phase.ts b/src/phases/add-enemy-buff-modifier-phase.ts index 185464670d3..218a3a653ff 100644 --- a/src/phases/add-enemy-buff-modifier-phase.ts +++ b/src/phases/add-enemy-buff-modifier-phase.ts @@ -1,8 +1,5 @@ import { ModifierTier } from "#enums/modifier-tier"; -import { - regenerateModifierPoolThresholds, - getEnemyBuffModifierForWave, -} from "#app/modifier/modifier-type"; +import { regenerateModifierPoolThresholds, getEnemyBuffModifierForWave } from "#app/modifier/modifier-type"; import { ModifierPoolType } from "#enums/modifier-pool-type"; import { EnemyPersistentModifier } from "#app/modifier/modifier"; import { Phase } from "#app/phase"; diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index d7264b4aff2..e796f22921c 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -149,7 +149,7 @@ export class CommandPhase extends FieldPhase { switch (command) { case Command.TERA: - case Command.FIGHT: + case Command.FIGHT: { let useStruggle = false; const turnMove: TurnMove | undefined = args.length === 2 ? (args[1] as TurnMove) : undefined; if ( @@ -233,7 +233,8 @@ export class CommandPhase extends FieldPhase { ); } break; - case Command.BALL: + } + case Command.BALL: { const notInDex = globalScene .getEnemyField() @@ -337,8 +338,9 @@ export class CommandPhase extends FieldPhase { } } break; + } case Command.POKEMON: - case Command.RUN: + case Command.RUN: { const isSwitch = command === Command.POKEMON; const { currentBattle, arena } = globalScene; const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed; @@ -445,6 +447,7 @@ export class CommandPhase extends FieldPhase { } } break; + } } if (success) { diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index d72c7396f1f..a14327749f7 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -135,7 +135,8 @@ export class MovePhase extends BattlePhase { this.showMoveText(); this.showFailedText(); } - return this.end(); + this.end(); + return; } this.pokemon.turnData.acted = true; @@ -310,7 +311,8 @@ export class MovePhase extends BattlePhase { if (fail) { this.showMoveText(); this.showFailedText(); - return this.end(); + this.end(); + return; } } diff --git a/src/phases/pokemon-anim-phase.ts b/src/phases/pokemon-anim-phase.ts index b1a21446996..314bddb981d 100644 --- a/src/phases/pokemon-anim-phase.ts +++ b/src/phases/pokemon-anim-phase.ts @@ -53,7 +53,8 @@ export class PokemonAnimPhase extends BattlePhase { private doSubstituteAddAnim(): void { const substitute = this.pokemon.getTag(SubstituteTag); if (isNullOrUndefined(substitute)) { - return this.end(); + this.end(); + return; } const getSprite = () => { @@ -116,12 +117,14 @@ export class PokemonAnimPhase extends BattlePhase { private doSubstitutePreMoveAnim(): void { if (this.fieldAssets.length !== 1) { - return this.end(); + this.end(); + return; } const subSprite = this.fieldAssets[0]; if (subSprite === undefined) { - return this.end(); + this.end(); + return; } globalScene.tweens.add({ @@ -145,12 +148,14 @@ export class PokemonAnimPhase extends BattlePhase { private doSubstitutePostMoveAnim(): void { if (this.fieldAssets.length !== 1) { - return this.end(); + this.end(); + return; } const subSprite = this.fieldAssets[0]; if (subSprite === undefined) { - return this.end(); + this.end(); + return; } globalScene.tweens.add({ @@ -174,12 +179,14 @@ export class PokemonAnimPhase extends BattlePhase { private doSubstituteRemoveAnim(): void { if (this.fieldAssets.length !== 1) { - return this.end(); + this.end(); + return; } const subSprite = this.fieldAssets[0]; if (subSprite === undefined) { - return this.end(); + this.end(); + return; } const getSprite = () => { @@ -244,12 +251,14 @@ export class PokemonAnimPhase extends BattlePhase { private doCommanderApplyAnim(): void { if (!globalScene.currentBattle?.double) { - return this.end(); + this.end(); + return; } const dondozo = this.pokemon.getAlly(); if (dondozo?.species?.speciesId !== SpeciesId.DONDOZO) { - return this.end(); + this.end(); + return; } const tatsugiriX = this.pokemon.x + this.pokemon.getSprite().x; @@ -329,7 +338,8 @@ export class PokemonAnimPhase extends BattlePhase { const tatsugiri = this.pokemon.getAlly(); if (isNullOrUndefined(tatsugiri)) { console.warn("Aborting COMMANDER_REMOVE anim: Tatsugiri is undefined"); - return this.end(); + this.end(); + return; } const tatsuSprite = globalScene.addPokemonSprite( diff --git a/src/phases/pokemon-transform-phase.ts b/src/phases/pokemon-transform-phase.ts index ab0949c42b9..65fccb24d99 100644 --- a/src/phases/pokemon-transform-phase.ts +++ b/src/phases/pokemon-transform-phase.ts @@ -29,7 +29,8 @@ export class PokemonTransformPhase extends PokemonPhase { const target = globalScene.getField(true).find(p => p.getBattlerIndex() === this.targetIndex); if (!target) { - return this.end(); + this.end(); + return; } user.summonData.speciesForm = target.getSpeciesForm(); diff --git a/src/phases/quiet-form-change-phase.ts b/src/phases/quiet-form-change-phase.ts index e6a00c73756..41b691844bf 100644 --- a/src/phases/quiet-form-change-phase.ts +++ b/src/phases/quiet-form-change-phase.ts @@ -29,7 +29,8 @@ export class QuietFormChangePhase extends BattlePhase { super.start(); if (this.pokemon.formIndex === this.pokemon.species.forms.findIndex(f => f.formKey === this.formChange.formKey)) { - return this.end(); + this.end(); + return; } const preName = getPokemonNameWithAffix(this.pokemon); diff --git a/src/pipelines/glsl/fieldSpriteFragShader.frag b/src/pipelines/glsl/fieldSpriteFragShader.frag index e79dea86fe9..0eb95ece5e3 100644 --- a/src/pipelines/glsl/fieldSpriteFragShader.frag +++ b/src/pipelines/glsl/fieldSpriteFragShader.frag @@ -51,7 +51,7 @@ float hue2rgb(float f1, float f2, float hue) { vec3 rgb2hsl(vec3 color) { vec3 hsl; - + float fmin = min(min(color.r, color.g), color.b); float fmax = max(max(color.r, color.g), color.b); float delta = fmax - fmin; @@ -66,7 +66,7 @@ vec3 rgb2hsl(vec3 color) { hsl.y = delta / (fmax + fmin); else hsl.y = delta / (2.0 - fmax - fmin); - + float deltaR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta; float deltaG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta; float deltaB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta; @@ -89,24 +89,24 @@ vec3 rgb2hsl(vec3 color) { vec3 hsl2rgb(vec3 hsl) { vec3 rgb; - + if (hsl.y == 0.0) rgb = vec3(hsl.z); else { float f2; - + if (hsl.z < 0.5) f2 = hsl.z * (1.0 + hsl.y); else f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z); - + float f1 = 2.0 * hsl.z - f2; - + rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0)); rgb.g = hue2rgb(f1, f2, hsl.x); rgb.b = hue2rgb(f1, f2, hsl.x - (1.0/3.0)); } - + return rgb; } diff --git a/src/pipelines/glsl/spriteFragShader.frag b/src/pipelines/glsl/spriteFragShader.frag index 03f8c8c27bc..9328cc4d96d 100644 --- a/src/pipelines/glsl/spriteFragShader.frag +++ b/src/pipelines/glsl/spriteFragShader.frag @@ -83,7 +83,7 @@ vec3 rgb2hsl(vec3 color) { hsl.y = delta / (fmax + fmin); else hsl.y = delta / (2.0 - fmax - fmin); - + float deltaR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta; float deltaG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta; float deltaB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta; @@ -106,24 +106,24 @@ vec3 rgb2hsl(vec3 color) { vec3 hsl2rgb(vec3 hsl) { vec3 rgb; - + if (hsl.y == 0.0) rgb = vec3(hsl.z); else { float f2; - + if (hsl.z < 0.5) f2 = hsl.z * (1.0 + hsl.y); else f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z); - + float f1 = 2.0 * hsl.z - f2; - + rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0)); rgb.g = hue2rgb(f1, f2, hsl.x); rgb.b= hue2rgb(f1, f2, hsl.x - (1.0/3.0)); } - + return rgb; } diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index 515d9aec528..8ca9005096f 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -174,7 +174,24 @@ export async function initI18n(): Promise { "es-MX": ["es-ES", "en"], default: ["en"], }, - supportedLngs: ["en", "es-ES", "es-MX", "fr", "it", "de", "zh-CN", "zh-TW", "pt-BR", "ko", "ja", "ca", "da", "tr", "ro", "ru"], + supportedLngs: [ + "en", + "es-ES", + "es-MX", + "fr", + "it", + "de", + "zh-CN", + "zh-TW", + "pt-BR", + "ko", + "ja", + "ca", + "da", + "tr", + "ro", + "ru", + ], backend: { loadPath(lng: string, [ns]: string[]) { let fileName: string; diff --git a/src/system/settings/settings-gamepad.ts b/src/system/settings/settings-gamepad.ts index d39d5cf5a41..8a28e9fbf14 100644 --- a/src/system/settings/settings-gamepad.ts +++ b/src/system/settings/settings-gamepad.ts @@ -32,7 +32,10 @@ const pressAction = i18next.t("settings:pressActionToAssign"); export const settingGamepadOptions = { [SettingGamepad.Controller]: [i18next.t("settings:controllerDefault"), i18next.t("settings:controllerChange")], - [SettingGamepad.Gamepad_Support]: [i18next.t("settings:gamepadSupportAuto"), i18next.t("settings:gamepadSupportDisabled")], + [SettingGamepad.Gamepad_Support]: [ + i18next.t("settings:gamepadSupportAuto"), + i18next.t("settings:gamepadSupportDisabled"), + ], [SettingGamepad.Button_Up]: [`KEY ${Button.UP.toString()}`, pressAction], [SettingGamepad.Button_Down]: [`KEY ${Button.DOWN.toString()}`, pressAction], [SettingGamepad.Button_Left]: [`KEY ${Button.LEFT.toString()}`, pressAction], diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 69abc669870..ca5395a5af7 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -959,7 +959,7 @@ export function setSetting(setting: string, value: number): boolean { }, { label: "Türkçe (Needs Help)", - handler: () => changeLocaleHandler("tr") + handler: () => changeLocaleHandler("tr"), }, { label: "Русский (Needs Help)", @@ -967,11 +967,11 @@ export function setSetting(setting: string, value: number): boolean { }, { label: "Dansk (Needs Help)", - handler: () => changeLocaleHandler("da") + handler: () => changeLocaleHandler("da"), }, { label: "Română (Needs Help)", - handler: () => changeLocaleHandler("ro") + handler: () => changeLocaleHandler("ro"), }, { label: i18next.t("settings:back"), diff --git a/src/ui/abstact-option-select-ui-handler.ts b/src/ui/abstact-option-select-ui-handler.ts index 07609648a4e..e1263bc088c 100644 --- a/src/ui/abstact-option-select-ui-handler.ts +++ b/src/ui/abstact-option-select-ui-handler.ts @@ -56,10 +56,6 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { protected defaultTextStyle: TextStyle = TextStyle.WINDOW; protected textContent: string; - constructor(mode: UiMode | null) { - super(mode); - } - abstract getWindowWidth(): number; getWindowHeight(): number { diff --git a/src/ui/admin-ui-handler.ts b/src/ui/admin-ui-handler.ts index 67ae3118863..c8c8e43802b 100644 --- a/src/ui/admin-ui-handler.ts +++ b/src/ui/admin-ui-handler.ts @@ -69,7 +69,7 @@ export default class AdminUiHandler extends FormModalUiHandler { case AdminMode.SEARCH: inputFieldConfigs.push({ label: "Username" }); break; - case AdminMode.ADMIN: + case AdminMode.ADMIN: { const adminResult = this.adminResult ?? { username: "", discordId: "", @@ -90,6 +90,7 @@ export default class AdminUiHandler extends FormModalUiHandler { inputFieldConfigs.push({ label: "Last played", isReadOnly: true }); inputFieldConfigs.push({ label: "Registered", isReadOnly: true }); break; + } } return inputFieldConfigs; } diff --git a/src/ui/daily-run-scoreboard.ts b/src/ui/daily-run-scoreboard.ts index 076a782908b..e19882b09cc 100644 --- a/src/ui/daily-run-scoreboard.ts +++ b/src/ui/daily-run-scoreboard.ts @@ -169,12 +169,13 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container { entryContainer.add(scoreLabel); switch (this.category) { - case ScoreboardCategory.DAILY: + case ScoreboardCategory.DAILY: { const waveLabel = addTextObject(68, 0, wave, TextStyle.WINDOW, { fontSize: "54px", }); entryContainer.add(waveLabel); break; + } case ScoreboardCategory.WEEKLY: scoreLabel.x -= 16; break; diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index d6b1b630a05..1b1aed91203 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -131,7 +131,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { gachaInfoContainer.add(gachaUpLabel); switch (gachaType as GachaType) { - case GachaType.LEGENDARY: + case GachaType.LEGENDARY: { if (["de", "es-ES"].includes(currentLanguage)) { gachaUpLabel.setAlign("center"); gachaUpLabel.setY(0); @@ -152,6 +152,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { gachaInfoContainer.add(pokemonIcon); break; + } case GachaType.MOVE: if (["de", "es-ES", "fr", "pt-BR", "ru"].includes(currentLanguage)) { gachaUpLabel.setAlign("center"); @@ -623,11 +624,12 @@ export default class EggGachaUiHandler extends MessageUiHandler { updateGachaInfo(gachaType: GachaType): void { const infoContainer = this.gachaInfoContainers[gachaType]; switch (gachaType as GachaType) { - case GachaType.LEGENDARY: + case GachaType.LEGENDARY: { const species = getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(new Date().getTime())); const pokemonIcon = infoContainer.getAt(1) as Phaser.GameObjects.Sprite; pokemonIcon.setTexture(species.getIconAtlasKey(), species.getIconId(false)); break; + } } } diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index e68cc706aba..5ab1d4f9e96 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -686,7 +686,7 @@ export default class MenuUiHandler extends MessageUiHandler { error = true; } break; - case MenuOptions.LOG_OUT: + case MenuOptions.LOG_OUT: { success = true; const doLogout = () => { ui.setMode(UiMode.LOADING, { @@ -718,6 +718,7 @@ export default class MenuUiHandler extends MessageUiHandler { doLogout(); } break; + } } } else if (button === Button.CANCEL) { success = true; diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index a2dfe83e996..8e197c08ef3 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -1385,7 +1385,7 @@ export default class PartyUiHandler extends MessageUiHandler { case PartyOption.MOVE_1: case PartyOption.MOVE_2: case PartyOption.MOVE_3: - case PartyOption.MOVE_4: + case PartyOption.MOVE_4: { const move = pokemon.moveset[option - PartyOption.MOVE_1]; if (this.showMovePp) { const maxPP = move.getMovePp(); @@ -1395,7 +1395,8 @@ export default class PartyUiHandler extends MessageUiHandler { optionName = move.getName(); } break; - default: + } + default: { const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); if (formChangeItemModifiers && option >= PartyOption.FORM_CHANGE_ITEM) { const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM]; @@ -1410,6 +1411,7 @@ export default class PartyUiHandler extends MessageUiHandler { } } break; + } } } else if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { const learnableLevelMoves = pokemon.getLearnableLevelMoves(); diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index a4de2215fa4..78ab4a40407 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -567,7 +567,7 @@ export default class RunInfoUiHandler extends UiHandler { case GameModes.SPLICED_ENDLESS: modeText.appendText(`${i18next.t("gameMode:endlessSpliced")}`, false); break; - case GameModes.CHALLENGE: + case GameModes.CHALLENGE: { modeText.appendText(`${i18next.t("gameMode:challenge")}`, false); modeText.appendText(`${i18next.t("runHistory:challengeRules")}: `); modeText.setWrapMode(1); // wrap by word @@ -582,6 +582,7 @@ export default class RunInfoUiHandler extends UiHandler { } } break; + } case GameModes.ENDLESS: modeText.appendText(`${i18next.t("gameMode:endless")}`, false); break; @@ -687,7 +688,7 @@ export default class RunInfoUiHandler extends UiHandler { case Challenges.SINGLE_GENERATION: rules.push(i18next.t(`runHistory:challengeMonoGen${this.runInfo.challenges[i].value}`)); break; - case Challenges.SINGLE_TYPE: + case Challenges.SINGLE_TYPE: { const typeRule = PokemonType[this.runInfo.challenges[i].value - 1]; const typeTextColor = `[color=${TypeColor[typeRule]}]`; const typeShadowColor = `[shadow=${TypeShadow[typeRule]}]`; @@ -695,16 +696,18 @@ export default class RunInfoUiHandler extends UiHandler { typeTextColor + typeShadowColor + i18next.t(`pokemonInfo:Type.${typeRule}`)! + "[/color]" + "[/shadow]"; rules.push(typeText); break; + } case Challenges.INVERSE_BATTLE: rules.push(i18next.t("challenges:inverseBattle.shortName")); break; - default: + default: { const localisationKey = Challenges[this.runInfo.challenges[i].id] .split("_") .map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase())) .join(""); rules.push(i18next.t(`challenges:${localisationKey}.name`)); break; + } } } } diff --git a/src/ui/settings/abstract-settings-ui-handler.ts b/src/ui/settings/abstract-settings-ui-handler.ts index a621b056e28..6db9840a818 100644 --- a/src/ui/settings/abstract-settings-ui-handler.ts +++ b/src/ui/settings/abstract-settings-ui-handler.ts @@ -332,12 +332,13 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler { case Button.CYCLE_SHINY: success = this.navigationContainer.navigate(button); break; - case Button.ACTION: + case Button.ACTION: { const setting: Setting = this.settings[cursor]; if (setting?.activatable) { success = this.activateSetting(setting); } break; + } } } diff --git a/src/ui/settings/move-touch-controls-handler.ts b/src/ui/settings/move-touch-controls-handler.ts index 44377c8c2ab..f684d27f748 100644 --- a/src/ui/settings/move-touch-controls-handler.ts +++ b/src/ui/settings/move-touch-controls-handler.ts @@ -98,7 +98,7 @@ export default class MoveTouchControlsHandler {

-
+
${i18next.t("settings:orientation")} ${this.isLandscapeMode ? i18next.t("settings:landscape") : i18next.t("settings:portrait")} diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 88f881746bb..7c8b05b6f76 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1763,7 +1763,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } } else if (this.randomCursorObj.visible) { switch (button) { - case Button.ACTION: + case Button.ACTION: { if (this.starterSpecies.length >= 6) { error = true; break; @@ -1815,6 +1815,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } }); break; + } case Button.UP: this.randomCursorObj.setVisible(false); this.filterBarCursor = this.filterBar.numFilters - 1; diff --git a/src/ui/test-dialogue-ui-handler.ts b/src/ui/test-dialogue-ui-handler.ts index 9ecf1641e7b..b1e5047955a 100644 --- a/src/ui/test-dialogue-ui-handler.ts +++ b/src/ui/test-dialogue-ui-handler.ts @@ -10,10 +10,6 @@ import { UiMode } from "#enums/ui-mode"; export default class TestDialogueUiHandler extends FormModalUiHandler { keys: string[]; - constructor(mode) { - super(mode); - } - setup() { super.setup(); From fd1404706ac9b6163eb46d7aedee849e980f48a4 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 14 Jun 2025 14:01:29 +0200 Subject: [PATCH 41/51] [Bug] Remove empty `modifierPool` (#5988) --- src/modifier/modifier-type.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 2c848c69e18..cf373e6441a 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -2393,8 +2393,6 @@ export interface ModifierPool { [tier: string]: WeightedModifierType[]; } -const modifierPool: ModifierPool = {}; - let modifierPoolThresholds = {}; let ignoredPoolIndexes = {}; @@ -2859,7 +2857,7 @@ function getNewModifierTypeOption( } tier += upgradeCount; - while (tier && (!modifierPool.hasOwnProperty(tier) || !modifierPool[tier].length)) { + while (tier && (!pool.hasOwnProperty(tier) || !pool[tier].length)) { tier--; if (upgradeCount) { upgradeCount--; @@ -2870,7 +2868,7 @@ function getNewModifierTypeOption( if (tier < ModifierTier.MASTER && allowLuckUpgrades) { const partyLuckValue = getPartyLuckValue(party); const upgradeOdds = Math.floor(128 / ((partyLuckValue + 4) / 4)); - while (modifierPool.hasOwnProperty(tier + upgradeCount + 1) && modifierPool[tier + upgradeCount + 1].length) { + while (pool.hasOwnProperty(tier + upgradeCount + 1) && pool[tier + upgradeCount + 1].length) { if (randSeedInt(upgradeOdds) < 4) { upgradeCount++; } else { @@ -2920,6 +2918,7 @@ function getNewModifierTypeOption( } export function getDefaultModifierTypeForTier(tier: ModifierTier): ModifierType { + const modifierPool = getModifierPoolForType(ModifierPoolType.PLAYER); let modifierType: ModifierType | WeightedModifierType = modifierPool[tier || ModifierTier.COMMON][0]; if (modifierType instanceof WeightedModifierType) { modifierType = (modifierType as WeightedModifierType).modifierType; From 7cf51d48a78bf4037c23e61fa2b319d032a71815 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sat, 14 Jun 2025 05:06:46 -0700 Subject: [PATCH 42/51] [Test] Make sure items are removed from enemies in Last Respects test --- test/moves/last_respects.test.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/moves/last_respects.test.ts b/test/moves/last_respects.test.ts index b2eb4c6695b..08256d3ebfb 100644 --- a/test/moves/last_respects.test.ts +++ b/test/moves/last_respects.test.ts @@ -164,15 +164,13 @@ describe("Moves - Last Respects", () => { await game.toNextWave(); expect(game.scene.currentBattle.enemyFaints).toBe(0); + game.removeEnemyHeldItems(); + game.move.select(MoveId.LAST_RESPECTS); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("MoveEndPhase"); - const enemy = game.field.getEnemyPokemon(); - const player = game.field.getPlayerPokemon(); - const items = `Player items: ${player.getHeldItems()} | Enemy Items: ${enemy.getHeldItems()} |`; - - expect(move.calculateBattlePower, items).toHaveLastReturnedWith(50); + expect(move.calculateBattlePower).toHaveLastReturnedWith(50); }); it("should reset playerFaints count if we enter new trainer battle", async () => { From 73e0a2905b750853da6526d4bf6cc5fe499f1f98 Mon Sep 17 00:00:00 2001 From: damocleas Date: Sat, 14 Jun 2025 17:40:18 -0400 Subject: [PATCH 43/51] [Beta] Revert "[UI/UX] Default cursor to no when stop trying to teach move" (#5990) Revert "[UI/UX] Default cursor to no when stop trying to teach move" This reverts commit ba2158ec640da8913e7b8de39b79c0dd7392aa70. --- src/enums/confirm-ui-mode.ts | 13 ------------- src/phases/learn-move-phase.ts | 5 ----- src/ui/confirm-ui-handler.ts | 14 +------------- test/phases/learn-move-phase.test.ts | 4 ---- 4 files changed, 1 insertion(+), 35 deletions(-) delete mode 100644 src/enums/confirm-ui-mode.ts diff --git a/src/enums/confirm-ui-mode.ts b/src/enums/confirm-ui-mode.ts deleted file mode 100644 index 46bc42374cd..00000000000 --- a/src/enums/confirm-ui-mode.ts +++ /dev/null @@ -1,13 +0,0 @@ -// biome-ignore lint/correctness/noUnusedImports: Used in tsdoc -import type ConfirmUiHandler from "#app/ui/confirm-ui-handler"; - -/** - * Used by {@linkcode ConfirmUiHandler} to determine whether the cursor should start on Yes or No - */ -export const ConfirmUiMode = Object.freeze({ - /** Start cursor on Yes */ - DEFAULT_YES: 1, - /** Start cursor on No */ - DEFAULT_NO: 2 -}); -export type ConfirmUiMode = typeof ConfirmUiMode[keyof typeof ConfirmUiMode]; \ No newline at end of file diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts index e197f876d76..e24efa63b5a 100644 --- a/src/phases/learn-move-phase.ts +++ b/src/phases/learn-move-phase.ts @@ -12,7 +12,6 @@ import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase"; import type Pokemon from "#app/field/pokemon"; -import { ConfirmUiMode } from "#enums/confirm-ui-mode"; import { LearnMoveType } from "#enums/learn-move-type"; export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { @@ -164,10 +163,6 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { globalScene.ui.setMode(this.messageMode); this.replaceMoveCheck(move, pokemon); }, - false, - 0, - 0, - ConfirmUiMode.DEFAULT_NO, ); } diff --git a/src/ui/confirm-ui-handler.ts b/src/ui/confirm-ui-handler.ts index 37fd50ca671..7b5ca3d7e63 100644 --- a/src/ui/confirm-ui-handler.ts +++ b/src/ui/confirm-ui-handler.ts @@ -4,11 +4,8 @@ import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; import { Button } from "#enums/buttons"; import { globalScene } from "#app/global-scene"; -import { ConfirmUiMode } from "#enums/confirm-ui-mode"; export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler { - private confirmUiMode: ConfirmUiMode; - public static readonly windowWidth: number = 48; private switchCheck: boolean; @@ -108,16 +105,7 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler { this.optionSelectContainer.setPosition(globalScene.game.canvas.width / 6 - 1 + xOffset, -48 + yOffset); - this.confirmUiMode = args.length >= 6 ? (args[5] as ConfirmUiMode) : ConfirmUiMode.DEFAULT_YES; - - switch (this.confirmUiMode) { - case ConfirmUiMode.DEFAULT_YES: - this.setCursor(this.switchCheck ? this.switchCheckCursor : 0); - break; - case ConfirmUiMode.DEFAULT_NO: - this.setCursor(this.switchCheck ? this.switchCheckCursor : 1); - break; - } + this.setCursor(this.switchCheck ? this.switchCheckCursor : 0); return true; } diff --git a/test/phases/learn-move-phase.test.ts b/test/phases/learn-move-phase.test.ts index 05dbf71d1f4..88b8187069b 100644 --- a/test/phases/learn-move-phase.test.ts +++ b/test/phases/learn-move-phase.test.ts @@ -92,10 +92,6 @@ describe("Learn Move Phase", () => { game.onNextPrompt("LearnMovePhase", UiMode.CONFIRM, () => { game.scene.ui.processInput(Button.ACTION); }); - game.onNextPrompt("LearnMovePhase", UiMode.CONFIRM, () => { - game.scene.ui.setCursor(0); - game.scene.ui.processInput(Button.ACTION); - }); await game.phaseInterceptor.to(LearnMovePhase); const levelReq = bulbasaur.getLevelMoves(5)[0][0]; From 061c9872658011c1eefba3c10b2de33bdff8a0b0 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sun, 15 Jun 2025 03:40:41 -0400 Subject: [PATCH 44/51] [Test] Convert `game.override` calls into chained line where possible https://github.com/pagefaultgames/pokerogue/pull/5926 * Condensed all overrides into 1 line where possible I hope I got them all... * Fixed tests 0.5 * Cleaned up safeguard test to not use outdated code; fixed rest of errors * Fixed illusion test * Revert safeguart etst * Fixed battle tets * Fixed stuff * Fixed things2.0 * Fixed import issues * Revert changes outside of the tests directory * Revert changes outside of the tests directory --------- Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- test/abilities/battery.test.ts | 11 ++- test/abilities/costar.test.ts | 9 +- test/abilities/disguise.test.ts | 34 +++---- test/abilities/flash_fire.test.ts | 19 ++-- test/abilities/flower_gift.test.ts | 7 +- test/abilities/good_as_gold.test.ts | 16 ++- test/abilities/hustle.test.ts | 3 +- test/abilities/ice_face.test.ts | 25 ++--- test/abilities/illusion.test.ts | 15 ++- test/abilities/imposter.test.ts | 3 +- test/abilities/intimidate.test.ts | 3 +- test/abilities/intrepid_sword.test.ts | 9 +- test/abilities/libero.test.ts | 12 +-- test/abilities/magic_bounce.test.ts | 97 ++++++++----------- test/abilities/magic_guard.test.ts | 52 +++++----- test/abilities/mirror_armor.test.ts | 37 +++---- test/abilities/moxie.test.ts | 15 +-- test/abilities/normalize.test.ts | 10 +- test/abilities/parental_bond.test.ts | 39 +++----- test/abilities/perish_body.test.ts | 28 +++--- test/abilities/power_construct.test.ts | 6 +- test/abilities/power_spot.test.ts | 11 ++- test/abilities/protean.test.ts | 12 +-- test/abilities/quick_draw.test.ts | 19 ++-- test/abilities/sand_spit.test.ts | 17 ++-- test/abilities/schooling.test.ts | 3 +- test/abilities/screen_cleaner.test.ts | 4 +- test/abilities/seed_sower.test.ts | 17 ++-- test/abilities/shield_dust.test.ts | 13 +-- test/abilities/shields_down.test.ts | 38 ++++---- test/abilities/steely_spirit.test.ts | 11 ++- test/abilities/sturdy.test.ts | 17 ++-- test/abilities/sweet_veil.test.ts | 5 +- test/abilities/unseen_fist.test.ts | 3 +- test/abilities/volt_absorb.test.ts | 34 ++++--- test/abilities/wind_power.test.ts | 21 ++-- test/abilities/wonder_skin.test.ts | 13 +-- test/abilities/zen_mode.test.ts | 3 +- test/abilities/zero_to_hero.test.ts | 3 +- test/achievements/achievement.test.ts | 6 -- test/arena/weather_fog.test.ts | 14 +-- test/arena/weather_strong_winds.test.ts | 11 ++- test/battle/battle-order.test.ts | 11 ++- test/battle/battle.test.ts | 81 +++++++--------- test/escape-calculations.test.ts | 3 +- test/evolution.test.ts | 11 +-- test/field/pokemon.test.ts | 13 +-- test/items/exp_booster.test.ts | 4 +- test/moves/baneful_bunker.test.ts | 18 ++-- test/moves/baton_pass.test.ts | 13 +-- test/moves/beak_blast.test.ts | 3 +- test/moves/beat_up.test.ts | 17 ++-- test/moves/ceaseless_edge.test.ts | 20 ++-- test/moves/clangorous_soul.test.ts | 13 +-- test/moves/crafty_shield.test.ts | 21 ++-- test/moves/dragon_rage.test.ts | 30 +++--- test/moves/dynamax_cannon.test.ts | 10 +- test/moves/fillet_away.test.ts | 13 +-- test/moves/fissure.test.ts | 28 +++--- test/moves/flame_burst.test.ts | 17 ++-- test/moves/flower_shield.test.ts | 11 ++- test/moves/follow_me.test.ts | 20 ++-- test/moves/gastro_acid.test.ts | 17 ++-- test/moves/glaive_rush.test.ts | 21 ++-- test/moves/growth.test.ts | 11 ++- test/moves/hard_press.test.ts | 13 +-- test/moves/haze.test.ts | 19 ++-- test/moves/hyper_beam.test.ts | 16 +-- test/moves/lunar_blessing.test.ts | 15 ++- test/moves/magic_coat.test.ts | 44 ++++----- test/moves/make_it_rain.test.ts | 20 ++-- test/moves/mat_block.test.ts | 18 ++-- test/moves/parting_shot.test.ts | 11 ++- test/moves/powder.test.ts | 3 +- test/moves/protect.test.ts | 18 ++-- test/moves/purify.test.ts | 17 ++-- test/moves/quick_guard.test.ts | 24 ++--- test/moves/rage_powder.test.ts | 13 +-- test/moves/rollout.test.ts | 21 ++-- test/moves/roost.test.ts | 13 +-- test/moves/safeguard.test.ts | 39 ++++---- test/moves/spikes.test.ts | 4 +- test/moves/spit_up.test.ts | 17 ++-- test/moves/spotlight.test.ts | 15 +-- test/moves/stockpile.test.ts | 17 ++-- test/moves/substitute.test.ts | 30 ++---- test/moves/tackle.test.ts | 1 + test/moves/tail_whip.test.ts | 15 +-- test/moves/thousand_arrows.test.ts | 16 +-- test/moves/tidy_up.test.ts | 19 ++-- test/moves/toxic.test.ts | 3 +- test/moves/transform.test.ts | 3 +- test/moves/wide_guard.test.ts | 18 ++-- .../a-trainers-test-encounter.test.ts | 9 +- .../absolute-avarice-encounter.test.ts | 16 ++- ...an-offer-you-cant-refuse-encounter.test.ts | 3 +- .../bug-type-superfan-encounter.test.ts | 9 +- .../clowning-around-encounter.test.ts | 9 +- .../dancing-lessons-encounter.test.ts | 12 +-- .../encounters/delibirdy-encounter.test.ts | 9 +- .../department-store-sale-encounter.test.ts | 12 +-- .../encounters/field-trip-encounter.test.ts | 11 ++- .../fight-or-flight-encounter.test.ts | 9 +- .../fun-and-games-encounter.test.ts | 12 +-- .../global-trade-system-encounter.test.ts | 12 +-- .../encounters/lost-at-sea-encounter.test.ts | 12 +-- .../mysterious-challengers-encounter.test.ts | 12 +-- .../encounters/part-timer-encounter.test.ts | 12 +-- .../encounters/safari-zone.test.ts | 12 +-- .../teleporting-hijinks-encounter.test.ts | 9 +- .../the-expert-breeder-encounter.test.ts | 12 +-- .../the-pokemon-salesman-encounter.test.ts | 12 +-- .../the-strong-stuff-encounter.test.ts | 3 +- .../the-winstrate-challenge-encounter.test.ts | 12 +-- .../trash-to-treasure-encounter.test.ts | 9 +- .../encounters/weird-dream-encounter.test.ts | 9 +- .../mystery-encounter-utils.test.ts | 1 + .../mystery-encounter.test.ts | 3 +- test/phases/mystery-encounter-phase.test.ts | 5 +- test/testUtils/helpers/moveHelper.ts | 4 +- test/ui/pokedex.test.ts | 4 +- test/ui/transfer-item.test.ts | 23 ++--- 122 files changed, 877 insertions(+), 993 deletions(-) diff --git a/test/abilities/battery.test.ts b/test/abilities/battery.test.ts index 980eaa5b381..db129e72b0b 100644 --- a/test/abilities/battery.test.ts +++ b/test/abilities/battery.test.ts @@ -26,11 +26,12 @@ describe("Abilities - Battery", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("double"); - game.override.enemySpecies(SpeciesId.SHUCKLE); - game.override.enemyAbility(AbilityId.BALL_FETCH); - game.override.moveset([MoveId.TACKLE, MoveId.BREAKING_SWIPE, MoveId.SPLASH, MoveId.DAZZLING_GLEAM]); - game.override.enemyMoveset(MoveId.SPLASH); + game.override + .battleStyle("double") + .enemySpecies(SpeciesId.SHUCKLE) + .enemyAbility(AbilityId.BALL_FETCH) + .moveset([MoveId.TACKLE, MoveId.BREAKING_SWIPE, MoveId.SPLASH, MoveId.DAZZLING_GLEAM]) + .enemyMoveset(MoveId.SPLASH); }); it("raises the power of allies' special moves by 30%", async () => { diff --git a/test/abilities/costar.test.ts b/test/abilities/costar.test.ts index ae09aeea096..a40d84e72f5 100644 --- a/test/abilities/costar.test.ts +++ b/test/abilities/costar.test.ts @@ -24,10 +24,11 @@ describe("Abilities - COSTAR", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("double"); - game.override.ability(AbilityId.COSTAR); - game.override.moveset([MoveId.SPLASH, MoveId.NASTY_PLOT]); - game.override.enemyMoveset(MoveId.SPLASH); + game.override + .battleStyle("double") + .ability(AbilityId.COSTAR) + .moveset([MoveId.SPLASH, MoveId.NASTY_PLOT]) + .enemyMoveset(MoveId.SPLASH); }); test("ability copies positive stat stages", async () => { diff --git a/test/abilities/disguise.test.ts b/test/abilities/disguise.test.ts index 64e0715774d..4ccd825e574 100644 --- a/test/abilities/disguise.test.ts +++ b/test/abilities/disguise.test.ts @@ -66,8 +66,7 @@ describe("Abilities - Disguise", () => { }); it("takes no damage from the first hit of a multihit move and transforms to Busted form, then takes damage from the second hit", async () => { - game.override.moveset([MoveId.SURGING_STRIKES]); - game.override.enemyLevel(5); + game.override.moveset([MoveId.SURGING_STRIKES]).enemyLevel(5); await game.classicMode.startBattle(); const mimikyu = game.scene.getEnemyPokemon()!; @@ -106,8 +105,7 @@ describe("Abilities - Disguise", () => { }); it("persists form change when switched out", async () => { - game.override.enemyMoveset([MoveId.SHADOW_SNEAK]); - game.override.starterSpecies(0); + game.override.enemyMoveset([MoveId.SHADOW_SNEAK]).starterSpecies(0); await game.classicMode.startBattle([SpeciesId.MIMIKYU, SpeciesId.FURRET]); @@ -131,8 +129,7 @@ describe("Abilities - Disguise", () => { }); it("persists form change when wave changes with no arena reset", async () => { - game.override.starterSpecies(0); - game.override.starterForms({ + game.override.starterSpecies(0).starterForms({ [SpeciesId.MIMIKYU]: bustedForm, }); await game.classicMode.startBattle([SpeciesId.FURRET, SpeciesId.MIMIKYU]); @@ -148,11 +145,12 @@ describe("Abilities - Disguise", () => { }); it("reverts to Disguised form on arena reset", async () => { - game.override.startingWave(4); - game.override.starterSpecies(SpeciesId.MIMIKYU); - game.override.starterForms({ - [SpeciesId.MIMIKYU]: bustedForm, - }); + game.override + .startingWave(4) + .starterSpecies(SpeciesId.MIMIKYU) + .starterForms({ + [SpeciesId.MIMIKYU]: bustedForm, + }); await game.classicMode.startBattle(); @@ -168,11 +166,12 @@ describe("Abilities - Disguise", () => { }); it("reverts to Disguised form on biome change when fainted", async () => { - game.override.startingWave(10); - game.override.starterSpecies(0); - game.override.starterForms({ - [SpeciesId.MIMIKYU]: bustedForm, - }); + game.override + .startingWave(10) + .starterSpecies(0) + .starterForms({ + [SpeciesId.MIMIKYU]: bustedForm, + }); await game.classicMode.startBattle([SpeciesId.MIMIKYU, SpeciesId.FURRET]); @@ -206,8 +205,7 @@ describe("Abilities - Disguise", () => { }); it("activates when Aerilate circumvents immunity to the move's base type", async () => { - game.override.ability(AbilityId.AERILATE); - game.override.moveset([MoveId.TACKLE]); + game.override.ability(AbilityId.AERILATE).moveset([MoveId.TACKLE]); await game.classicMode.startBattle(); diff --git a/test/abilities/flash_fire.test.ts b/test/abilities/flash_fire.test.ts index 92bd7b52e57..c3477388912 100644 --- a/test/abilities/flash_fire.test.ts +++ b/test/abilities/flash_fire.test.ts @@ -73,8 +73,7 @@ describe("Abilities - Flash Fire", () => { }, 20000); it("activated after being frozen", async () => { - game.override.enemyMoveset([MoveId.EMBER]).moveset(MoveId.SPLASH); - game.override.statusEffect(StatusEffect.FREEZE); + game.override.enemyMoveset([MoveId.EMBER]).moveset(MoveId.SPLASH).statusEffect(StatusEffect.FREEZE); await game.classicMode.startBattle([SpeciesId.BLISSEY]); const blissey = game.scene.getPlayerPokemon()!; @@ -102,8 +101,11 @@ describe("Abilities - Flash Fire", () => { }, 20000); it("boosts Fire-type move when the ability is activated", async () => { - game.override.enemyMoveset([MoveId.FIRE_PLEDGE]).moveset([MoveId.EMBER, MoveId.SPLASH]); - game.override.enemyAbility(AbilityId.FLASH_FIRE).ability(AbilityId.NONE); + game.override + .enemyMoveset([MoveId.FIRE_PLEDGE]) + .moveset([MoveId.EMBER, MoveId.SPLASH]) + .enemyAbility(AbilityId.FLASH_FIRE) + .ability(AbilityId.NONE); await game.classicMode.startBattle([SpeciesId.BLISSEY]); const blissey = game.scene.getPlayerPokemon()!; const initialHP = 1000; @@ -127,9 +129,12 @@ describe("Abilities - Flash Fire", () => { }, 20000); it("still activates regardless of accuracy check", async () => { - game.override.moveset(MoveId.FIRE_PLEDGE).enemyMoveset(MoveId.EMBER); - game.override.enemyAbility(AbilityId.NONE).ability(AbilityId.FLASH_FIRE); - game.override.enemySpecies(SpeciesId.BLISSEY); + game.override + .moveset(MoveId.FIRE_PLEDGE) + .enemyMoveset(MoveId.EMBER) + .enemyAbility(AbilityId.NONE) + .ability(AbilityId.FLASH_FIRE) + .enemySpecies(SpeciesId.BLISSEY); await game.classicMode.startBattle([SpeciesId.RATTATA]); const blissey = game.scene.getEnemyPokemon()!; diff --git a/test/abilities/flower_gift.test.ts b/test/abilities/flower_gift.test.ts index 26180492cbd..f072afe7f34 100644 --- a/test/abilities/flower_gift.test.ts +++ b/test/abilities/flower_gift.test.ts @@ -47,9 +47,10 @@ describe("Abilities - Flower Gift", () => { allyAbility = AbilityId.BALL_FETCH, enemyAbility = AbilityId.BALL_FETCH, ): Promise<[number, number]> => { - game.override.battleStyle("double"); - game.override.moveset([MoveId.SPLASH, MoveId.SUNNY_DAY, move, MoveId.HEAL_PULSE]); - game.override.enemyMoveset([MoveId.SPLASH, MoveId.HEAL_PULSE]); + game.override + .battleStyle("double") + .moveset([MoveId.SPLASH, MoveId.SUNNY_DAY, move, MoveId.HEAL_PULSE]) + .enemyMoveset([MoveId.SPLASH, MoveId.HEAL_PULSE]); const target_index = allyAttacker ? BattlerIndex.ENEMY : BattlerIndex.PLAYER_2; const attacker_index = allyAttacker ? BattlerIndex.PLAYER_2 : BattlerIndex.ENEMY; const ally_move = allyAttacker ? move : MoveId.SPLASH; diff --git a/test/abilities/good_as_gold.test.ts b/test/abilities/good_as_gold.test.ts index 85fadd472dc..3b6600eabd4 100644 --- a/test/abilities/good_as_gold.test.ts +++ b/test/abilities/good_as_gold.test.ts @@ -63,9 +63,10 @@ describe("Abilities - Good As Gold", () => { }); it("should not block any status moves that target the field, one side, or all pokemon", async () => { - game.override.battleStyle("double"); - game.override.enemyMoveset([MoveId.STEALTH_ROCK, MoveId.HAZE]); - game.override.moveset([MoveId.SWORDS_DANCE, MoveId.SAFEGUARD]); + game.override + .battleStyle("double") + .enemyMoveset([MoveId.STEALTH_ROCK, MoveId.HAZE]) + .moveset([MoveId.SWORDS_DANCE, MoveId.SAFEGUARD]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.FEEBAS]); const [good_as_gold, ball_fetch] = game.scene.getPlayerField(); @@ -85,8 +86,7 @@ describe("Abilities - Good As Gold", () => { }); it("should not block field targeted effects in singles", async () => { - game.override.battleStyle("single"); - game.override.enemyMoveset([MoveId.SPIKES]); + game.override.battleStyle("single").enemyMoveset([MoveId.SPIKES]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); game.move.select(MoveId.SPLASH, 0); @@ -96,8 +96,7 @@ describe("Abilities - Good As Gold", () => { }); it("should block the ally's helping hand", async () => { - game.override.battleStyle("double"); - game.override.moveset([MoveId.HELPING_HAND, MoveId.TACKLE]); + game.override.battleStyle("double").moveset([MoveId.HELPING_HAND, MoveId.TACKLE]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.FEEBAS]); game.move.select(MoveId.HELPING_HAND, 0); @@ -129,8 +128,7 @@ describe("Abilities - Good As Gold", () => { }); it("should not block field targeted effects like rain dance", async () => { - game.override.battleStyle("single"); - game.override.enemyMoveset([MoveId.RAIN_DANCE]); + game.override.battleStyle("single").enemyMoveset([MoveId.RAIN_DANCE]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); game.move.use(MoveId.SPLASH, 0); diff --git a/test/abilities/hustle.test.ts b/test/abilities/hustle.test.ts index 58aa01bb39a..7ac636b15b2 100644 --- a/test/abilities/hustle.test.ts +++ b/test/abilities/hustle.test.ts @@ -75,8 +75,7 @@ describe("Abilities - Hustle", () => { }); it("does not affect OHKO moves", async () => { - game.override.startingLevel(100); - game.override.enemyLevel(30); + game.override.startingLevel(100).enemyLevel(30); await game.classicMode.startBattle([SpeciesId.PIKACHU]); const pikachu = game.scene.getPlayerPokemon()!; diff --git a/test/abilities/ice_face.test.ts b/test/abilities/ice_face.test.ts index 2899120db5a..c42713d7e6c 100644 --- a/test/abilities/ice_face.test.ts +++ b/test/abilities/ice_face.test.ts @@ -30,10 +30,11 @@ describe("Abilities - Ice Face", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.enemySpecies(SpeciesId.EISCUE); - game.override.enemyAbility(AbilityId.ICE_FACE); - game.override.moveset([MoveId.TACKLE, MoveId.ICE_BEAM, MoveId.TOXIC_THREAD, MoveId.HAIL]); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.EISCUE) + .enemyAbility(AbilityId.ICE_FACE) + .moveset([MoveId.TACKLE, MoveId.ICE_BEAM, MoveId.TOXIC_THREAD, MoveId.HAIL]); }); it("takes no damage from physical move and transforms to Noice", async () => { @@ -51,8 +52,7 @@ describe("Abilities - Ice Face", () => { }); it("takes no damage from the first hit of multihit physical move and transforms to Noice", async () => { - game.override.moveset([MoveId.SURGING_STRIKES]); - game.override.enemyLevel(1); + game.override.moveset([MoveId.SURGING_STRIKES]).enemyLevel(1); await game.classicMode.startBattle([SpeciesId.HITMONLEE]); game.move.select(MoveId.SURGING_STRIKES); @@ -196,12 +196,13 @@ describe("Abilities - Ice Face", () => { }); it("reverts to Ice Face on arena reset", async () => { - game.override.startingWave(4); - game.override.startingLevel(4); - game.override.enemySpecies(SpeciesId.MAGIKARP); - game.override.starterForms({ - [SpeciesId.EISCUE]: noiceForm, - }); + game.override + .startingWave(4) + .startingLevel(4) + .enemySpecies(SpeciesId.MAGIKARP) + .starterForms({ + [SpeciesId.EISCUE]: noiceForm, + }); await game.classicMode.startBattle([SpeciesId.EISCUE]); diff --git a/test/abilities/illusion.test.ts b/test/abilities/illusion.test.ts index 37e2ca59c6a..1c2809ad813 100644 --- a/test/abilities/illusion.test.ts +++ b/test/abilities/illusion.test.ts @@ -116,26 +116,23 @@ describe("Abilities - Illusion", () => { expect(psychicEffectiveness).above(flameThrowerEffectiveness); }); - it("does not break from indirect damage", async () => { - game.override.enemySpecies(SpeciesId.GIGALITH); - game.override.enemyAbility(AbilityId.SAND_STREAM); - game.override.enemyMoveset(MoveId.WILL_O_WISP); - game.override.moveset([MoveId.FLARE_BLITZ]); + it("should not break from indirect damage from status, weather or recoil", async () => { + game.override.enemySpecies(SpeciesId.GIGALITH).enemyAbility(AbilityId.SAND_STREAM); await game.classicMode.startBattle([SpeciesId.ZOROARK, SpeciesId.AZUMARILL]); - game.move.select(MoveId.FLARE_BLITZ); - - await game.phaseInterceptor.to("TurnEndPhase"); + game.move.use(MoveId.FLARE_BLITZ); + await game.move.forceEnemyMove(MoveId.WILL_O_WISP); + await game.toEndOfTurn(); const zoroark = game.scene.getPlayerPokemon()!; - expect(!!zoroark.summonData.illusion).equals(true); }); it("copies the the name, nickname, gender, shininess, and pokeball from the illusion source", async () => { game.override.enemyMoveset(MoveId.SPLASH); await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.ZOROARK, SpeciesId.AXEW]); + const axew = game.scene.getPlayerParty().at(2)!; axew.shiny = true; axew.nickname = btoa(unescape(encodeURIComponent("axew nickname"))); diff --git a/test/abilities/imposter.test.ts b/test/abilities/imposter.test.ts index e9ae6bd64bb..30491139877 100644 --- a/test/abilities/imposter.test.ts +++ b/test/abilities/imposter.test.ts @@ -162,8 +162,7 @@ describe("Abilities - Imposter", () => { }); it("should stay transformed with the correct form after reload", async () => { - game.override.moveset([MoveId.ABSORB]); - game.override.enemySpecies(SpeciesId.UNOWN); + game.override.moveset([MoveId.ABSORB]).enemySpecies(SpeciesId.UNOWN); await game.classicMode.startBattle([SpeciesId.DITTO]); const enemy = game.scene.getEnemyPokemon()!; diff --git a/test/abilities/intimidate.test.ts b/test/abilities/intimidate.test.ts index e72b9669b6e..cc75b257686 100644 --- a/test/abilities/intimidate.test.ts +++ b/test/abilities/intimidate.test.ts @@ -88,8 +88,7 @@ describe("Abilities - Intimidate", () => { }, 20000); it("should not activate again if there is no switch or new entry", async () => { - game.override.startingWave(2); - game.override.moveset([MoveId.SPLASH]); + game.override.startingWave(2).moveset([MoveId.SPLASH]); await game.classicMode.startBattle([SpeciesId.MIGHTYENA, SpeciesId.POOCHYENA]); const playerPokemon = game.scene.getPlayerPokemon()!; diff --git a/test/abilities/intrepid_sword.test.ts b/test/abilities/intrepid_sword.test.ts index 7cfa9581bb9..3a24f44a78e 100644 --- a/test/abilities/intrepid_sword.test.ts +++ b/test/abilities/intrepid_sword.test.ts @@ -22,10 +22,11 @@ describe("Abilities - Intrepid Sword", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.enemySpecies(SpeciesId.ZACIAN); - game.override.enemyAbility(AbilityId.INTREPID_SWORD); - game.override.ability(AbilityId.INTREPID_SWORD); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.ZACIAN) + .enemyAbility(AbilityId.INTREPID_SWORD) + .ability(AbilityId.INTREPID_SWORD); }); it("should raise ATK stat stage by 1 on entry", async () => { diff --git a/test/abilities/libero.test.ts b/test/abilities/libero.test.ts index 6cae83219fd..eaa2630e90d 100644 --- a/test/abilities/libero.test.ts +++ b/test/abilities/libero.test.ts @@ -108,8 +108,7 @@ describe("Abilities - Libero", () => { }); test("ability applies correctly even if the type has changed by another ability", async () => { - game.override.moveset([MoveId.TACKLE]); - game.override.passiveAbility(AbilityId.REFRIGERATE); + game.override.moveset([MoveId.TACKLE]).passiveAbility(AbilityId.REFRIGERATE); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -156,8 +155,7 @@ describe("Abilities - Libero", () => { }); test("ability applies correctly even if the pokemon's move misses", async () => { - game.override.moveset([MoveId.TACKLE]); - game.override.enemyMoveset(MoveId.SPLASH); + game.override.moveset([MoveId.TACKLE]).enemyMoveset(MoveId.SPLASH); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -188,8 +186,7 @@ describe("Abilities - Libero", () => { }); test("ability applies correctly even if the pokemon's move fails because of type immunity", async () => { - game.override.moveset([MoveId.TACKLE]); - game.override.enemySpecies(SpeciesId.GASTLY); + game.override.moveset([MoveId.TACKLE]).enemySpecies(SpeciesId.GASTLY); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -262,8 +259,7 @@ describe("Abilities - Libero", () => { }); test("ability applies correctly even if the pokemon's Trick-or-Treat fails", async () => { - game.override.moveset([MoveId.TRICK_OR_TREAT]); - game.override.enemySpecies(SpeciesId.GASTLY); + game.override.moveset([MoveId.TRICK_OR_TREAT]).enemySpecies(SpeciesId.GASTLY); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); diff --git a/test/abilities/magic_bounce.test.ts b/test/abilities/magic_bounce.test.ts index 3c4ff2ad909..fb4f273950a 100644 --- a/test/abilities/magic_bounce.test.ts +++ b/test/abilities/magic_bounce.test.ts @@ -41,18 +41,16 @@ describe("Abilities - Magic Bounce", () => { it("should reflect basic status moves", async () => { await game.classicMode.startBattle([SpeciesId.MAGIKARP]); - game.move.select(MoveId.GROWL); + game.move.use(MoveId.GROWL); await game.phaseInterceptor.to("BerryPhase"); expect(game.scene.getPlayerPokemon()!.getStatStage(Stat.ATK)).toBe(-1); }); it("should not bounce moves while the target is in the semi-invulnerable state", async () => { await game.classicMode.startBattle([SpeciesId.MAGIKARP]); - game.override.moveset([MoveId.GROWL]); - game.override.enemyMoveset([MoveId.FLY]); - game.move.select(MoveId.GROWL); - await game.move.selectEnemyMove(MoveId.FLY); + game.move.use(MoveId.GROWL); + await game.move.forceEnemyMove(MoveId.FLY); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("BerryPhase"); @@ -61,11 +59,10 @@ describe("Abilities - Magic Bounce", () => { it("should individually bounce back multi-target moves", async () => { game.override.battleStyle("double"); - game.override.moveset([MoveId.GROWL, MoveId.SPLASH]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]); - game.move.select(MoveId.GROWL, 0); - game.move.select(MoveId.SPLASH, 1); + game.move.use(MoveId.GROWL, 0); + game.move.use(MoveId.SPLASH, 1); await game.phaseInterceptor.to("BerryPhase"); const user = game.scene.getPlayerField()[0]; @@ -75,9 +72,8 @@ describe("Abilities - Magic Bounce", () => { it("should still bounce back a move that would otherwise fail", async () => { await game.classicMode.startBattle([SpeciesId.MAGIKARP]); game.scene.getEnemyPokemon()?.setStatStage(Stat.ATK, -6); - game.override.moveset([MoveId.GROWL]); - game.move.select(MoveId.GROWL); + game.move.use(MoveId.GROWL); await game.phaseInterceptor.to("BerryPhase"); expect(game.scene.getPlayerPokemon()!.getStatStage(Stat.ATK)).toBe(-1); @@ -107,29 +103,26 @@ describe("Abilities - Magic Bounce", () => { game.override.ability(AbilityId.MOLD_BREAKER); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); - game.move.select(MoveId.GROWL); + game.move.use(MoveId.GROWL); await game.phaseInterceptor.to("BerryPhase"); expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.ATK)).toBe(-1); }); it("should bounce back a spread status move against both pokemon", async () => { - game.override.battleStyle("double"); - game.override.moveset([MoveId.GROWL, MoveId.SPLASH]); - game.override.enemyMoveset([MoveId.SPLASH]); + game.override.battleStyle("double").enemyMoveset([MoveId.SPLASH]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]); - game.move.select(MoveId.GROWL, 0); - game.move.select(MoveId.SPLASH, 1); + game.move.use(MoveId.GROWL, 0); + game.move.use(MoveId.SPLASH, 1); await game.phaseInterceptor.to("BerryPhase"); expect(game.scene.getPlayerField().every(p => p.getStatStage(Stat.ATK) === -2)).toBeTruthy(); }); it("should only bounce spikes back once in doubles when both targets have magic bounce", async () => { - game.override.battleStyle("double"); + game.override.battleStyle("double").moveset([MoveId.SPIKES]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); - game.override.moveset([MoveId.SPIKES]); game.move.select(MoveId.SPIKES); await game.phaseInterceptor.to("BerryPhase"); @@ -139,8 +132,7 @@ describe("Abilities - Magic Bounce", () => { }); it("should bounce spikes even when the target is protected", async () => { - game.override.moveset([MoveId.SPIKES]); - game.override.enemyMoveset([MoveId.PROTECT]); + game.override.moveset([MoveId.SPIKES]).enemyMoveset([MoveId.PROTECT]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); game.move.select(MoveId.SPIKES); @@ -149,8 +141,7 @@ describe("Abilities - Magic Bounce", () => { }); it("should not bounce spikes when the target is in the semi-invulnerable state", async () => { - game.override.moveset([MoveId.SPIKES]); - game.override.enemyMoveset([MoveId.FLY]); + game.override.moveset([MoveId.SPIKES]).enemyMoveset([MoveId.FLY]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); game.move.select(MoveId.SPIKES); @@ -160,9 +151,8 @@ describe("Abilities - Magic Bounce", () => { }); it("should not bounce back curse", async () => { - game.override.starterSpecies(SpeciesId.GASTLY); - await game.classicMode.startBattle([SpeciesId.GASTLY]); game.override.moveset([MoveId.CURSE]); + await game.classicMode.startBattle([SpeciesId.GASTLY]); game.move.select(MoveId.CURSE); await game.phaseInterceptor.to("BerryPhase"); @@ -171,8 +161,7 @@ describe("Abilities - Magic Bounce", () => { }); it("should not cause encore to be interrupted after bouncing", async () => { - game.override.moveset([MoveId.SPLASH, MoveId.GROWL, MoveId.ENCORE]); - game.override.enemyMoveset([MoveId.TACKLE, MoveId.GROWL]); + game.override.moveset([MoveId.SPLASH, MoveId.GROWL, MoveId.ENCORE]).enemyMoveset([MoveId.TACKLE, MoveId.GROWL]); // game.override.ability(AbilityId.MOLD_BREAKER); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); const playerPokemon = game.scene.getPlayerPokemon()!; @@ -199,9 +188,10 @@ describe("Abilities - Magic Bounce", () => { // TODO: encore is failing if the last move was virtual. it.todo("should not cause the bounced move to count for encore", async () => { - game.override.moveset([MoveId.SPLASH, MoveId.GROWL, MoveId.ENCORE]); - game.override.enemyMoveset([MoveId.GROWL, MoveId.TACKLE]); - game.override.enemyAbility(AbilityId.MAGIC_BOUNCE); + game.override + .moveset([MoveId.SPLASH, MoveId.GROWL, MoveId.ENCORE]) + .enemyMoveset([MoveId.GROWL, MoveId.TACKLE]) + .enemyAbility(AbilityId.MAGIC_BOUNCE); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); const playerPokemon = game.scene.getPlayerPokemon()!; @@ -227,9 +217,8 @@ describe("Abilities - Magic Bounce", () => { // TODO: stomping tantrum should consider moves that were bounced. it.todo("should cause stomping tantrum to double in power when the last move was bounced", async () => { - game.override.battleStyle("single"); + game.override.battleStyle("single").moveset([MoveId.STOMPING_TANTRUM, MoveId.CHARM]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); - game.override.moveset([MoveId.STOMPING_TANTRUM, MoveId.CHARM]); const stomping_tantrum = allMoves[MoveId.STOMPING_TANTRUM]; vi.spyOn(stomping_tantrum, "calculateBattlePower"); @@ -242,36 +231,30 @@ describe("Abilities - Magic Bounce", () => { expect(stomping_tantrum.calculateBattlePower).toHaveReturnedWith(150); }); - // TODO: stomping tantrum should consider moves that were bounced. - it.todo( - "should properly cause the enemy's stomping tantrum to be doubled in power after bouncing and failing", - async () => { - game.override.enemyMoveset([MoveId.STOMPING_TANTRUM, MoveId.SPLASH, MoveId.CHARM]); - await game.classicMode.startBattle([SpeciesId.BULBASAUR]); + // TODO: stomping tantrum should consider moves that were bounced + it.todo("should boost enemy's stomping tantrum after failed bounce", async () => { + game.override.enemyMoveset([MoveId.STOMPING_TANTRUM, MoveId.SPLASH, MoveId.CHARM]); + await game.classicMode.startBattle([SpeciesId.BULBASAUR]); - const stomping_tantrum = allMoves[MoveId.STOMPING_TANTRUM]; - const enemy = game.scene.getEnemyPokemon()!; - vi.spyOn(stomping_tantrum, "calculateBattlePower"); + const stomping_tantrum = allMoves[MoveId.STOMPING_TANTRUM]; + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(stomping_tantrum, "calculateBattlePower"); - game.move.select(MoveId.SPORE); - await game.move.selectEnemyMove(MoveId.CHARM); - await game.phaseInterceptor.to("TurnEndPhase"); - expect(enemy.getLastXMoves(1)[0].result).toBe("success"); + // Spore gets reflected back onto us + game.move.select(MoveId.SPORE); + await game.move.selectEnemyMove(MoveId.CHARM); + await game.toNextTurn(); + expect(enemy.getLastXMoves(1)[0].result).toBe("success"); - await game.phaseInterceptor.to("BerryPhase"); - expect(stomping_tantrum.calculateBattlePower).toHaveReturnedWith(75); - - await game.toNextTurn(); - game.move.select(MoveId.GROWL); - await game.phaseInterceptor.to("BerryPhase"); - expect(stomping_tantrum.calculateBattlePower).toHaveReturnedWith(75); - }, - ); + game.move.select(MoveId.SPORE); + await game.move.selectEnemyMove(MoveId.STOMPING_TANTRUM); + await game.toNextTurn(); + expect(stomping_tantrum.calculateBattlePower).toHaveReturnedWith(150); + }); it("should respect immunities when bouncing a move", async () => { vi.spyOn(allMoves[MoveId.THUNDER_WAVE], "accuracy", "get").mockReturnValue(100); - game.override.moveset([MoveId.THUNDER_WAVE, MoveId.GROWL]); - game.override.ability(AbilityId.SOUNDPROOF); + game.override.moveset([MoveId.THUNDER_WAVE, MoveId.GROWL]).ability(AbilityId.SOUNDPROOF); await game.classicMode.startBattle([SpeciesId.PHANPY]); // Turn 1 - thunder wave immunity test @@ -309,8 +292,7 @@ describe("Abilities - Magic Bounce", () => { }); it("should always apply the leftmost available target's magic bounce when bouncing moves like sticky webs in doubles", async () => { - game.override.battleStyle("double"); - game.override.moveset([MoveId.STICKY_WEB, MoveId.SPLASH, MoveId.TRICK_ROOM]); + game.override.battleStyle("double").moveset([MoveId.STICKY_WEB, MoveId.SPLASH, MoveId.TRICK_ROOM]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]); const [enemy_1, enemy_2] = game.scene.getEnemyField(); @@ -345,6 +327,7 @@ describe("Abilities - Magic Bounce", () => { it("should not bounce back status moves that hit through semi-invulnerable states", async () => { game.override.moveset([MoveId.TOXIC, MoveId.CHARM]); await game.classicMode.startBattle([SpeciesId.BULBASAUR]); + game.move.select(MoveId.TOXIC); await game.move.selectEnemyMove(MoveId.FLY); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); diff --git a/test/abilities/magic_guard.test.ts b/test/abilities/magic_guard.test.ts index 40bb3203673..4274e692a2e 100644 --- a/test/abilities/magic_guard.test.ts +++ b/test/abilities/magic_guard.test.ts @@ -30,16 +30,16 @@ describe("Abilities - Magic Guard", () => { beforeEach(() => { game = new GameManager(phaserGame); - /** Player Pokemon overrides */ - game.override.ability(AbilityId.MAGIC_GUARD); - game.override.moveset([MoveId.SPLASH]); - game.override.startingLevel(100); - - /** Enemy Pokemon overrides */ - game.override.enemySpecies(SpeciesId.SNORLAX); - game.override.enemyAbility(AbilityId.INSOMNIA); - game.override.enemyMoveset(MoveId.SPLASH); - game.override.enemyLevel(100); + game.override + /** Player Pokemon overrides */ + .ability(AbilityId.MAGIC_GUARD) + .moveset([MoveId.SPLASH]) + .startingLevel(100) + /** Enemy Pokemon overrides */ + .enemySpecies(SpeciesId.SNORLAX) + .enemyAbility(AbilityId.INSOMNIA) + .enemyMoveset(MoveId.SPLASH) + .enemyLevel(100); }); //Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/Magic_Guard_(Ability) @@ -89,8 +89,9 @@ describe("Abilities - Magic Guard", () => { }); it("ability effect should not persist when the ability is replaced", async () => { - game.override.enemyMoveset([MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED]); - game.override.statusEffect(StatusEffect.POISON); + game.override + .enemyMoveset([MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED]) + .statusEffect(StatusEffect.POISON); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -108,8 +109,7 @@ describe("Abilities - Magic Guard", () => { }); it("Magic Guard prevents damage caused by burn but other non-damaging effects are still applied", async () => { - game.override.enemyStatusEffect(StatusEffect.BURN); - game.override.enemyAbility(AbilityId.MAGIC_GUARD); + game.override.enemyStatusEffect(StatusEffect.BURN).enemyAbility(AbilityId.MAGIC_GUARD); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -130,8 +130,7 @@ describe("Abilities - Magic Guard", () => { }); it("Magic Guard prevents damage caused by toxic but other non-damaging effects are still applied", async () => { - game.override.enemyStatusEffect(StatusEffect.TOXIC); - game.override.enemyAbility(AbilityId.MAGIC_GUARD); + game.override.enemyStatusEffect(StatusEffect.TOXIC).enemyAbility(AbilityId.MAGIC_GUARD); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -208,8 +207,7 @@ describe("Abilities - Magic Guard", () => { it("Magic Guard prevents against damage from volatile status effects", async () => { await game.classicMode.startBattle([SpeciesId.DUSKULL]); - game.override.moveset([MoveId.CURSE]); - game.override.enemyAbility(AbilityId.MAGIC_GUARD); + game.override.moveset([MoveId.CURSE]).enemyAbility(AbilityId.MAGIC_GUARD); const leadPokemon = game.scene.getPlayerPokemon()!; @@ -331,8 +329,9 @@ describe("Abilities - Magic Guard", () => { //Tests the ability Bad Dreams game.override.statusEffect(StatusEffect.SLEEP); //enemy pokemon is given Spore just in case player pokemon somehow awakens during test - game.override.enemyMoveset([MoveId.SPORE, MoveId.SPORE, MoveId.SPORE, MoveId.SPORE]); - game.override.enemyAbility(AbilityId.BAD_DREAMS); + game.override + .enemyMoveset([MoveId.SPORE, MoveId.SPORE, MoveId.SPORE, MoveId.SPORE]) + .enemyAbility(AbilityId.BAD_DREAMS); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -353,8 +352,7 @@ describe("Abilities - Magic Guard", () => { it("Magic Guard prevents damage from abilities with PostFaintContactDamageAbAttr", async () => { //Tests the abilities Innards Out/Aftermath - game.override.moveset([MoveId.TACKLE]); - game.override.enemyAbility(AbilityId.AFTERMATH); + game.override.moveset([MoveId.TACKLE]).enemyAbility(AbilityId.AFTERMATH); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -377,8 +375,7 @@ describe("Abilities - Magic Guard", () => { it("Magic Guard prevents damage from abilities with PostDefendContactDamageAbAttr", async () => { //Tests the abilities Iron Barbs/Rough Skin - game.override.moveset([MoveId.TACKLE]); - game.override.enemyAbility(AbilityId.IRON_BARBS); + game.override.moveset([MoveId.TACKLE]).enemyAbility(AbilityId.IRON_BARBS); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -400,8 +397,7 @@ describe("Abilities - Magic Guard", () => { it("Magic Guard prevents damage from abilities with ReverseDrainAbAttr", async () => { //Tests the ability Liquid Ooze - game.override.moveset([MoveId.ABSORB]); - game.override.enemyAbility(AbilityId.LIQUID_OOZE); + game.override.moveset([MoveId.ABSORB]).enemyAbility(AbilityId.LIQUID_OOZE); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -422,9 +418,7 @@ describe("Abilities - Magic Guard", () => { }); it("Magic Guard prevents HP loss from abilities with PostWeatherLapseDamageAbAttr", async () => { - //Tests the abilities Solar Power/Dry Skin - game.override.passiveAbility(AbilityId.SOLAR_POWER); - game.override.weather(WeatherType.SUNNY); + game.override.passiveAbility(AbilityId.SOLAR_POWER).weather(WeatherType.SUNNY); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; diff --git a/test/abilities/mirror_armor.test.ts b/test/abilities/mirror_armor.test.ts index 540a3b5d426..71b072c7d06 100644 --- a/test/abilities/mirror_armor.test.ts +++ b/test/abilities/mirror_armor.test.ts @@ -37,8 +37,7 @@ describe("Ability - Mirror Armor", () => { }); it("Player side + single battle Intimidate - opponent loses stats", async () => { - game.override.ability(AbilityId.MIRROR_ARMOR); - game.override.enemyAbility(AbilityId.INTIMIDATE); + game.override.ability(AbilityId.MIRROR_ARMOR).enemyAbility(AbilityId.INTIMIDATE); await game.classicMode.startBattle([SpeciesId.BULBASAUR]); const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -54,8 +53,7 @@ describe("Ability - Mirror Armor", () => { }); it("Enemy side + single battle Intimidate - player loses stats", async () => { - game.override.enemyAbility(AbilityId.MIRROR_ARMOR); - game.override.ability(AbilityId.INTIMIDATE); + game.override.enemyAbility(AbilityId.MIRROR_ARMOR).ability(AbilityId.INTIMIDATE); await game.classicMode.startBattle([SpeciesId.BULBASAUR]); const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -71,9 +69,7 @@ describe("Ability - Mirror Armor", () => { }); it("Player side + double battle Intimidate - opponents each lose -2 atk", async () => { - game.override.battleStyle("double"); - game.override.ability(AbilityId.MIRROR_ARMOR); - game.override.enemyAbility(AbilityId.INTIMIDATE); + game.override.battleStyle("double").ability(AbilityId.MIRROR_ARMOR).enemyAbility(AbilityId.INTIMIDATE); await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER]); const [enemy1, enemy2] = game.scene.getEnemyField(); @@ -93,9 +89,7 @@ describe("Ability - Mirror Armor", () => { }); it("Enemy side + double battle Intimidate - players each lose -2 atk", async () => { - game.override.battleStyle("double"); - game.override.enemyAbility(AbilityId.MIRROR_ARMOR); - game.override.ability(AbilityId.INTIMIDATE); + game.override.battleStyle("double").enemyAbility(AbilityId.MIRROR_ARMOR).ability(AbilityId.INTIMIDATE); await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER]); const [enemy1, enemy2] = game.scene.getEnemyField(); @@ -115,8 +109,7 @@ describe("Ability - Mirror Armor", () => { }); it("Player side + single battle Intimidate + Tickle - opponent loses stats", async () => { - game.override.ability(AbilityId.MIRROR_ARMOR); - game.override.enemyAbility(AbilityId.INTIMIDATE); + game.override.ability(AbilityId.MIRROR_ARMOR).enemyAbility(AbilityId.INTIMIDATE); await game.classicMode.startBattle([SpeciesId.BULBASAUR]); const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -134,9 +127,7 @@ describe("Ability - Mirror Armor", () => { }); it("Player side + double battle Intimidate + Tickle - opponents each lose -3 atk, -1 def", async () => { - game.override.battleStyle("double"); - game.override.ability(AbilityId.MIRROR_ARMOR); - game.override.enemyAbility(AbilityId.INTIMIDATE); + game.override.battleStyle("double").ability(AbilityId.MIRROR_ARMOR).enemyAbility(AbilityId.INTIMIDATE); await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER]); const [enemy1, enemy2] = game.scene.getEnemyField(); @@ -159,8 +150,7 @@ describe("Ability - Mirror Armor", () => { }); it("Enemy side + single battle Intimidate + Tickle - player loses stats", async () => { - game.override.enemyAbility(AbilityId.MIRROR_ARMOR); - game.override.ability(AbilityId.INTIMIDATE); + game.override.enemyAbility(AbilityId.MIRROR_ARMOR).ability(AbilityId.INTIMIDATE); await game.classicMode.startBattle([SpeciesId.BULBASAUR]); const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -178,8 +168,7 @@ describe("Ability - Mirror Armor", () => { }); it("Player side + single battle Intimidate + oppoenent has white smoke - no one loses stats", async () => { - game.override.enemyAbility(AbilityId.WHITE_SMOKE); - game.override.ability(AbilityId.MIRROR_ARMOR); + game.override.enemyAbility(AbilityId.WHITE_SMOKE).ability(AbilityId.MIRROR_ARMOR); await game.classicMode.startBattle([SpeciesId.BULBASAUR]); const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -197,8 +186,7 @@ describe("Ability - Mirror Armor", () => { }); it("Enemy side + single battle Intimidate + player has white smoke - no one loses stats", async () => { - game.override.ability(AbilityId.WHITE_SMOKE); - game.override.enemyAbility(AbilityId.MIRROR_ARMOR); + game.override.ability(AbilityId.WHITE_SMOKE).enemyAbility(AbilityId.MIRROR_ARMOR); await game.classicMode.startBattle([SpeciesId.BULBASAUR]); const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -252,9 +240,7 @@ describe("Ability - Mirror Armor", () => { }); it("Both sides have mirror armor - does not loop, player loses attack", async () => { - game.override.enemyAbility(AbilityId.MIRROR_ARMOR); - game.override.ability(AbilityId.MIRROR_ARMOR); - game.override.ability(AbilityId.INTIMIDATE); + game.override.enemyAbility(AbilityId.MIRROR_ARMOR).ability(AbilityId.MIRROR_ARMOR).ability(AbilityId.INTIMIDATE); await game.classicMode.startBattle([SpeciesId.BULBASAUR]); const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -288,8 +274,7 @@ describe("Ability - Mirror Armor", () => { }); it("Double battle + sticky web applied player side - player switches out and enemy 1 should lose -1 speed", async () => { - game.override.battleStyle("double"); - game.override.ability(AbilityId.MIRROR_ARMOR); + game.override.battleStyle("double").ability(AbilityId.MIRROR_ARMOR); await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER, SpeciesId.SQUIRTLE]); const [enemy1, enemy2] = game.scene.getEnemyField(); diff --git a/test/abilities/moxie.test.ts b/test/abilities/moxie.test.ts index 8b658f60bf6..7631a0dfd2d 100644 --- a/test/abilities/moxie.test.ts +++ b/test/abilities/moxie.test.ts @@ -27,13 +27,14 @@ describe("Abilities - Moxie", () => { beforeEach(() => { game = new GameManager(phaserGame); const moveToUse = MoveId.AERIAL_ACE; - game.override.battleStyle("single"); - game.override.enemySpecies(SpeciesId.RATTATA); - game.override.enemyAbility(AbilityId.MOXIE); - game.override.ability(AbilityId.MOXIE); - game.override.startingLevel(2000); - game.override.moveset([moveToUse]); - game.override.enemyMoveset(MoveId.SPLASH); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.RATTATA) + .enemyAbility(AbilityId.MOXIE) + .ability(AbilityId.MOXIE) + .startingLevel(2000) + .moveset([moveToUse]) + .enemyMoveset(MoveId.SPLASH); }); it("should raise ATK stat stage by 1 when winning a battle", async () => { diff --git a/test/abilities/normalize.test.ts b/test/abilities/normalize.test.ts index 821ce9589a1..2e000e76a27 100644 --- a/test/abilities/normalize.test.ts +++ b/test/abilities/normalize.test.ts @@ -45,8 +45,9 @@ describe("Abilities - Normalize", () => { }); it("should not apply the old type boost item after changing a move's type", async () => { - game.override.startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: PokemonType.GRASS }]); - game.override.moveset([MoveId.LEAFAGE]); + game.override + .startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: PokemonType.GRASS }]) + .moveset([MoveId.LEAFAGE]); const powerSpy = vi.spyOn(allMoves[MoveId.LEAFAGE], "calculateBattlePower"); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -58,8 +59,9 @@ describe("Abilities - Normalize", () => { }); it("should apply silk scarf's power boost after changing a move's type", async () => { - game.override.startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: PokemonType.NORMAL }]); - game.override.moveset([MoveId.LEAFAGE]); + game.override + .startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: PokemonType.NORMAL }]) + .moveset([MoveId.LEAFAGE]); const powerSpy = vi.spyOn(allMoves[MoveId.LEAFAGE], "calculateBattlePower"); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); diff --git a/test/abilities/parental_bond.test.ts b/test/abilities/parental_bond.test.ts index 73efd0f57e2..f9bd703d751 100644 --- a/test/abilities/parental_bond.test.ts +++ b/test/abilities/parental_bond.test.ts @@ -26,14 +26,15 @@ describe("Abilities - Parental Bond", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.disableCrits(); - game.override.ability(AbilityId.PARENTAL_BOND); - game.override.enemySpecies(SpeciesId.SNORLAX); - game.override.enemyAbility(AbilityId.FUR_COAT); - game.override.enemyMoveset(MoveId.SPLASH); - game.override.startingLevel(100); - game.override.enemyLevel(100); + game.override + .battleStyle("single") + .disableCrits() + .ability(AbilityId.PARENTAL_BOND) + .enemySpecies(SpeciesId.SNORLAX) + .enemyAbility(AbilityId.FUR_COAT) + .enemyMoveset(MoveId.SPLASH) + .startingLevel(100) + .enemyLevel(100); }); it("should add second strike to attack move", async () => { @@ -61,8 +62,7 @@ describe("Abilities - Parental Bond", () => { }); it("should apply secondary effects to both strikes", async () => { - game.override.moveset([MoveId.POWER_UP_PUNCH]); - game.override.enemySpecies(SpeciesId.AMOONGUSS); + game.override.moveset([MoveId.POWER_UP_PUNCH]).enemySpecies(SpeciesId.AMOONGUSS); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -148,8 +148,7 @@ describe("Abilities - Parental Bond", () => { }); it("should not apply multiplier to counter moves", async () => { - game.override.moveset([MoveId.COUNTER]); - game.override.enemyMoveset([MoveId.TACKLE]); + game.override.moveset([MoveId.COUNTER]).enemyMoveset([MoveId.TACKLE]); await game.classicMode.startBattle([SpeciesId.SHUCKLE]); @@ -167,9 +166,7 @@ describe("Abilities - Parental Bond", () => { }); it("should not apply to multi-target moves", async () => { - game.override.battleStyle("double"); - game.override.moveset([MoveId.EARTHQUAKE]); - game.override.passiveAbility(AbilityId.LEVITATE); + game.override.battleStyle("double").moveset([MoveId.EARTHQUAKE]).passiveAbility(AbilityId.LEVITATE); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.FEEBAS]); @@ -237,8 +234,7 @@ describe("Abilities - Parental Bond", () => { }); it("Moves boosted by this ability and Multi-Lens should strike 3 times", async () => { - game.override.moveset([MoveId.TACKLE]); - game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]); + game.override.moveset([MoveId.TACKLE]).startingHeldItems([{ name: "MULTI_LENS", count: 1 }]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -252,8 +248,7 @@ describe("Abilities - Parental Bond", () => { }); it("Seismic Toss boosted by this ability and Multi-Lens should strike 3 times", async () => { - game.override.moveset([MoveId.SEISMIC_TOSS]); - game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]); + game.override.moveset([MoveId.SEISMIC_TOSS]).startingHeldItems([{ name: "MULTI_LENS", count: 1 }]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -378,8 +373,7 @@ describe("Abilities - Parental Bond", () => { }); it("should not cause user to hit into King's Shield more than once", async () => { - game.override.moveset([MoveId.TACKLE]); - game.override.enemyMoveset([MoveId.KINGS_SHIELD]); + game.override.moveset([MoveId.TACKLE]).enemyMoveset([MoveId.KINGS_SHIELD]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -393,8 +387,7 @@ describe("Abilities - Parental Bond", () => { }); it("should not cause user to hit into Storm Drain more than once", async () => { - game.override.moveset([MoveId.WATER_GUN]); - game.override.enemyAbility(AbilityId.STORM_DRAIN); + game.override.moveset([MoveId.WATER_GUN]).enemyAbility(AbilityId.STORM_DRAIN); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); diff --git a/test/abilities/perish_body.test.ts b/test/abilities/perish_body.test.ts index 9668bf349a8..5b98aab5f6c 100644 --- a/test/abilities/perish_body.test.ts +++ b/test/abilities/perish_body.test.ts @@ -21,19 +21,18 @@ describe("Abilities - Perish Song", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.disableCrits(); - - game.override.enemySpecies(SpeciesId.MAGIKARP); - game.override.enemyAbility(AbilityId.BALL_FETCH); - - game.override.starterSpecies(SpeciesId.CURSOLA); - game.override.ability(AbilityId.PERISH_BODY); - game.override.moveset([MoveId.SPLASH]); + game.override + .battleStyle("single") + .disableCrits() + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.BALL_FETCH) + .starterSpecies(SpeciesId.CURSOLA) + .ability(AbilityId.PERISH_BODY) + .moveset([MoveId.SPLASH]) + .enemyMoveset([MoveId.AQUA_JET]); }); it("should trigger when hit with damaging move", async () => { - game.override.enemyMoveset([MoveId.AQUA_JET]); await game.classicMode.startBattle(); const cursola = game.scene.getPlayerPokemon(); const magikarp = game.scene.getEnemyPokemon(); @@ -46,7 +45,7 @@ describe("Abilities - Perish Song", () => { }); it("should trigger even when fainting", async () => { - game.override.enemyMoveset([MoveId.AQUA_JET]).enemyLevel(100).startingLevel(1); + game.override.enemyLevel(100).startingLevel(1); await game.classicMode.startBattle([SpeciesId.CURSOLA, SpeciesId.FEEBAS]); const magikarp = game.scene.getEnemyPokemon(); @@ -87,9 +86,10 @@ describe("Abilities - Perish Song", () => { }); it("should activate if cursola already has perish song, but not reset its counter", async () => { - game.override.enemyMoveset([MoveId.PERISH_SONG, MoveId.AQUA_JET, MoveId.SPLASH]); - game.override.moveset([MoveId.WHIRLWIND, MoveId.SPLASH]); - game.override.startingWave(5); + game.override + .enemyMoveset([MoveId.PERISH_SONG, MoveId.AQUA_JET, MoveId.SPLASH]) + .moveset([MoveId.WHIRLWIND, MoveId.SPLASH]) + .startingWave(5); await game.classicMode.startBattle([SpeciesId.CURSOLA]); const cursola = game.scene.getPlayerPokemon(); diff --git a/test/abilities/power_construct.test.ts b/test/abilities/power_construct.test.ts index f8e3de802e6..2ea14597ef9 100644 --- a/test/abilities/power_construct.test.ts +++ b/test/abilities/power_construct.test.ts @@ -35,8 +35,7 @@ describe("Abilities - POWER CONSTRUCT", () => { test("check if fainted 50% Power Construct Pokemon switches to base form on arena reset", async () => { const baseForm = 2, completeForm = 4; - game.override.startingWave(4); - game.override.starterForms({ + game.override.startingWave(4).starterForms({ [SpeciesId.ZYGARDE]: completeForm, }); @@ -62,8 +61,7 @@ describe("Abilities - POWER CONSTRUCT", () => { test("check if fainted 10% Power Construct Pokemon switches to base form on arena reset", async () => { const baseForm = 3, completeForm = 5; - game.override.startingWave(4); - game.override.starterForms({ + game.override.startingWave(4).starterForms({ [SpeciesId.ZYGARDE]: completeForm, }); diff --git a/test/abilities/power_spot.test.ts b/test/abilities/power_spot.test.ts index 5e6cbce7742..11f75588386 100644 --- a/test/abilities/power_spot.test.ts +++ b/test/abilities/power_spot.test.ts @@ -26,11 +26,12 @@ describe("Abilities - Power Spot", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("double"); - game.override.moveset([MoveId.TACKLE, MoveId.BREAKING_SWIPE, MoveId.SPLASH, MoveId.DAZZLING_GLEAM]); - game.override.enemyMoveset(MoveId.SPLASH); - game.override.enemySpecies(SpeciesId.SHUCKLE); - game.override.enemyAbility(AbilityId.BALL_FETCH); + game.override + .battleStyle("double") + .moveset([MoveId.TACKLE, MoveId.BREAKING_SWIPE, MoveId.SPLASH, MoveId.DAZZLING_GLEAM]) + .enemyMoveset(MoveId.SPLASH) + .enemySpecies(SpeciesId.SHUCKLE) + .enemyAbility(AbilityId.BALL_FETCH); }); it("raises the power of allies' special moves by 30%", async () => { diff --git a/test/abilities/protean.test.ts b/test/abilities/protean.test.ts index a0b04fa0be5..09c9addbc35 100644 --- a/test/abilities/protean.test.ts +++ b/test/abilities/protean.test.ts @@ -108,8 +108,7 @@ describe("Abilities - Protean", () => { }); test("ability applies correctly even if the type has changed by another ability", async () => { - game.override.moveset([MoveId.TACKLE]); - game.override.passiveAbility(AbilityId.REFRIGERATE); + game.override.moveset([MoveId.TACKLE]).passiveAbility(AbilityId.REFRIGERATE); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -156,8 +155,7 @@ describe("Abilities - Protean", () => { }); test("ability applies correctly even if the pokemon's move misses", async () => { - game.override.moveset([MoveId.TACKLE]); - game.override.enemyMoveset(MoveId.SPLASH); + game.override.moveset([MoveId.TACKLE]).enemyMoveset(MoveId.SPLASH); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -188,8 +186,7 @@ describe("Abilities - Protean", () => { }); test("ability applies correctly even if the pokemon's move fails because of type immunity", async () => { - game.override.moveset([MoveId.TACKLE]); - game.override.enemySpecies(SpeciesId.GASTLY); + game.override.moveset([MoveId.TACKLE]).enemySpecies(SpeciesId.GASTLY); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -262,8 +259,7 @@ describe("Abilities - Protean", () => { }); test("ability applies correctly even if the pokemon's Trick-or-Treat fails", async () => { - game.override.moveset([MoveId.TRICK_OR_TREAT]); - game.override.enemySpecies(SpeciesId.GASTLY); + game.override.moveset([MoveId.TRICK_OR_TREAT]).enemySpecies(SpeciesId.GASTLY); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); diff --git a/test/abilities/quick_draw.test.ts b/test/abilities/quick_draw.test.ts index 5e5e57fb056..02c2ff86a56 100644 --- a/test/abilities/quick_draw.test.ts +++ b/test/abilities/quick_draw.test.ts @@ -23,16 +23,15 @@ describe("Abilities - Quick Draw", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - - game.override.starterSpecies(SpeciesId.MAGIKARP); - game.override.ability(AbilityId.QUICK_DRAW); - game.override.moveset([MoveId.TACKLE, MoveId.TAIL_WHIP]); - - game.override.enemyLevel(100); - game.override.enemySpecies(SpeciesId.MAGIKARP); - game.override.enemyAbility(AbilityId.BALL_FETCH); - game.override.enemyMoveset([MoveId.TACKLE]); + game.override + .battleStyle("single") + .starterSpecies(SpeciesId.MAGIKARP) + .ability(AbilityId.QUICK_DRAW) + .moveset([MoveId.TACKLE, MoveId.TAIL_WHIP]) + .enemyLevel(100) + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyMoveset([MoveId.TACKLE]); vi.spyOn( allAbilities[AbilityId.QUICK_DRAW].getAttrs("BypassSpeedChanceAbAttr")[0], diff --git a/test/abilities/sand_spit.test.ts b/test/abilities/sand_spit.test.ts index 96e6380ae88..9be23e7f7c2 100644 --- a/test/abilities/sand_spit.test.ts +++ b/test/abilities/sand_spit.test.ts @@ -22,15 +22,14 @@ describe("Abilities - Sand Spit", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.disableCrits(); - - game.override.enemySpecies(SpeciesId.MAGIKARP); - game.override.enemyAbility(AbilityId.BALL_FETCH); - - game.override.starterSpecies(SpeciesId.SILICOBRA); - game.override.ability(AbilityId.SAND_SPIT); - game.override.moveset([MoveId.SPLASH, MoveId.COIL]); + game.override + .battleStyle("single") + .disableCrits() + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.BALL_FETCH) + .starterSpecies(SpeciesId.SILICOBRA) + .ability(AbilityId.SAND_SPIT) + .moveset([MoveId.SPLASH, MoveId.COIL]); }); it("should trigger when hit with damaging move", async () => { diff --git a/test/abilities/schooling.test.ts b/test/abilities/schooling.test.ts index 728e9bb7024..646beafb80f 100644 --- a/test/abilities/schooling.test.ts +++ b/test/abilities/schooling.test.ts @@ -31,8 +31,7 @@ describe("Abilities - SCHOOLING", () => { test("check if fainted pokemon switches to base form on arena reset", async () => { const soloForm = 0, schoolForm = 1; - game.override.startingWave(4); - game.override.starterForms({ + game.override.startingWave(4).starterForms({ [SpeciesId.WISHIWASHI]: schoolForm, }); diff --git a/test/abilities/screen_cleaner.test.ts b/test/abilities/screen_cleaner.test.ts index 619cc08bccf..e896d2e51f9 100644 --- a/test/abilities/screen_cleaner.test.ts +++ b/test/abilities/screen_cleaner.test.ts @@ -24,9 +24,7 @@ describe("Abilities - Screen Cleaner", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.ability(AbilityId.SCREEN_CLEANER); - game.override.enemySpecies(SpeciesId.SHUCKLE); + game.override.battleStyle("single").ability(AbilityId.SCREEN_CLEANER).enemySpecies(SpeciesId.SHUCKLE); }); it("removes Aurora Veil", async () => { diff --git a/test/abilities/seed_sower.test.ts b/test/abilities/seed_sower.test.ts index 761342bfb27..4b649275d83 100644 --- a/test/abilities/seed_sower.test.ts +++ b/test/abilities/seed_sower.test.ts @@ -22,15 +22,14 @@ describe("Abilities - Seed Sower", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.disableCrits(); - - game.override.enemySpecies(SpeciesId.MAGIKARP); - game.override.enemyAbility(AbilityId.BALL_FETCH); - - game.override.starterSpecies(SpeciesId.ARBOLIVA); - game.override.ability(AbilityId.SEED_SOWER); - game.override.moveset([MoveId.SPLASH]); + game.override + .battleStyle("single") + .disableCrits() + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.BALL_FETCH) + .starterSpecies(SpeciesId.ARBOLIVA) + .ability(AbilityId.SEED_SOWER) + .moveset([MoveId.SPLASH]); }); it("should trigger when hit with damaging move", async () => { diff --git a/test/abilities/shield_dust.test.ts b/test/abilities/shield_dust.test.ts index db1266d3088..6bb63fd16a5 100644 --- a/test/abilities/shield_dust.test.ts +++ b/test/abilities/shield_dust.test.ts @@ -26,12 +26,13 @@ describe("Abilities - Shield Dust", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.enemySpecies(SpeciesId.ONIX); - game.override.enemyAbility(AbilityId.SHIELD_DUST); - game.override.startingLevel(100); - game.override.moveset(MoveId.AIR_SLASH); - game.override.enemyMoveset(MoveId.TACKLE); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.ONIX) + .enemyAbility(AbilityId.SHIELD_DUST) + .startingLevel(100) + .moveset(MoveId.AIR_SLASH) + .enemyMoveset(MoveId.TACKLE); }); it("Shield Dust", async () => { diff --git a/test/abilities/shields_down.test.ts b/test/abilities/shields_down.test.ts index 0a7270dff31..7b4803915f1 100644 --- a/test/abilities/shields_down.test.ts +++ b/test/abilities/shields_down.test.ts @@ -26,17 +26,17 @@ describe("Abilities - SHIELDS DOWN", () => { beforeEach(() => { game = new GameManager(phaserGame); const moveToUse = MoveId.SPLASH; - game.override.battleStyle("single"); - game.override.ability(AbilityId.SHIELDS_DOWN); - game.override.moveset([moveToUse]); - game.override.enemyMoveset([MoveId.TACKLE]); + game.override + .battleStyle("single") + .ability(AbilityId.SHIELDS_DOWN) + .moveset([moveToUse]) + .enemyMoveset([MoveId.TACKLE]); }); test("check if fainted pokemon switched to base form on arena reset", async () => { const meteorForm = 0, coreForm = 7; - game.override.startingWave(4); - game.override.starterForms({ + game.override.startingWave(4).starterForms({ [SpeciesId.MINIOR]: coreForm, }); @@ -70,8 +70,7 @@ describe("Abilities - SHIELDS DOWN", () => { }); test("should still ignore non-volatile status moves used by a pokemon with mold breaker", async () => { - game.override.enemyAbility(AbilityId.MOLD_BREAKER); - game.override.enemyMoveset([MoveId.SPORE]); + game.override.enemyAbility(AbilityId.MOLD_BREAKER).enemyMoveset([MoveId.SPORE]); await game.classicMode.startBattle([SpeciesId.MINIOR]); @@ -94,8 +93,7 @@ describe("Abilities - SHIELDS DOWN", () => { }); test("should ignore status moves even through mold breaker", async () => { - game.override.enemyMoveset([MoveId.SPORE]); - game.override.enemyAbility(AbilityId.MOLD_BREAKER); + game.override.enemyMoveset([MoveId.SPORE]).enemyAbility(AbilityId.MOLD_BREAKER); await game.classicMode.startBattle([SpeciesId.MINIOR]); @@ -108,8 +106,9 @@ describe("Abilities - SHIELDS DOWN", () => { // toxic spikes currently does not poison flying types when gravity is in effect test.todo("should become poisoned by toxic spikes when grounded", async () => { - game.override.enemyMoveset([MoveId.GRAVITY, MoveId.TOXIC_SPIKES, MoveId.SPLASH]); - game.override.moveset([MoveId.GRAVITY, MoveId.SPLASH]); + game.override + .enemyMoveset([MoveId.GRAVITY, MoveId.TOXIC_SPIKES, MoveId.SPLASH]) + .moveset([MoveId.GRAVITY, MoveId.SPLASH]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MINIOR]); @@ -155,9 +154,7 @@ describe("Abilities - SHIELDS DOWN", () => { // the `NoTransformAbilityAbAttr` attribute is not checked anywhere, so this test cannot pass. test.todo("ditto should not be immune to status after transforming", async () => { - game.override.enemySpecies(SpeciesId.DITTO); - game.override.enemyAbility(AbilityId.IMPOSTER); - game.override.moveset([MoveId.SPLASH, MoveId.SPORE]); + game.override.enemySpecies(SpeciesId.DITTO).enemyAbility(AbilityId.IMPOSTER).moveset([MoveId.SPLASH, MoveId.SPORE]); await game.classicMode.startBattle([SpeciesId.MINIOR]); @@ -169,11 +166,12 @@ describe("Abilities - SHIELDS DOWN", () => { }); test("should not prevent minior from receiving the fainted status effect in trainer battles", async () => { - game.override.enemyMoveset([MoveId.TACKLE]); - game.override.moveset([MoveId.THUNDERBOLT]); - game.override.startingLevel(100); - game.override.startingWave(5); - game.override.enemySpecies(SpeciesId.MINIOR); + game.override + .enemyMoveset([MoveId.TACKLE]) + .moveset([MoveId.THUNDERBOLT]) + .startingLevel(100) + .startingWave(5) + .enemySpecies(SpeciesId.MINIOR); await game.classicMode.startBattle([SpeciesId.REGIELEKI]); const minior = game.scene.getEnemyPokemon()!; diff --git a/test/abilities/steely_spirit.test.ts b/test/abilities/steely_spirit.test.ts index 481e037fb4b..7a2bd98b6b1 100644 --- a/test/abilities/steely_spirit.test.ts +++ b/test/abilities/steely_spirit.test.ts @@ -28,11 +28,12 @@ describe("Abilities - Steely Spirit", () => { beforeEach(() => { ironHeadPower = allMoves[moveToCheck].power; game = new GameManager(phaserGame); - game.override.battleStyle("double"); - game.override.enemySpecies(SpeciesId.SHUCKLE); - game.override.enemyAbility(AbilityId.BALL_FETCH); - game.override.moveset([MoveId.IRON_HEAD, MoveId.SPLASH]); - game.override.enemyMoveset(MoveId.SPLASH); + game.override + .battleStyle("double") + .enemySpecies(SpeciesId.SHUCKLE) + .enemyAbility(AbilityId.BALL_FETCH) + .moveset([MoveId.IRON_HEAD, MoveId.SPLASH]) + .enemyMoveset(MoveId.SPLASH); vi.spyOn(allMoves[moveToCheck], "calculateBattlePower"); }); diff --git a/test/abilities/sturdy.test.ts b/test/abilities/sturdy.test.ts index a50e2075fea..e7b2c05040d 100644 --- a/test/abilities/sturdy.test.ts +++ b/test/abilities/sturdy.test.ts @@ -24,15 +24,14 @@ describe("Abilities - Sturdy", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - - game.override.starterSpecies(SpeciesId.LUCARIO); - game.override.startingLevel(100); - game.override.moveset([MoveId.CLOSE_COMBAT, MoveId.FISSURE]); - - game.override.enemySpecies(SpeciesId.ARON); - game.override.enemyLevel(5); - game.override.enemyAbility(AbilityId.STURDY); + game.override + .battleStyle("single") + .starterSpecies(SpeciesId.LUCARIO) + .startingLevel(100) + .moveset([MoveId.CLOSE_COMBAT, MoveId.FISSURE]) + .enemySpecies(SpeciesId.ARON) + .enemyLevel(5) + .enemyAbility(AbilityId.STURDY); }); test("Sturdy activates when user is at full HP", async () => { diff --git a/test/abilities/sweet_veil.test.ts b/test/abilities/sweet_veil.test.ts index 8a31e676ec5..ed9cb20afcc 100644 --- a/test/abilities/sweet_veil.test.ts +++ b/test/abilities/sweet_veil.test.ts @@ -69,10 +69,7 @@ describe("Abilities - Sweet Veil", () => { }); it("prevents the user and its allies already drowsy due to Yawn from falling asleep.", async () => { - game.override.enemySpecies(SpeciesId.PIKACHU); - game.override.enemyLevel(5); - game.override.startingLevel(5); - game.override.enemyMoveset(MoveId.SPLASH); + game.override.enemySpecies(SpeciesId.PIKACHU).enemyLevel(5).startingLevel(5).enemyMoveset(MoveId.SPLASH); await game.classicMode.startBattle([SpeciesId.SHUCKLE, SpeciesId.SHUCKLE, SpeciesId.SWIRLIX]); diff --git a/test/abilities/unseen_fist.test.ts b/test/abilities/unseen_fist.test.ts index 56408c6cbc3..26de77d4643 100644 --- a/test/abilities/unseen_fist.test.ts +++ b/test/abilities/unseen_fist.test.ts @@ -51,8 +51,7 @@ describe("Abilities - Unseen Fist", () => { await testUnseenFistHitResult(game, MoveId.BULLDOZE, MoveId.WIDE_GUARD, false)); it("should cause a contact move to ignore Protect, but not Substitute", async () => { - game.override.enemyLevel(1); - game.override.moveset([MoveId.TACKLE]); + game.override.enemyLevel(1).moveset([MoveId.TACKLE]); await game.classicMode.startBattle(); diff --git a/test/abilities/volt_absorb.test.ts b/test/abilities/volt_absorb.test.ts index 34dbbbd4783..707a889f951 100644 --- a/test/abilities/volt_absorb.test.ts +++ b/test/abilities/volt_absorb.test.ts @@ -3,11 +3,11 @@ import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#enums/battler-tag-type"; import { MoveId } from "#enums/move-id"; -import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { BattlerIndex } from "#enums/battler-index"; +import { SpeciesId } from "#enums/species-id"; // See also: TypeImmunityAbAttr describe("Abilities - Volt Absorb", () => { @@ -26,19 +26,19 @@ describe("Abilities - Volt Absorb", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.disableCrits(); + game.override.battleStyle("single").disableCrits(); }); it("does not activate when CHARGE is used", async () => { const moveToUse = MoveId.CHARGE; const ability = AbilityId.VOLT_ABSORB; - game.override.moveset([moveToUse]); - game.override.ability(ability); - game.override.enemyMoveset([MoveId.SPLASH, MoveId.NONE, MoveId.NONE, MoveId.NONE]); - game.override.enemySpecies(SpeciesId.DUSKULL); - game.override.enemyAbility(AbilityId.BALL_FETCH); + game.override + .moveset([moveToUse]) + .ability(ability) + .enemyMoveset([MoveId.SPLASH]) + .enemySpecies(SpeciesId.DUSKULL) + .enemyAbility(AbilityId.BALL_FETCH); await game.classicMode.startBattle(); @@ -54,10 +54,11 @@ describe("Abilities - Volt Absorb", () => { }); it("should activate regardless of accuracy checks", async () => { - game.override.moveset(MoveId.THUNDERBOLT); - game.override.enemyMoveset(MoveId.SPLASH); - game.override.enemySpecies(SpeciesId.MAGIKARP); - game.override.enemyAbility(AbilityId.VOLT_ABSORB); + game.override + .moveset(MoveId.THUNDERBOLT) + .enemyMoveset(MoveId.SPLASH) + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.VOLT_ABSORB); await game.classicMode.startBattle(); @@ -74,10 +75,11 @@ describe("Abilities - Volt Absorb", () => { }); it("regardless of accuracy should not trigger on pokemon in semi invulnerable state", async () => { - game.override.moveset(MoveId.THUNDERBOLT); - game.override.enemyMoveset(MoveId.DIVE); - game.override.enemySpecies(SpeciesId.MAGIKARP); - game.override.enemyAbility(AbilityId.VOLT_ABSORB); + game.override + .moveset(MoveId.THUNDERBOLT) + .enemyMoveset(MoveId.DIVE) + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.VOLT_ABSORB); await game.classicMode.startBattle(); diff --git a/test/abilities/wind_power.test.ts b/test/abilities/wind_power.test.ts index a7b4d525a56..8e657997008 100644 --- a/test/abilities/wind_power.test.ts +++ b/test/abilities/wind_power.test.ts @@ -23,14 +23,15 @@ describe("Abilities - Wind Power", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.enemySpecies(SpeciesId.SHIFTRY); - game.override.enemyAbility(AbilityId.WIND_POWER); - game.override.moveset([MoveId.TAILWIND, MoveId.SPLASH, MoveId.PETAL_BLIZZARD, MoveId.SANDSTORM]); - game.override.enemyMoveset(MoveId.SPLASH); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.SHIFTRY) + .enemyAbility(AbilityId.WIND_POWER) + .moveset([MoveId.TAILWIND, MoveId.SPLASH, MoveId.PETAL_BLIZZARD, MoveId.SANDSTORM]) + .enemyMoveset(MoveId.SPLASH); }); - it("it becomes charged when hit by wind moves", async () => { + it("becomes charged when hit by wind moves", async () => { await game.classicMode.startBattle([SpeciesId.MAGIKARP]); const shiftry = game.scene.getEnemyPokemon()!; @@ -42,9 +43,8 @@ describe("Abilities - Wind Power", () => { expect(shiftry.getTag(BattlerTagType.CHARGED)).toBeDefined(); }); - it("it becomes charged when Tailwind takes effect on its side", async () => { - game.override.ability(AbilityId.WIND_POWER); - game.override.enemySpecies(SpeciesId.MAGIKARP); + it("becomes charged when Tailwind takes effect on its side", async () => { + game.override.ability(AbilityId.WIND_POWER).enemySpecies(SpeciesId.MAGIKARP); await game.classicMode.startBattle([SpeciesId.SHIFTRY]); const shiftry = game.scene.getPlayerPokemon()!; @@ -58,8 +58,7 @@ describe("Abilities - Wind Power", () => { }); it("does not become charged when Tailwind takes effect on opposing side", async () => { - game.override.enemySpecies(SpeciesId.MAGIKARP); - game.override.ability(AbilityId.WIND_POWER); + game.override.enemySpecies(SpeciesId.MAGIKARP).ability(AbilityId.WIND_POWER); await game.classicMode.startBattle([SpeciesId.SHIFTRY]); const magikarp = game.scene.getEnemyPokemon()!; diff --git a/test/abilities/wonder_skin.test.ts b/test/abilities/wonder_skin.test.ts index 933d3653580..886882ab6fd 100644 --- a/test/abilities/wonder_skin.test.ts +++ b/test/abilities/wonder_skin.test.ts @@ -23,12 +23,13 @@ describe("Abilities - Wonder Skin", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.moveset([MoveId.TACKLE, MoveId.CHARM]); - game.override.ability(AbilityId.BALL_FETCH); - game.override.enemySpecies(SpeciesId.SHUCKLE); - game.override.enemyAbility(AbilityId.WONDER_SKIN); - game.override.enemyMoveset(MoveId.SPLASH); + game.override + .battleStyle("single") + .moveset([MoveId.TACKLE, MoveId.CHARM]) + .ability(AbilityId.BALL_FETCH) + .enemySpecies(SpeciesId.SHUCKLE) + .enemyAbility(AbilityId.WONDER_SKIN) + .enemyMoveset(MoveId.SPLASH); }); it("lowers accuracy of status moves to 50%", async () => { diff --git a/test/abilities/zen_mode.test.ts b/test/abilities/zen_mode.test.ts index 163df7b1853..70d2f564218 100644 --- a/test/abilities/zen_mode.test.ts +++ b/test/abilities/zen_mode.test.ts @@ -85,8 +85,7 @@ describe("Abilities - ZEN MODE", () => { }); it("should switch to base form on arena reset", async () => { - game.override.startingWave(4); - game.override.starterForms({ + game.override.startingWave(4).starterForms({ [SpeciesId.DARMANITAN]: zenForm, }); diff --git a/test/abilities/zero_to_hero.test.ts b/test/abilities/zero_to_hero.test.ts index 90e10c89ca1..7d0128a4dbc 100644 --- a/test/abilities/zero_to_hero.test.ts +++ b/test/abilities/zero_to_hero.test.ts @@ -34,8 +34,7 @@ describe("Abilities - ZERO TO HERO", () => { }); it("should swap to base form on arena reset", async () => { - game.override.startingWave(4); - game.override.starterForms({ + game.override.startingWave(4).starterForms({ [SpeciesId.PALAFIN]: heroForm, }); diff --git a/test/achievements/achievement.test.ts b/test/achievements/achievement.test.ts index 0b49c4d23ab..5a74c77fe6a 100644 --- a/test/achievements/achievement.test.ts +++ b/test/achievements/achievement.test.ts @@ -130,12 +130,6 @@ describe("RibbonAchv", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.moveset([]); - game.override.startingLevel(0); - game.override.starterSpecies(0); - game.override.enemyMoveset([]); - game.override.enemySpecies(0); - game.override.startingWave(0); scene = game.scene; }); diff --git a/test/arena/weather_fog.test.ts b/test/arena/weather_fog.test.ts index 24c07d26cba..e6984e13bd0 100644 --- a/test/arena/weather_fog.test.ts +++ b/test/arena/weather_fog.test.ts @@ -24,12 +24,14 @@ describe("Weather - Fog", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.weather(WeatherType.FOG).battleStyle("single"); - game.override.moveset([MoveId.TACKLE]); - game.override.ability(AbilityId.BALL_FETCH); - game.override.enemyAbility(AbilityId.BALL_FETCH); - game.override.enemySpecies(SpeciesId.MAGIKARP); - game.override.enemyMoveset([MoveId.SPLASH]); + game.override + .weather(WeatherType.FOG) + .battleStyle("single") + .moveset([MoveId.TACKLE]) + .ability(AbilityId.BALL_FETCH) + .enemyAbility(AbilityId.BALL_FETCH) + .enemySpecies(SpeciesId.MAGIKARP) + .enemyMoveset([MoveId.SPLASH]); }); it("move accuracy is multiplied by 90%", async () => { diff --git a/test/arena/weather_strong_winds.test.ts b/test/arena/weather_strong_winds.test.ts index 8b3c2562e6f..d0d256816eb 100644 --- a/test/arena/weather_strong_winds.test.ts +++ b/test/arena/weather_strong_winds.test.ts @@ -24,11 +24,12 @@ describe("Weather - Strong Winds", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.startingLevel(10); - game.override.enemySpecies(SpeciesId.TAILLOW); - game.override.enemyAbility(AbilityId.DELTA_STREAM); - game.override.moveset([MoveId.THUNDERBOLT, MoveId.ICE_BEAM, MoveId.ROCK_SLIDE]); + game.override + .battleStyle("single") + .startingLevel(10) + .enemySpecies(SpeciesId.TAILLOW) + .enemyAbility(AbilityId.DELTA_STREAM) + .moveset([MoveId.THUNDERBOLT, MoveId.ICE_BEAM, MoveId.ROCK_SLIDE]); }); it("electric type move is not very effective on Rayquaza", async () => { diff --git a/test/battle/battle-order.test.ts b/test/battle/battle-order.test.ts index c12760a7f30..c969e1767a3 100644 --- a/test/battle/battle-order.test.ts +++ b/test/battle/battle-order.test.ts @@ -24,11 +24,12 @@ describe("Battle order", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.enemySpecies(SpeciesId.MEWTWO); - game.override.enemyAbility(AbilityId.INSOMNIA); - game.override.ability(AbilityId.INSOMNIA); - game.override.moveset([MoveId.TACKLE]); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.MEWTWO) + .enemyAbility(AbilityId.INSOMNIA) + .ability(AbilityId.INSOMNIA) + .moveset([MoveId.TACKLE]); }); it("opponent faster than player 50 vs 150", async () => { diff --git a/test/battle/battle.test.ts b/test/battle/battle.test.ts index 3eab06e2f48..60e09607ffe 100644 --- a/test/battle/battle.test.ts +++ b/test/battle/battle.test.ts @@ -10,13 +10,11 @@ import { EnemyCommandPhase } from "#app/phases/enemy-command-phase"; import { LoginPhase } from "#app/phases/login-phase"; import { NextEncounterPhase } from "#app/phases/next-encounter-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 { SummonPhase } from "#app/phases/summon-phase"; import { SwitchPhase } from "#app/phases/switch-phase"; import { TitlePhase } from "#app/phases/title-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; -import { VictoryPhase } from "#app/phases/victory-phase"; import GameManager from "#test/testUtils/gameManager"; import { generateStarter } from "#test/testUtils/gameManagerUtils"; import { UiMode } from "#enums/ui-mode"; @@ -92,28 +90,22 @@ describe("Test Battle Phase", () => { }, 20000); it("do attack wave 3 - single battle - regular - OHKO", async () => { - game.override.starterSpecies(SpeciesId.MEWTWO); - game.override.enemySpecies(SpeciesId.RATTATA); - game.override.startingLevel(2000); - game.override.startingWave(3).battleStyle("single"); - game.override.moveset([MoveId.TACKLE]); - game.override.enemyAbility(AbilityId.HYDRATION); - game.override.enemyMoveset([MoveId.TACKLE, MoveId.TACKLE, MoveId.TACKLE, MoveId.TACKLE]); - await game.classicMode.startBattle(); - game.move.select(MoveId.TACKLE); - await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(SelectModifierPhase, false); + game.override.enemySpecies(SpeciesId.RATTATA).startingLevel(2000).battleStyle("single"); + await game.classicMode.startBattle([SpeciesId.MEWTWO]); + game.move.use(MoveId.TACKLE); + await game.phaseInterceptor.to("SelectModifierPhase"); }, 20000); it("do attack wave 3 - single battle - regular - NO OHKO with opponent using non damage attack", async () => { - game.override.starterSpecies(SpeciesId.MEWTWO); - game.override.enemySpecies(SpeciesId.RATTATA); - game.override.startingLevel(5); - game.override.startingWave(3); - game.override.moveset([MoveId.TACKLE]); - game.override.enemyAbility(AbilityId.HYDRATION); - game.override.enemyMoveset([MoveId.TAIL_WHIP, MoveId.TAIL_WHIP, MoveId.TAIL_WHIP, MoveId.TAIL_WHIP]); - game.override.battleStyle("single"); - await game.classicMode.startBattle(); + game.override + .enemySpecies(SpeciesId.RATTATA) + .startingLevel(5) + .startingWave(3) + .moveset([MoveId.TACKLE]) + .enemyAbility(AbilityId.HYDRATION) + .enemyMoveset([MoveId.TAIL_WHIP, MoveId.TAIL_WHIP, MoveId.TAIL_WHIP, MoveId.TAIL_WHIP]) + .battleStyle("single"); + await game.classicMode.startBattle([SpeciesId.MEWTWO]); game.move.select(MoveId.TACKLE); await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase, false); }, 20000); @@ -247,36 +239,37 @@ describe("Test Battle Phase", () => { it("kill opponent pokemon", async () => { const moveToUse = MoveId.SPLASH; - game.override.battleStyle("single"); - game.override.starterSpecies(SpeciesId.MEWTWO); - game.override.enemySpecies(SpeciesId.RATTATA); - game.override.enemyAbility(AbilityId.HYDRATION); - game.override.ability(AbilityId.ZEN_MODE); - game.override.startingLevel(2000); - game.override.startingWave(3); - game.override.moveset([moveToUse]); - game.override.enemyMoveset([MoveId.TACKLE, MoveId.TACKLE, MoveId.TACKLE, MoveId.TACKLE]); + game.override + .battleStyle("single") + .starterSpecies(SpeciesId.MEWTWO) + .enemySpecies(SpeciesId.RATTATA) + .enemyAbility(AbilityId.HYDRATION) + .ability(AbilityId.ZEN_MODE) + .startingLevel(2000) + .startingWave(3) + .moveset([moveToUse]) + .enemyMoveset([MoveId.TACKLE, MoveId.TACKLE, MoveId.TACKLE, MoveId.TACKLE]); await game.classicMode.startBattle([SpeciesId.DARMANITAN, SpeciesId.CHARIZARD]); game.move.select(moveToUse); await game.phaseInterceptor.to(DamageAnimPhase, false); await game.killPokemon(game.scene.currentBattle.enemyParty[0]); expect(game.scene.currentBattle.enemyParty[0].isFainted()).toBe(true); - await game.phaseInterceptor.to(VictoryPhase, false); - }, 200000); + await game.phaseInterceptor.to("VictoryPhase"); + }); it("to next turn", async () => { const moveToUse = MoveId.SPLASH; - game.override.battleStyle("single"); - game.override.starterSpecies(SpeciesId.MEWTWO); - game.override.enemySpecies(SpeciesId.RATTATA); - game.override.enemyAbility(AbilityId.HYDRATION); - game.override.ability(AbilityId.ZEN_MODE); - game.override.startingLevel(2000); - game.override.startingWave(3); - game.override.moveset([moveToUse]); - game.override.enemyMoveset([MoveId.TACKLE, MoveId.TACKLE, MoveId.TACKLE, MoveId.TACKLE]); - await game.classicMode.startBattle(); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.RATTATA) + .enemyAbility(AbilityId.HYDRATION) + .ability(AbilityId.ZEN_MODE) + .startingLevel(2000) + .startingWave(3) + .moveset([moveToUse]) + .enemyMoveset([MoveId.TACKLE, MoveId.TACKLE, MoveId.TACKLE, MoveId.TACKLE]); + await game.classicMode.startBattle([SpeciesId.MEWTWO]); const turn = game.scene.currentBattle.turn; game.move.select(moveToUse); await game.toNextTurn(); @@ -294,8 +287,8 @@ describe("Test Battle Phase", () => { .startingLevel(2000) .startingWave(3) .startingBiome(BiomeId.LAKE) - .moveset([moveToUse]); - game.override.enemyMoveset([MoveId.TACKLE, MoveId.TACKLE, MoveId.TACKLE, MoveId.TACKLE]); + .moveset([moveToUse]) + .enemyMoveset([MoveId.TACKLE, MoveId.TACKLE, MoveId.TACKLE, MoveId.TACKLE]); await game.classicMode.startBattle(); const waveIndex = game.scene.currentBattle.waveIndex; game.move.select(moveToUse); diff --git a/test/escape-calculations.test.ts b/test/escape-calculations.test.ts index ca51f40a509..8a81bf2c6b8 100644 --- a/test/escape-calculations.test.ts +++ b/test/escape-calculations.test.ts @@ -262,8 +262,7 @@ describe("Escape chance calculations", () => { }, 20000); it("double boss opponent", async () => { - game.override.battleStyle("double"); - game.override.startingWave(10); + game.override.battleStyle("double").startingWave(10); await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.ABOMASNOW]); const playerPokemon = game.scene.getPlayerField(); diff --git a/test/evolution.test.ts b/test/evolution.test.ts index b2e7a9243d0..c4bcca785c1 100644 --- a/test/evolution.test.ts +++ b/test/evolution.test.ts @@ -28,12 +28,11 @@ describe("Evolution", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - - game.override.enemySpecies(SpeciesId.MAGIKARP); - game.override.enemyAbility(AbilityId.BALL_FETCH); - - game.override.startingLevel(60); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.BALL_FETCH) + .startingLevel(60); }); it("should keep hidden ability after evolving", async () => { diff --git a/test/field/pokemon.test.ts b/test/field/pokemon.test.ts index 74ad973b51d..774d46b18fe 100644 --- a/test/field/pokemon.test.ts +++ b/test/field/pokemon.test.ts @@ -88,9 +88,7 @@ describe("Spec - Pokemon", () => { let scene: BattleScene; beforeEach(async () => { - game.override.enemySpecies(SpeciesId.ZUBAT); - game.override.starterSpecies(SpeciesId.ABRA); - game.override.enableStarterFusion(); + game.override.enemySpecies(SpeciesId.ZUBAT).starterSpecies(SpeciesId.ABRA).enableStarterFusion(); scene = game.scene; }); @@ -146,8 +144,7 @@ describe("Spec - Pokemon", () => { }); it("Fusing mons with one and two types", async () => { - game.override.starterSpecies(SpeciesId.CHARMANDER); - game.override.starterFusionSpecies(SpeciesId.HOUNDOUR); + game.override.starterSpecies(SpeciesId.CHARMANDER).starterFusionSpecies(SpeciesId.HOUNDOUR); await game.classicMode.startBattle(); const pokemon = scene.getPlayerParty()[0]; @@ -157,8 +154,7 @@ describe("Spec - Pokemon", () => { }); it("Fusing mons with two and one types", async () => { - game.override.starterSpecies(SpeciesId.NUMEL); - game.override.starterFusionSpecies(SpeciesId.CHARMANDER); + game.override.starterSpecies(SpeciesId.NUMEL).starterFusionSpecies(SpeciesId.CHARMANDER); await game.classicMode.startBattle(); const pokemon = scene.getPlayerParty()[0]; @@ -168,8 +164,7 @@ describe("Spec - Pokemon", () => { }); it("Fusing two mons with two types", async () => { - game.override.starterSpecies(SpeciesId.NATU); - game.override.starterFusionSpecies(SpeciesId.HOUNDOUR); + game.override.starterSpecies(SpeciesId.NATU).starterFusionSpecies(SpeciesId.HOUNDOUR); await game.classicMode.startBattle(); const pokemon = scene.getPlayerParty()[0]; diff --git a/test/items/exp_booster.test.ts b/test/items/exp_booster.test.ts index f5273a78a55..96a8571785e 100644 --- a/test/items/exp_booster.test.ts +++ b/test/items/exp_booster.test.ts @@ -22,9 +22,7 @@ describe("EXP Modifier Items", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.enemyAbility(AbilityId.BALL_FETCH); - game.override.ability(AbilityId.BALL_FETCH); - game.override.battleStyle("single"); + game.override.enemyAbility(AbilityId.BALL_FETCH).ability(AbilityId.BALL_FETCH).battleStyle("single"); }); it("EXP booster items stack multiplicatively", async () => { diff --git a/test/moves/baneful_bunker.test.ts b/test/moves/baneful_bunker.test.ts index eddfa87ead4..c9cc2025a58 100644 --- a/test/moves/baneful_bunker.test.ts +++ b/test/moves/baneful_bunker.test.ts @@ -24,16 +24,14 @@ describe("Moves - Baneful Bunker", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - - game.override.moveset(MoveId.SLASH); - - game.override.enemySpecies(SpeciesId.SNORLAX); - game.override.enemyAbility(AbilityId.INSOMNIA); - game.override.enemyMoveset(MoveId.BANEFUL_BUNKER); - - game.override.startingLevel(100); - game.override.enemyLevel(100); + game.override + .battleStyle("single") + .moveset(MoveId.SLASH) + .enemySpecies(SpeciesId.SNORLAX) + .enemyAbility(AbilityId.INSOMNIA) + .enemyMoveset(MoveId.BANEFUL_BUNKER) + .startingLevel(100) + .enemyLevel(100); }); test("should protect the user and poison attackers that make contact", async () => { await game.classicMode.startBattle([SpeciesId.CHARIZARD]); diff --git a/test/moves/baton_pass.test.ts b/test/moves/baton_pass.test.ts index f6256e95ce8..42e46352a11 100644 --- a/test/moves/baton_pass.test.ts +++ b/test/moves/baton_pass.test.ts @@ -59,28 +59,23 @@ describe("Moves - Baton Pass", () => { it("passes stat stage buffs when AI uses it", async () => { // arrange - game.override.startingWave(5).enemyMoveset(new Array(4).fill([MoveId.NASTY_PLOT])); + game.override.startingWave(5).enemyMoveset([MoveId.NASTY_PLOT, MoveId.BATON_PASS]); await game.classicMode.startBattle([SpeciesId.RAICHU, SpeciesId.SHUCKLE]); // round 1 - ai buffs game.move.select(MoveId.SPLASH); + await game.move.forceEnemyMove(MoveId.NASTY_PLOT); await game.toNextTurn(); // round 2 - baton pass - game.scene.getEnemyPokemon()!.hp = 100; - game.override.enemyMoveset([MoveId.BATON_PASS]); - // Force moveset to update mid-battle - // TODO: replace with enemy ai control function when it's added - game.scene.getEnemyParty()[0].getMoveset(); game.move.select(MoveId.SPLASH); + await game.move.forceEnemyMove(MoveId.BATON_PASS); await game.phaseInterceptor.to("PostSummonPhase", false); - // assert // check buffs are still there - expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.SPATK)).toEqual(2); + expect(game.scene.getEnemyPokemon()?.getStatStage(Stat.SPATK)).toEqual(2); // confirm that a switch actually happened. can't use species because I // can't find a way to override trainer parties with more than 1 pokemon species - expect(game.scene.getEnemyPokemon()!.hp).not.toEqual(100); expect(game.phaseInterceptor.log.slice(-4)).toEqual([ "MoveEffectPhase", "SwitchSummonPhase", diff --git a/test/moves/beak_blast.test.ts b/test/moves/beak_blast.test.ts index ad2959e5101..2cb9f9bdd6f 100644 --- a/test/moves/beak_blast.test.ts +++ b/test/moves/beak_blast.test.ts @@ -129,8 +129,7 @@ describe("Moves - Beak Blast", () => { }); it("should not burn a long reach enemy that hits the user with a contact move", async () => { - game.override.enemyAbility(AbilityId.LONG_REACH); - game.override.enemyMoveset([MoveId.FALSE_SWIPE]).enemyLevel(100); + game.override.enemyAbility(AbilityId.LONG_REACH).enemyMoveset([MoveId.FALSE_SWIPE]).enemyLevel(100); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); game.move.select(MoveId.BEAK_BLAST); await game.phaseInterceptor.to("BerryPhase", false); diff --git a/test/moves/beat_up.test.ts b/test/moves/beat_up.test.ts index 184204a91aa..7c4686ab4fa 100644 --- a/test/moves/beat_up.test.ts +++ b/test/moves/beat_up.test.ts @@ -23,15 +23,14 @@ describe("Moves - Beat Up", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - - game.override.enemySpecies(SpeciesId.SNORLAX); - game.override.enemyLevel(100); - game.override.enemyMoveset([MoveId.SPLASH]); - game.override.enemyAbility(AbilityId.INSOMNIA); - - game.override.startingLevel(100); - game.override.moveset([MoveId.BEAT_UP]); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.SNORLAX) + .enemyLevel(100) + .enemyMoveset([MoveId.SPLASH]) + .enemyAbility(AbilityId.INSOMNIA) + .startingLevel(100) + .moveset([MoveId.BEAT_UP]); }); it("should hit once for each healthy player Pokemon", async () => { diff --git a/test/moves/ceaseless_edge.test.ts b/test/moves/ceaseless_edge.test.ts index fcaddfb0d76..1dec98fe3a8 100644 --- a/test/moves/ceaseless_edge.test.ts +++ b/test/moves/ceaseless_edge.test.ts @@ -27,14 +27,15 @@ describe("Moves - Ceaseless Edge", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.enemySpecies(SpeciesId.RATTATA); - game.override.enemyAbility(AbilityId.RUN_AWAY); - game.override.enemyPassiveAbility(AbilityId.RUN_AWAY); - game.override.startingLevel(100); - game.override.enemyLevel(100); - game.override.moveset([MoveId.CEASELESS_EDGE, MoveId.SPLASH, MoveId.ROAR]); - game.override.enemyMoveset(MoveId.SPLASH); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.RATTATA) + .enemyAbility(AbilityId.RUN_AWAY) + .enemyPassiveAbility(AbilityId.RUN_AWAY) + .startingLevel(100) + .enemyLevel(100) + .moveset([MoveId.CEASELESS_EDGE, MoveId.SPLASH, MoveId.ROAR]) + .enemyMoveset(MoveId.SPLASH); vi.spyOn(allMoves[MoveId.CEASELESS_EDGE], "accuracy", "get").mockReturnValue(100); }); @@ -82,8 +83,7 @@ describe("Moves - Ceaseless Edge", () => { }); test("trainer - move should hit twice, apply two layers of spikes, force switch opponent - opponent takes damage", async () => { - game.override.startingHeldItems([{ name: "MULTI_LENS" }]); - game.override.startingWave(25); + game.override.startingHeldItems([{ name: "MULTI_LENS" }]).startingWave(25); await game.classicMode.startBattle([SpeciesId.ILLUMISE]); diff --git a/test/moves/clangorous_soul.test.ts b/test/moves/clangorous_soul.test.ts index 6f4c18852b0..f08503acdc6 100644 --- a/test/moves/clangorous_soul.test.ts +++ b/test/moves/clangorous_soul.test.ts @@ -27,12 +27,13 @@ describe("Moves - Clangorous Soul", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.starterSpecies(SpeciesId.MAGIKARP); - game.override.enemySpecies(SpeciesId.SNORLAX); - game.override.startingLevel(100); - game.override.enemyLevel(100); - game.override.moveset([MoveId.CLANGOROUS_SOUL]); - game.override.enemyMoveset(MoveId.SPLASH); + game.override + .starterSpecies(SpeciesId.MAGIKARP) + .enemySpecies(SpeciesId.SNORLAX) + .startingLevel(100) + .enemyLevel(100) + .moveset([MoveId.CLANGOROUS_SOUL]) + .enemyMoveset(MoveId.SPLASH); }); //Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/Clangorous_Soul_(move) diff --git a/test/moves/crafty_shield.test.ts b/test/moves/crafty_shield.test.ts index 01ab0416e5a..12a607a2a1b 100644 --- a/test/moves/crafty_shield.test.ts +++ b/test/moves/crafty_shield.test.ts @@ -26,16 +26,14 @@ describe("Moves - Crafty Shield", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("double"); - - game.override.moveset([MoveId.CRAFTY_SHIELD, MoveId.SPLASH, MoveId.SWORDS_DANCE]); - - game.override.enemySpecies(SpeciesId.SNORLAX); - game.override.enemyMoveset([MoveId.GROWL]); - game.override.enemyAbility(AbilityId.INSOMNIA); - - game.override.startingLevel(100); - game.override.enemyLevel(100); + game.override + .battleStyle("double") + .moveset([MoveId.CRAFTY_SHIELD, MoveId.SPLASH, MoveId.SWORDS_DANCE]) + .enemySpecies(SpeciesId.SNORLAX) + .enemyMoveset([MoveId.GROWL]) + .enemyAbility(AbilityId.INSOMNIA) + .startingLevel(100) + .enemyLevel(100); }); test("should protect the user and allies from status moves", async () => { @@ -73,8 +71,7 @@ describe("Moves - Crafty Shield", () => { }); test("should protect the user and allies from moves that ignore other protection", async () => { - game.override.enemySpecies(SpeciesId.DUSCLOPS); - game.override.enemyMoveset([MoveId.CURSE]); + game.override.enemySpecies(SpeciesId.DUSCLOPS).enemyMoveset([MoveId.CURSE]); await game.classicMode.startBattle([SpeciesId.CHARIZARD, SpeciesId.BLASTOISE]); diff --git a/test/moves/dragon_rage.test.ts b/test/moves/dragon_rage.test.ts index c2a0a1bc979..f0be6748c7f 100644 --- a/test/moves/dragon_rage.test.ts +++ b/test/moves/dragon_rage.test.ts @@ -1,6 +1,5 @@ import { Stat } from "#enums/stat"; import { PokemonType } from "#enums/pokemon-type"; -import { SpeciesId } from "#enums/species-id"; import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { AbilityId } from "#enums/ability-id"; @@ -9,6 +8,7 @@ import { MoveId } from "#enums/move-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { SpeciesId } from "#enums/species-id"; describe("Moves - Dragon Rage", () => { let phaserGame: Phaser.Game; @@ -31,19 +31,18 @@ describe("Moves - Dragon Rage", () => { beforeEach(async () => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - - game.override.starterSpecies(SpeciesId.SNORLAX); - game.override.moveset([MoveId.DRAGON_RAGE]); - game.override.ability(AbilityId.BALL_FETCH); - game.override.passiveAbility(AbilityId.BALL_FETCH); - game.override.startingLevel(100); - - game.override.enemySpecies(SpeciesId.SNORLAX); - game.override.enemyMoveset(MoveId.SPLASH); - game.override.enemyAbility(AbilityId.BALL_FETCH); - game.override.enemyPassiveAbility(AbilityId.BALL_FETCH); - game.override.enemyLevel(100); + game.override + .battleStyle("single") + .starterSpecies(SpeciesId.SNORLAX) + .moveset([MoveId.DRAGON_RAGE]) + .ability(AbilityId.BALL_FETCH) + .passiveAbility(AbilityId.BALL_FETCH) + .startingLevel(100) + .enemySpecies(SpeciesId.SNORLAX) + .enemyMoveset(MoveId.SPLASH) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyPassiveAbility(AbilityId.BALL_FETCH) + .enemyLevel(100); await game.classicMode.startBattle(); @@ -101,8 +100,7 @@ describe("Moves - Dragon Rage", () => { }); it("ignores damage modification from abilities, for example ICE_SCALES", async () => { - game.override.disableCrits(); - game.override.enemyAbility(AbilityId.ICE_SCALES); + game.override.disableCrits().enemyAbility(AbilityId.ICE_SCALES); game.move.select(MoveId.DRAGON_RAGE); await game.phaseInterceptor.to(TurnEndPhase); diff --git a/test/moves/dynamax_cannon.test.ts b/test/moves/dynamax_cannon.test.ts index 2cdba48c0ea..c0b6d9e0b40 100644 --- a/test/moves/dynamax_cannon.test.ts +++ b/test/moves/dynamax_cannon.test.ts @@ -38,15 +38,7 @@ describe("Moves - Dynamax Cannon", () => { .enemySpecies(SpeciesId.MAGIKARP) .enemyMoveset(MoveId.SPLASH); - // Note that, for Waves 1-10, the level cap is 10 - game.override.startingWave(1); - game.override.battleStyle("single"); - game.override.disableCrits(); - - game.override.enemySpecies(SpeciesId.MAGIKARP); - game.override.enemyMoveset([MoveId.SPLASH, MoveId.SPLASH, MoveId.SPLASH, MoveId.SPLASH]); - - vi.spyOn(dynamaxCannon, "calculateBattlePower"); + vi.spyOn(allMoves[MoveId.DYNAMAX_CANNON], "calculateBattlePower"); }); it("should return 100 power against an enemy below level cap", async () => { diff --git a/test/moves/fillet_away.test.ts b/test/moves/fillet_away.test.ts index a24b5c4ae04..1e00f2ee02d 100644 --- a/test/moves/fillet_away.test.ts +++ b/test/moves/fillet_away.test.ts @@ -28,12 +28,13 @@ describe("Moves - FILLET AWAY", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.starterSpecies(SpeciesId.MAGIKARP); - game.override.enemySpecies(SpeciesId.SNORLAX); - game.override.startingLevel(100); - game.override.enemyLevel(100); - game.override.moveset([MoveId.FILLET_AWAY]); - game.override.enemyMoveset(MoveId.SPLASH); + game.override + .starterSpecies(SpeciesId.MAGIKARP) + .enemySpecies(SpeciesId.SNORLAX) + .startingLevel(100) + .enemyLevel(100) + .moveset([MoveId.FILLET_AWAY]) + .enemyMoveset(MoveId.SPLASH); }); //Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/fillet_away_(move) diff --git a/test/moves/fissure.test.ts b/test/moves/fissure.test.ts index 53327b505c4..231eab427e1 100644 --- a/test/moves/fissure.test.ts +++ b/test/moves/fissure.test.ts @@ -1,5 +1,4 @@ import { Stat } from "#enums/stat"; -import { SpeciesId } from "#enums/species-id"; import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; @@ -8,6 +7,7 @@ import { MoveId } from "#enums/move-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { SpeciesId } from "#enums/species-id"; describe("Moves - Fissure", () => { let phaserGame: Phaser.Game; @@ -28,18 +28,17 @@ describe("Moves - Fissure", () => { beforeEach(async () => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.disableCrits(); - - game.override.starterSpecies(SpeciesId.SNORLAX); - game.override.moveset([MoveId.FISSURE]); - game.override.passiveAbility(AbilityId.BALL_FETCH); - game.override.startingLevel(100); - - game.override.enemySpecies(SpeciesId.SNORLAX); - game.override.enemyMoveset(MoveId.SPLASH); - game.override.enemyPassiveAbility(AbilityId.BALL_FETCH); - game.override.enemyLevel(100); + game.override + .battleStyle("single") + .disableCrits() + .starterSpecies(SpeciesId.SNORLAX) + .moveset([MoveId.FISSURE]) + .passiveAbility(AbilityId.BALL_FETCH) + .startingLevel(100) + .enemySpecies(SpeciesId.SNORLAX) + .enemyMoveset(MoveId.SPLASH) + .enemyPassiveAbility(AbilityId.BALL_FETCH) + .enemyLevel(100); await game.classicMode.startBattle(); @@ -48,8 +47,7 @@ describe("Moves - Fissure", () => { }); it("ignores damage modification from abilities, for example FUR_COAT", async () => { - game.override.ability(AbilityId.NO_GUARD); - game.override.enemyAbility(AbilityId.FUR_COAT); + game.override.ability(AbilityId.NO_GUARD).enemyAbility(AbilityId.FUR_COAT); game.move.select(MoveId.FISSURE); await game.phaseInterceptor.to(DamageAnimPhase, true); diff --git a/test/moves/flame_burst.test.ts b/test/moves/flame_burst.test.ts index 1d61daaacb5..e994dc3e568 100644 --- a/test/moves/flame_burst.test.ts +++ b/test/moves/flame_burst.test.ts @@ -35,14 +35,15 @@ describe("Moves - Flame Burst", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("double"); - game.override.moveset([MoveId.FLAME_BURST, MoveId.SPLASH]); - game.override.disableCrits(); - game.override.ability(AbilityId.UNNERVE); - game.override.startingWave(4); - game.override.enemySpecies(SpeciesId.SHUCKLE); - game.override.enemyAbility(AbilityId.BALL_FETCH); - game.override.enemyMoveset([MoveId.SPLASH]); + game.override + .battleStyle("double") + .moveset([MoveId.FLAME_BURST, MoveId.SPLASH]) + .disableCrits() + .ability(AbilityId.UNNERVE) + .startingWave(4) + .enemySpecies(SpeciesId.SHUCKLE) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyMoveset([MoveId.SPLASH]); }); it("inflicts damage to the target's ally equal to 1/16 of its max HP", async () => { diff --git a/test/moves/flower_shield.test.ts b/test/moves/flower_shield.test.ts index 3561d396ef9..c3c5e5cf870 100644 --- a/test/moves/flower_shield.test.ts +++ b/test/moves/flower_shield.test.ts @@ -26,11 +26,12 @@ describe("Moves - Flower Shield", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.ability(AbilityId.NONE); - game.override.enemyAbility(AbilityId.NONE); - game.override.battleStyle("single"); - game.override.moveset([MoveId.FLOWER_SHIELD, MoveId.SPLASH]); - game.override.enemyMoveset(MoveId.SPLASH); + game.override + .ability(AbilityId.NONE) + .enemyAbility(AbilityId.NONE) + .battleStyle("single") + .moveset([MoveId.FLOWER_SHIELD, MoveId.SPLASH]) + .enemyMoveset(MoveId.SPLASH); }); it("raises DEF stat stage by 1 for all Grass-type Pokemon on the field by one stage - single battle", async () => { diff --git a/test/moves/follow_me.test.ts b/test/moves/follow_me.test.ts index 8279e7b325a..a99ac0b6c00 100644 --- a/test/moves/follow_me.test.ts +++ b/test/moves/follow_me.test.ts @@ -24,14 +24,15 @@ describe("Moves - Follow Me", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("double"); - game.override.starterSpecies(SpeciesId.AMOONGUSS); - game.override.ability(AbilityId.BALL_FETCH); - game.override.enemySpecies(SpeciesId.SNORLAX); - game.override.startingLevel(100); - game.override.enemyLevel(100); - game.override.moveset([MoveId.FOLLOW_ME, MoveId.RAGE_POWDER, MoveId.SPOTLIGHT, MoveId.QUICK_ATTACK]); - game.override.enemyMoveset([MoveId.TACKLE, MoveId.FOLLOW_ME, MoveId.SPLASH]); + game.override + .battleStyle("double") + .starterSpecies(SpeciesId.AMOONGUSS) + .ability(AbilityId.BALL_FETCH) + .enemySpecies(SpeciesId.SNORLAX) + .startingLevel(100) + .enemyLevel(100) + .moveset([MoveId.FOLLOW_ME, MoveId.RAGE_POWDER, MoveId.SPOTLIGHT, MoveId.QUICK_ATTACK]) + .enemyMoveset([MoveId.TACKLE, MoveId.FOLLOW_ME, MoveId.SPLASH]); }); test("move should redirect enemy attacks to the user", async () => { @@ -73,8 +74,7 @@ describe("Moves - Follow Me", () => { }); test("move effect should be bypassed by Stalwart", async () => { - game.override.ability(AbilityId.STALWART); - game.override.moveset([MoveId.QUICK_ATTACK]); + game.override.ability(AbilityId.STALWART).moveset([MoveId.QUICK_ATTACK]); await game.classicMode.startBattle([SpeciesId.AMOONGUSS, SpeciesId.CHARIZARD]); diff --git a/test/moves/gastro_acid.test.ts b/test/moves/gastro_acid.test.ts index 28f2c8e8476..dbaa53dcb4f 100644 --- a/test/moves/gastro_acid.test.ts +++ b/test/moves/gastro_acid.test.ts @@ -22,14 +22,15 @@ describe("Moves - Gastro Acid", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("double"); - game.override.startingLevel(1); - game.override.enemyLevel(100); - game.override.ability(AbilityId.BALL_FETCH); - game.override.moveset([MoveId.GASTRO_ACID, MoveId.WATER_GUN, MoveId.SPLASH, MoveId.CORE_ENFORCER]); - game.override.enemySpecies(SpeciesId.BIDOOF); - game.override.enemyMoveset(MoveId.SPLASH); - game.override.enemyAbility(AbilityId.WATER_ABSORB); + game.override + .battleStyle("double") + .startingLevel(1) + .enemyLevel(100) + .ability(AbilityId.NONE) + .moveset([MoveId.GASTRO_ACID, MoveId.WATER_GUN, MoveId.SPLASH, MoveId.CORE_ENFORCER]) + .enemySpecies(SpeciesId.BIDOOF) + .enemyMoveset(MoveId.SPLASH) + .enemyAbility(AbilityId.WATER_ABSORB); }); it("suppresses effect of ability", async () => { diff --git a/test/moves/glaive_rush.test.ts b/test/moves/glaive_rush.test.ts index 768c4a691b0..2aead405fb6 100644 --- a/test/moves/glaive_rush.test.ts +++ b/test/moves/glaive_rush.test.ts @@ -28,13 +28,12 @@ describe("Moves - Glaive Rush", () => { .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset([MoveId.GLAIVE_RUSH]) - .starterSpecies(SpeciesId.KLINK) .ability(AbilityId.BALL_FETCH) .moveset([MoveId.SHADOW_SNEAK, MoveId.AVALANCHE, MoveId.SPLASH, MoveId.GLAIVE_RUSH]); }); it("takes double damage from attacks", async () => { - await game.classicMode.startBattle(); + await game.classicMode.startBattle([SpeciesId.KLINK]); const enemy = game.scene.getEnemyPokemon()!; enemy.hp = 1000; @@ -49,7 +48,7 @@ describe("Moves - Glaive Rush", () => { }); it("always gets hit by attacks", async () => { - await game.classicMode.startBattle(); + await game.classicMode.startBattle([SpeciesId.KLINK]); const enemy = game.scene.getEnemyPokemon()!; enemy.hp = 1000; @@ -62,7 +61,7 @@ describe("Moves - Glaive Rush", () => { it("interacts properly with multi-lens", async () => { game.override.startingHeldItems([{ name: "MULTI_LENS", count: 2 }]).enemyMoveset([MoveId.AVALANCHE]); - await game.classicMode.startBattle(); + await game.classicMode.startBattle([SpeciesId.KLINK]); const player = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; @@ -82,7 +81,7 @@ describe("Moves - Glaive Rush", () => { it("secondary effects only last until next move", async () => { game.override.enemyMoveset([MoveId.SHADOW_SNEAK]); - await game.classicMode.startBattle(); + await game.classicMode.startBattle([SpeciesId.KLINK]); const player = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; @@ -106,7 +105,7 @@ describe("Moves - Glaive Rush", () => { }); it("secondary effects are removed upon switching", async () => { - game.override.enemyMoveset([MoveId.SHADOW_SNEAK]).starterSpecies(0); + game.override.enemyMoveset([MoveId.SHADOW_SNEAK]); await game.classicMode.startBattle([SpeciesId.KLINK, SpeciesId.FEEBAS]); const player = game.scene.getPlayerPokemon()!; @@ -127,8 +126,10 @@ describe("Moves - Glaive Rush", () => { }); it("secondary effects don't activate if move fails", async () => { - game.override.moveset([MoveId.SHADOW_SNEAK, MoveId.PROTECT, MoveId.SPLASH, MoveId.GLAIVE_RUSH]); - await game.classicMode.startBattle(); + game.override + .moveset([MoveId.SHADOW_SNEAK, MoveId.PROTECT, MoveId.SPLASH, MoveId.GLAIVE_RUSH]) + .enemyMoveset([MoveId.GLAIVE_RUSH, MoveId.SPLASH]); + await game.classicMode.startBattle([SpeciesId.KLINK]); const player = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; @@ -137,15 +138,17 @@ describe("Moves - Glaive Rush", () => { player.hp = 1000; game.move.select(MoveId.PROTECT); + await game.move.forceEnemyMove(MoveId.GLAIVE_RUSH); await game.phaseInterceptor.to("TurnEndPhase"); game.move.select(MoveId.SHADOW_SNEAK); + await game.move.forceEnemyMove(MoveId.GLAIVE_RUSH); await game.phaseInterceptor.to("TurnEndPhase"); - game.override.enemyMoveset([MoveId.SPLASH]); const damagedHP1 = 1000 - enemy.hp; enemy.hp = 1000; game.move.select(MoveId.SHADOW_SNEAK); + await game.move.forceEnemyMove(MoveId.SPLASH); await game.phaseInterceptor.to("TurnEndPhase"); const damagedHP2 = 1000 - enemy.hp; diff --git a/test/moves/growth.test.ts b/test/moves/growth.test.ts index d468523aca7..a7078961e2a 100644 --- a/test/moves/growth.test.ts +++ b/test/moves/growth.test.ts @@ -24,11 +24,12 @@ describe("Moves - Growth", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.enemyAbility(AbilityId.MOXIE); - game.override.ability(AbilityId.INSOMNIA); - game.override.moveset([MoveId.GROWTH]); - game.override.enemyMoveset(MoveId.SPLASH); + game.override + .battleStyle("single") + .enemyAbility(AbilityId.MOXIE) + .ability(AbilityId.INSOMNIA) + .moveset([MoveId.GROWTH]) + .enemyMoveset(MoveId.SPLASH); }); it("should raise SPATK stat stage by 1", async () => { diff --git a/test/moves/hard_press.test.ts b/test/moves/hard_press.test.ts index 88a8c4a025b..e57c9af981f 100644 --- a/test/moves/hard_press.test.ts +++ b/test/moves/hard_press.test.ts @@ -27,12 +27,13 @@ describe("Moves - Hard Press", () => { beforeEach(() => { moveToCheck = allMoves[MoveId.HARD_PRESS]; game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.ability(AbilityId.BALL_FETCH); - game.override.enemySpecies(SpeciesId.MUNCHLAX); - game.override.enemyAbility(AbilityId.BALL_FETCH); - game.override.enemyMoveset(MoveId.SPLASH); - game.override.moveset([MoveId.HARD_PRESS]); + game.override + .battleStyle("single") + .ability(AbilityId.BALL_FETCH) + .enemySpecies(SpeciesId.MUNCHLAX) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyMoveset(MoveId.SPLASH) + .moveset([MoveId.HARD_PRESS]); vi.spyOn(moveToCheck, "calculateBattlePower"); }); diff --git a/test/moves/haze.test.ts b/test/moves/haze.test.ts index e77542227b8..f3e3dafae0a 100644 --- a/test/moves/haze.test.ts +++ b/test/moves/haze.test.ts @@ -23,16 +23,15 @@ describe("Moves - Haze", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - - game.override.enemySpecies(SpeciesId.RATTATA); - game.override.enemyLevel(100); - game.override.enemyMoveset(MoveId.SPLASH); - game.override.enemyAbility(AbilityId.NONE); - - game.override.startingLevel(100); - game.override.moveset([MoveId.HAZE, MoveId.SWORDS_DANCE, MoveId.CHARM, MoveId.SPLASH]); - game.override.ability(AbilityId.NONE); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.RATTATA) + .enemyLevel(100) + .enemyMoveset(MoveId.SPLASH) + .enemyAbility(AbilityId.BALL_FETCH) + .startingLevel(100) + .moveset([MoveId.HAZE, MoveId.SWORDS_DANCE, MoveId.CHARM, MoveId.SPLASH]) + .ability(AbilityId.BALL_FETCH); }); it("should reset all stat changes of all Pokemon on field", async () => { diff --git a/test/moves/hyper_beam.test.ts b/test/moves/hyper_beam.test.ts index 083482d16ef..bca7cba4e9a 100644 --- a/test/moves/hyper_beam.test.ts +++ b/test/moves/hyper_beam.test.ts @@ -26,14 +26,14 @@ describe("Moves - Hyper Beam", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.ability(AbilityId.BALL_FETCH); - game.override.enemySpecies(SpeciesId.SNORLAX); - game.override.enemyAbility(AbilityId.BALL_FETCH); - game.override.enemyMoveset([MoveId.SPLASH]); - game.override.enemyLevel(100); - - game.override.moveset([MoveId.HYPER_BEAM, MoveId.TACKLE]); + game.override + .battleStyle("single") + .ability(AbilityId.BALL_FETCH) + .enemySpecies(SpeciesId.SNORLAX) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyMoveset([MoveId.SPLASH]) + .enemyLevel(100) + .moveset([MoveId.HYPER_BEAM, MoveId.TACKLE]); vi.spyOn(allMoves[MoveId.HYPER_BEAM], "accuracy", "get").mockReturnValue(100); }); diff --git a/test/moves/lunar_blessing.test.ts b/test/moves/lunar_blessing.test.ts index 59d0c662859..2709ccbacf3 100644 --- a/test/moves/lunar_blessing.test.ts +++ b/test/moves/lunar_blessing.test.ts @@ -22,14 +22,13 @@ describe("Moves - Lunar Blessing", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("double"); - - game.override.enemySpecies(SpeciesId.SHUCKLE); - game.override.enemyMoveset(MoveId.SPLASH); - game.override.enemyAbility(AbilityId.BALL_FETCH); - - game.override.moveset([MoveId.LUNAR_BLESSING, MoveId.SPLASH]); - game.override.ability(AbilityId.BALL_FETCH); + game.override + .battleStyle("double") + .enemySpecies(SpeciesId.SHUCKLE) + .enemyMoveset(MoveId.SPLASH) + .enemyAbility(AbilityId.BALL_FETCH) + .moveset([MoveId.LUNAR_BLESSING, MoveId.SPLASH]) + .ability(AbilityId.BALL_FETCH); }); it("should restore 25% HP of the user and its ally", async () => { diff --git a/test/moves/magic_coat.test.ts b/test/moves/magic_coat.test.ts index a20aaf38043..7bbd4779da6 100644 --- a/test/moves/magic_coat.test.ts +++ b/test/moves/magic_coat.test.ts @@ -56,8 +56,7 @@ describe("Moves - Magic Coat", () => { }); it("should not reflect moves used on the next turn", async () => { - game.override.moveset([MoveId.GROWL, MoveId.SPLASH]); - game.override.enemyMoveset([MoveId.MAGIC_COAT, MoveId.SPLASH]); + game.override.moveset([MoveId.GROWL, MoveId.SPLASH]).enemyMoveset([MoveId.MAGIC_COAT, MoveId.SPLASH]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); // turn 1 @@ -82,8 +81,7 @@ describe("Moves - Magic Coat", () => { }); it("should individually bounce back multi-target moves when used by both targets in doubles", async () => { - game.override.battleStyle("double"); - game.override.moveset([MoveId.GROWL, MoveId.SPLASH]); + game.override.battleStyle("double").moveset([MoveId.GROWL, MoveId.SPLASH]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]); game.move.select(MoveId.GROWL, 0); @@ -95,9 +93,10 @@ describe("Moves - Magic Coat", () => { }); it("should bounce back a spread status move against both pokemon", async () => { - game.override.battleStyle("double"); - game.override.moveset([MoveId.GROWL, MoveId.SPLASH]); - game.override.enemyMoveset([MoveId.SPLASH, MoveId.MAGIC_COAT]); + game.override + .battleStyle("double") + .moveset([MoveId.GROWL, MoveId.SPLASH]) + .enemyMoveset([MoveId.SPLASH, MoveId.MAGIC_COAT]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]); game.move.select(MoveId.GROWL, 0); @@ -121,10 +120,11 @@ describe("Moves - Magic Coat", () => { }); it("should not bounce back a move that was just bounced", async () => { - game.override.battleStyle("double"); - game.override.ability(AbilityId.MAGIC_BOUNCE); - game.override.moveset([MoveId.GROWL, MoveId.MAGIC_COAT]); - game.override.enemyMoveset([MoveId.SPLASH, MoveId.MAGIC_COAT]); + game.override + .battleStyle("double") + .ability(AbilityId.MAGIC_BOUNCE) + .moveset([MoveId.GROWL, MoveId.MAGIC_COAT]) + .enemyMoveset([MoveId.SPLASH, MoveId.MAGIC_COAT]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]); game.move.select(MoveId.MAGIC_COAT, 0); @@ -147,8 +147,7 @@ describe("Moves - Magic Coat", () => { }); it("should still bounce back a move from a mold breaker user", async () => { - game.override.ability(AbilityId.MOLD_BREAKER); - game.override.moveset([MoveId.GROWL]); + game.override.ability(AbilityId.MOLD_BREAKER).moveset([MoveId.GROWL]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); game.move.select(MoveId.GROWL); @@ -159,9 +158,8 @@ describe("Moves - Magic Coat", () => { }); it("should only bounce spikes back once when both targets use magic coat in doubles", async () => { - game.override.battleStyle("double"); + game.override.battleStyle("double").moveset([MoveId.SPIKES]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); - game.override.moveset([MoveId.SPIKES]); game.move.select(MoveId.SPIKES); await game.phaseInterceptor.to("BerryPhase"); @@ -171,9 +169,8 @@ describe("Moves - Magic Coat", () => { }); it("should not bounce back curse", async () => { - game.override.starterSpecies(SpeciesId.GASTLY); - await game.classicMode.startBattle([SpeciesId.GASTLY]); game.override.moveset([MoveId.CURSE]); + await game.classicMode.startBattle([SpeciesId.GASTLY]); game.move.select(MoveId.CURSE); await game.phaseInterceptor.to("BerryPhase"); @@ -183,9 +180,10 @@ describe("Moves - Magic Coat", () => { // TODO: encore is failing if the last move was virtual. it.todo("should not cause the bounced move to count for encore", async () => { - game.override.moveset([MoveId.GROWL, MoveId.ENCORE]); - game.override.enemyMoveset([MoveId.MAGIC_COAT, MoveId.TACKLE]); - game.override.enemyAbility(AbilityId.MAGIC_BOUNCE); + game.override + .moveset([MoveId.GROWL, MoveId.ENCORE]) + .enemyMoveset([MoveId.MAGIC_COAT, MoveId.TACKLE]) + .enemyAbility(AbilityId.MAGIC_BOUNCE); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -206,9 +204,8 @@ describe("Moves - Magic Coat", () => { // TODO: stomping tantrum should consider moves that were bounced. it.todo("should cause stomping tantrum to double in power when the last move was bounced", async () => { - game.override.battleStyle("single"); + game.override.battleStyle("single").moveset([MoveId.STOMPING_TANTRUM, MoveId.CHARM]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); - game.override.moveset([MoveId.STOMPING_TANTRUM, MoveId.CHARM]); const stomping_tantrum = allMoves[MoveId.STOMPING_TANTRUM]; vi.spyOn(stomping_tantrum, "calculateBattlePower"); @@ -249,8 +246,7 @@ describe("Moves - Magic Coat", () => { it("should respect immunities when bouncing a move", async () => { vi.spyOn(allMoves[MoveId.THUNDER_WAVE], "accuracy", "get").mockReturnValue(100); - game.override.moveset([MoveId.THUNDER_WAVE, MoveId.GROWL]); - game.override.ability(AbilityId.SOUNDPROOF); + game.override.moveset([MoveId.THUNDER_WAVE, MoveId.GROWL]).ability(AbilityId.SOUNDPROOF); await game.classicMode.startBattle([SpeciesId.PHANPY]); // Turn 1 - thunder wave immunity test diff --git a/test/moves/make_it_rain.test.ts b/test/moves/make_it_rain.test.ts index 0089dfb642d..13b8e8f1853 100644 --- a/test/moves/make_it_rain.test.ts +++ b/test/moves/make_it_rain.test.ts @@ -24,13 +24,14 @@ describe("Moves - Make It Rain", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("double"); - game.override.moveset([MoveId.MAKE_IT_RAIN, MoveId.SPLASH]); - game.override.enemySpecies(SpeciesId.SNORLAX); - game.override.enemyAbility(AbilityId.INSOMNIA); - game.override.enemyMoveset(MoveId.SPLASH); - game.override.startingLevel(100); - game.override.enemyLevel(100); + game.override + .battleStyle("double") + .moveset([MoveId.MAKE_IT_RAIN, MoveId.SPLASH]) + .enemySpecies(SpeciesId.SNORLAX) + .enemyAbility(AbilityId.INSOMNIA) + .enemyMoveset(MoveId.SPLASH) + .startingLevel(100) + .enemyLevel(100); }); it("should only lower SPATK stat stage by 1 once in a double battle", async () => { @@ -47,8 +48,9 @@ describe("Moves - Make It Rain", () => { }); it("should apply effects even if the target faints", async () => { - game.override.enemyLevel(1); // ensures the enemy will faint - game.override.battleStyle("single"); + game.override + .enemyLevel(1) // ensures the enemy will faint + .battleStyle("single"); await game.classicMode.startBattle([SpeciesId.CHARIZARD]); diff --git a/test/moves/mat_block.test.ts b/test/moves/mat_block.test.ts index c512503c9c4..d77f538a973 100644 --- a/test/moves/mat_block.test.ts +++ b/test/moves/mat_block.test.ts @@ -26,16 +26,14 @@ describe("Moves - Mat Block", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("double"); - - game.override.moveset([MoveId.MAT_BLOCK, MoveId.SPLASH]); - - game.override.enemySpecies(SpeciesId.SNORLAX); - game.override.enemyMoveset([MoveId.TACKLE]); - game.override.enemyAbility(AbilityId.INSOMNIA); - - game.override.startingLevel(100); - game.override.enemyLevel(100); + game.override + .battleStyle("double") + .moveset([MoveId.MAT_BLOCK, MoveId.SPLASH]) + .enemySpecies(SpeciesId.SNORLAX) + .enemyMoveset([MoveId.TACKLE]) + .enemyAbility(AbilityId.INSOMNIA) + .startingLevel(100) + .enemyLevel(100); }); test("should protect the user and allies from attack moves", async () => { diff --git a/test/moves/parting_shot.test.ts b/test/moves/parting_shot.test.ts index f7a1b86fb34..fdeab6bfc7c 100644 --- a/test/moves/parting_shot.test.ts +++ b/test/moves/parting_shot.test.ts @@ -26,11 +26,12 @@ describe("Moves - Parting Shot", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.moveset([MoveId.PARTING_SHOT, MoveId.SPLASH]); - game.override.enemyMoveset(MoveId.SPLASH); - game.override.startingLevel(5); - game.override.enemyLevel(5); + game.override + .battleStyle("single") + .moveset([MoveId.PARTING_SHOT, MoveId.SPLASH]) + .enemyMoveset(MoveId.SPLASH) + .startingLevel(5) + .enemyLevel(5); }); test("Parting Shot when buffed by prankster should fail against dark types", async () => { diff --git a/test/moves/powder.test.ts b/test/moves/powder.test.ts index 38e35d60335..889e660f167 100644 --- a/test/moves/powder.test.ts +++ b/test/moves/powder.test.ts @@ -28,9 +28,8 @@ describe("Moves - Powder", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override + .battleStyle("single") .enemySpecies(SpeciesId.SNORLAX) .enemyLevel(100) .enemyMoveset(MoveId.EMBER) diff --git a/test/moves/protect.test.ts b/test/moves/protect.test.ts index 754a3433701..601afb43ec3 100644 --- a/test/moves/protect.test.ts +++ b/test/moves/protect.test.ts @@ -28,16 +28,14 @@ describe("Moves - Protect", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - - game.override.moveset([MoveId.PROTECT]); - game.override.enemySpecies(SpeciesId.SNORLAX); - - game.override.enemyAbility(AbilityId.INSOMNIA); - game.override.enemyMoveset([MoveId.TACKLE]); - - game.override.startingLevel(100); - game.override.enemyLevel(100); + game.override + .battleStyle("single") + .moveset([MoveId.PROTECT]) + .enemySpecies(SpeciesId.SNORLAX) + .enemyAbility(AbilityId.INSOMNIA) + .enemyMoveset([MoveId.TACKLE]) + .startingLevel(100) + .enemyLevel(100); }); test("should protect the user from attacks", async () => { diff --git a/test/moves/purify.test.ts b/test/moves/purify.test.ts index 0510260b755..2e8e312f23c 100644 --- a/test/moves/purify.test.ts +++ b/test/moves/purify.test.ts @@ -25,15 +25,14 @@ describe("Moves - Purify", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - - game.override.starterSpecies(SpeciesId.PYUKUMUKU); - game.override.startingLevel(10); - game.override.moveset([MoveId.PURIFY, MoveId.SIZZLY_SLIDE]); - - game.override.enemySpecies(SpeciesId.MAGIKARP); - game.override.enemyLevel(10); - game.override.enemyMoveset([MoveId.SPLASH, MoveId.NONE, MoveId.NONE, MoveId.NONE]); + game.override + .battleStyle("single") + .starterSpecies(SpeciesId.PYUKUMUKU) + .startingLevel(10) + .moveset([MoveId.PURIFY, MoveId.SIZZLY_SLIDE]) + .enemySpecies(SpeciesId.MAGIKARP) + .enemyLevel(10) + .enemyMoveset([MoveId.SPLASH]); }); test("Purify heals opponent status effect and restores user hp", async () => { diff --git a/test/moves/quick_guard.test.ts b/test/moves/quick_guard.test.ts index 49f501fb839..7146b804ffa 100644 --- a/test/moves/quick_guard.test.ts +++ b/test/moves/quick_guard.test.ts @@ -25,16 +25,14 @@ describe("Moves - Quick Guard", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("double"); - - game.override.moveset([MoveId.QUICK_GUARD, MoveId.SPLASH, MoveId.FOLLOW_ME]); - - game.override.enemySpecies(SpeciesId.SNORLAX); - game.override.enemyMoveset([MoveId.QUICK_ATTACK]); - game.override.enemyAbility(AbilityId.INSOMNIA); - - game.override.startingLevel(100); - game.override.enemyLevel(100); + game.override + .battleStyle("double") + .moveset([MoveId.QUICK_GUARD, MoveId.SPLASH, MoveId.FOLLOW_ME]) + .enemySpecies(SpeciesId.SNORLAX) + .enemyMoveset([MoveId.QUICK_ATTACK]) + .enemyAbility(AbilityId.INSOMNIA) + .startingLevel(100) + .enemyLevel(100); }); test("should protect the user and allies from priority moves", async () => { @@ -51,8 +49,7 @@ describe("Moves - Quick Guard", () => { }); test("should protect the user and allies from Prankster-boosted moves", async () => { - game.override.enemyAbility(AbilityId.PRANKSTER); - game.override.enemyMoveset([MoveId.GROWL]); + game.override.enemyAbility(AbilityId.PRANKSTER).enemyMoveset([MoveId.GROWL]); await game.classicMode.startBattle([SpeciesId.CHARIZARD, SpeciesId.BLASTOISE]); @@ -84,8 +81,7 @@ describe("Moves - Quick Guard", () => { }); test("should fail if the user is the last to move in the turn", async () => { - game.override.battleStyle("single"); - game.override.enemyMoveset([MoveId.QUICK_GUARD]); + game.override.battleStyle("single").enemyMoveset([MoveId.QUICK_GUARD]); await game.classicMode.startBattle([SpeciesId.CHARIZARD]); diff --git a/test/moves/rage_powder.test.ts b/test/moves/rage_powder.test.ts index 807000a0ff0..845bff33d04 100644 --- a/test/moves/rage_powder.test.ts +++ b/test/moves/rage_powder.test.ts @@ -22,12 +22,13 @@ describe("Moves - Rage Powder", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("double"); - game.override.enemySpecies(SpeciesId.SNORLAX); - game.override.startingLevel(100); - game.override.enemyLevel(100); - game.override.moveset([MoveId.FOLLOW_ME, MoveId.RAGE_POWDER, MoveId.SPOTLIGHT, MoveId.QUICK_ATTACK]); - game.override.enemyMoveset([MoveId.RAGE_POWDER, MoveId.TACKLE, MoveId.SPLASH]); + game.override + .battleStyle("double") + .enemySpecies(SpeciesId.SNORLAX) + .startingLevel(100) + .enemyLevel(100) + .moveset([MoveId.FOLLOW_ME, MoveId.RAGE_POWDER, MoveId.SPOTLIGHT, MoveId.QUICK_ATTACK]) + .enemyMoveset([MoveId.RAGE_POWDER, MoveId.TACKLE, MoveId.SPLASH]); }); test("move effect should be bypassed by Grass type", async () => { diff --git a/test/moves/rollout.test.ts b/test/moves/rollout.test.ts index de990a1a750..b9faf438ae5 100644 --- a/test/moves/rollout.test.ts +++ b/test/moves/rollout.test.ts @@ -23,18 +23,19 @@ describe("Moves - Rollout", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.disableCrits(); - game.override.battleStyle("single"); - game.override.starterSpecies(SpeciesId.RATTATA); - game.override.ability(AbilityId.BALL_FETCH); - game.override.enemySpecies(SpeciesId.BIDOOF); - game.override.enemyAbility(AbilityId.BALL_FETCH); - game.override.startingLevel(100); - game.override.enemyLevel(100); - game.override.enemyMoveset(MoveId.SPLASH); + game.override + .disableCrits() + .battleStyle("single") + .starterSpecies(SpeciesId.RATTATA) + .ability(AbilityId.BALL_FETCH) + .enemySpecies(SpeciesId.BIDOOF) + .enemyAbility(AbilityId.BALL_FETCH) + .startingLevel(100) + .enemyLevel(100) + .enemyMoveset(MoveId.SPLASH); }); - it("should double it's dmg on sequential uses but reset after 5", async () => { + it("should double its dmg on sequential uses but reset after 5", async () => { game.override.moveset([MoveId.ROLLOUT]); vi.spyOn(allMoves[MoveId.ROLLOUT], "accuracy", "get").mockReturnValue(100); //always hit diff --git a/test/moves/roost.test.ts b/test/moves/roost.test.ts index 1e588cb1e15..3707e0ead45 100644 --- a/test/moves/roost.test.ts +++ b/test/moves/roost.test.ts @@ -25,12 +25,13 @@ describe("Moves - Roost", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.enemySpecies(SpeciesId.RELICANTH); - game.override.startingLevel(100); - game.override.enemyLevel(100); - game.override.enemyMoveset(MoveId.EARTHQUAKE); - game.override.moveset([MoveId.ROOST, MoveId.BURN_UP, MoveId.DOUBLE_SHOCK]); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.RELICANTH) + .startingLevel(100) + .enemyLevel(100) + .enemyMoveset(MoveId.EARTHQUAKE) + .moveset([MoveId.ROOST, MoveId.BURN_UP, MoveId.DOUBLE_SHOCK]); }); /** diff --git a/test/moves/safeguard.test.ts b/test/moves/safeguard.test.ts index 6cd90bf8ac6..8d5303e3feb 100644 --- a/test/moves/safeguard.test.ts +++ b/test/moves/safeguard.test.ts @@ -1,12 +1,12 @@ import { BattlerIndex } from "#enums/battler-index"; import { allAbilities } from "#app/data/data-lists"; -import { AbilityId } from "#enums/ability-id"; import { StatusEffect } from "#app/enums/status-effect"; import GameManager from "#test/testUtils/gameManager"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { AbilityId } from "#enums/ability-id"; describe("Moves - Safeguard", () => { let phaserGame: Phaser.Game; @@ -109,49 +109,50 @@ describe("Moves - Safeguard", () => { game.move.select(MoveId.SPLASH); await game.toNextTurn(); - expect(enemyPokemon.status?.effect).toEqual(StatusEffect.SLEEP); + expect(enemyPokemon.status?.effect).toBe(StatusEffect.SLEEP); }); - it("doesn't protect from self-inflicted via Rest or Flame Orb", async () => { + it("doesn't protect from self-inflicted status from Rest or Flame Orb", async () => { game.override.enemyHeldItems([{ name: "FLAME_ORB" }]); await game.classicMode.startBattle(); const enemyPokemon = game.scene.getEnemyPokemon()!; + enemyPokemon.hp = 1; game.move.select(MoveId.SPLASH); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.move.forceEnemyMove(MoveId.SAFEGUARD); await game.toNextTurn(); - enemyPokemon.damageAndUpdate(1); - expect(enemyPokemon.status?.effect).toEqual(StatusEffect.BURN); + expect(enemyPokemon.status?.effect).toBe(StatusEffect.BURN); + + enemyPokemon.resetStatus(); - game.override.enemyMoveset([MoveId.REST]); - // Force the moveset to update mid-battle - // TODO: Remove after enemy AI rework is in - enemyPokemon.getMoveset(); game.move.select(MoveId.SPLASH); - enemyPokemon.damageAndUpdate(1); + await game.move.forceEnemyMove(MoveId.REST); await game.toNextTurn(); - expect(enemyPokemon.status?.effect).toEqual(StatusEffect.SLEEP); + expect(enemyPokemon.status?.effect).toBe(StatusEffect.SLEEP); }); it("protects from ability-inflicted status", async () => { - game.override.ability(AbilityId.STATIC); + await game.classicMode.startBattle(); + + const player = game.field.getPlayerPokemon(); + game.field.mockAbility(player, AbilityId.STATIC); vi.spyOn( allAbilities[AbilityId.STATIC].getAttrs("PostDefendContactApplyStatusEffectAbAttr")[0], "chance", "get", ).mockReturnValue(100); - await game.classicMode.startBattle(); - const enemyPokemon = game.scene.getEnemyPokemon()!; game.move.select(MoveId.SPLASH); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); - await game.toNextTurn(); - game.override.enemyMoveset([MoveId.TACKLE]); - game.move.select(MoveId.SPLASH); + await game.move.forceEnemyMove(MoveId.SAFEGUARD); await game.toNextTurn(); + game.move.select(MoveId.SPLASH); + await game.move.forceEnemyMove(MoveId.TACKLE); + await game.toNextTurn(); + + const enemyPokemon = game.field.getEnemyPokemon(); expect(enemyPokemon.status).toBeUndefined(); }); }); diff --git a/test/moves/spikes.test.ts b/test/moves/spikes.test.ts index d847f296e59..a2d72054020 100644 --- a/test/moves/spikes.test.ts +++ b/test/moves/spikes.test.ts @@ -81,9 +81,7 @@ describe("Moves - Spikes", () => { }, 20000); it("should work when all targets fainted", async () => { - game.override.enemySpecies(SpeciesId.DIGLETT); - game.override.battleStyle("double"); - game.override.startingLevel(50); + game.override.enemySpecies(SpeciesId.DIGLETT).battleStyle("double").startingLevel(50); await game.classicMode.startBattle([SpeciesId.RAYQUAZA, SpeciesId.ROWLET]); game.move.select(MoveId.EARTHQUAKE); diff --git a/test/moves/spit_up.test.ts b/test/moves/spit_up.test.ts index 83549c28f40..fee58ef99f7 100644 --- a/test/moves/spit_up.test.ts +++ b/test/moves/spit_up.test.ts @@ -32,15 +32,14 @@ describe("Moves - Spit Up", () => { spitUp = allMoves[MoveId.SPIT_UP]; game = new GameManager(phaserGame); - game.override.battleStyle("single"); - - game.override.enemySpecies(SpeciesId.RATTATA); - game.override.enemyMoveset(MoveId.SPLASH); - game.override.enemyAbility(AbilityId.NONE); - game.override.enemyLevel(2000); - - game.override.moveset(new Array(4).fill(spitUp.id)); - game.override.ability(AbilityId.NONE); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.RATTATA) + .enemyMoveset(MoveId.SPLASH) + .enemyAbility(AbilityId.NONE) + .enemyLevel(2000) + .moveset(MoveId.SPIT_UP) + .ability(AbilityId.NONE); vi.spyOn(spitUp, "calculateBattlePower"); }); diff --git a/test/moves/spotlight.test.ts b/test/moves/spotlight.test.ts index 602cedcaec9..a9e8cd7e2b6 100644 --- a/test/moves/spotlight.test.ts +++ b/test/moves/spotlight.test.ts @@ -22,13 +22,14 @@ describe("Moves - Spotlight", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("double"); - game.override.starterSpecies(SpeciesId.AMOONGUSS); - game.override.enemySpecies(SpeciesId.SNORLAX); - game.override.startingLevel(100); - game.override.enemyLevel(100); - game.override.moveset([MoveId.FOLLOW_ME, MoveId.RAGE_POWDER, MoveId.SPOTLIGHT, MoveId.QUICK_ATTACK]); - game.override.enemyMoveset([MoveId.FOLLOW_ME, MoveId.SPLASH]); + game.override + .battleStyle("double") + .starterSpecies(SpeciesId.AMOONGUSS) + .enemySpecies(SpeciesId.SNORLAX) + .startingLevel(100) + .enemyLevel(100) + .moveset([MoveId.FOLLOW_ME, MoveId.RAGE_POWDER, MoveId.SPOTLIGHT, MoveId.QUICK_ATTACK]) + .enemyMoveset([MoveId.FOLLOW_ME, MoveId.SPLASH]); }); test("move should redirect attacks to the target", async () => { diff --git a/test/moves/stockpile.test.ts b/test/moves/stockpile.test.ts index 4baa7949bc6..342ca43c27f 100644 --- a/test/moves/stockpile.test.ts +++ b/test/moves/stockpile.test.ts @@ -27,15 +27,14 @@ describe("Moves - Stockpile", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - - game.override.enemySpecies(SpeciesId.RATTATA); - game.override.enemyMoveset(MoveId.SPLASH); - game.override.enemyAbility(AbilityId.NONE); - - game.override.startingLevel(2000); - game.override.moveset([MoveId.STOCKPILE, MoveId.SPLASH]); - game.override.ability(AbilityId.NONE); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.RATTATA) + .enemyMoveset(MoveId.SPLASH) + .enemyAbility(AbilityId.NONE) + .startingLevel(2000) + .moveset([MoveId.STOCKPILE, MoveId.SPLASH]) + .ability(AbilityId.NONE); }); it("gains a stockpile stack and raises user's DEF and SPDEF stat stages by 1 on each use, fails at max stacks (3)", async () => { diff --git a/test/moves/substitute.test.ts b/test/moves/substitute.test.ts index 857f20c7fa0..182fefb74d7 100644 --- a/test/moves/substitute.test.ts +++ b/test/moves/substitute.test.ts @@ -138,8 +138,7 @@ describe("Moves - Substitute", () => { }); it("should be bypassed by attackers with Infiltrator", async () => { - game.override.enemyMoveset(MoveId.TACKLE); - game.override.enemyAbility(AbilityId.INFILTRATOR); + game.override.enemyMoveset(MoveId.TACKLE).enemyAbility(AbilityId.INFILTRATOR); await game.classicMode.startBattle([SpeciesId.BLASTOISE]); @@ -230,8 +229,7 @@ describe("Moves - Substitute", () => { }); it("should protect the user from flinching", async () => { - game.override.enemyMoveset(MoveId.FAKE_OUT); - game.override.startingLevel(1); // Ensures the Substitute will break + game.override.enemyMoveset(MoveId.FAKE_OUT).startingLevel(1); // Ensures the Substitute will break await game.classicMode.startBattle([SpeciesId.BLASTOISE]); @@ -298,9 +296,8 @@ describe("Moves - Substitute", () => { }); it("should prevent the user's items from being stolen", async () => { - game.override.enemyMoveset(MoveId.THIEF); + game.override.enemyMoveset(MoveId.THIEF).startingHeldItems([{ name: "BERRY", type: BerryType.SITRUS }]); vi.spyOn(allMoves[MoveId.THIEF], "attrs", "get").mockReturnValue([new StealHeldItemChanceAttr(1.0)]); // give Thief 100% steal rate - game.override.startingHeldItems([{ name: "BERRY", type: BerryType.SITRUS }]); await game.classicMode.startBattle([SpeciesId.BLASTOISE]); @@ -316,8 +313,7 @@ describe("Moves - Substitute", () => { }); it("should prevent the user's items from being removed", async () => { - game.override.moveset([MoveId.KNOCK_OFF]); - game.override.enemyHeldItems([{ name: "BERRY", type: BerryType.SITRUS }]); + game.override.moveset([MoveId.KNOCK_OFF]).enemyHeldItems([{ name: "BERRY", type: BerryType.SITRUS }]); await game.classicMode.startBattle([SpeciesId.BLASTOISE]); @@ -334,8 +330,7 @@ describe("Moves - Substitute", () => { }); it("move effect should prevent the user's berries from being stolen and eaten", async () => { - game.override.enemyMoveset(MoveId.BUG_BITE); - game.override.startingHeldItems([{ name: "BERRY", type: BerryType.SITRUS }]); + game.override.enemyMoveset(MoveId.BUG_BITE).startingHeldItems([{ name: "BERRY", type: BerryType.SITRUS }]); await game.classicMode.startBattle([SpeciesId.BLASTOISE]); @@ -412,8 +407,7 @@ describe("Moves - Substitute", () => { }); it("should prevent the source's Rough Skin from activating when hit", async () => { - game.override.enemyMoveset(MoveId.TACKLE); - game.override.ability(AbilityId.ROUGH_SKIN); + game.override.enemyMoveset(MoveId.TACKLE).ability(AbilityId.ROUGH_SKIN); await game.classicMode.startBattle([SpeciesId.BLASTOISE]); @@ -427,8 +421,7 @@ describe("Moves - Substitute", () => { }); it("should prevent the source's Focus Punch from failing when hit", async () => { - game.override.enemyMoveset(MoveId.TACKLE); - game.override.moveset([MoveId.FOCUS_PUNCH]); + game.override.enemyMoveset(MoveId.TACKLE).moveset([MoveId.FOCUS_PUNCH]); // Make Focus Punch 40 power to avoid a KO vi.spyOn(allMoves[MoveId.FOCUS_PUNCH], "calculateBattlePower").mockReturnValue(40); @@ -449,8 +442,7 @@ describe("Moves - Substitute", () => { }); it("should not allow Shell Trap to activate when attacked", async () => { - game.override.enemyMoveset(MoveId.TACKLE); - game.override.moveset([MoveId.SHELL_TRAP]); + game.override.enemyMoveset(MoveId.TACKLE).moveset([MoveId.SHELL_TRAP]); await game.classicMode.startBattle([SpeciesId.BLASTOISE]); @@ -466,8 +458,7 @@ describe("Moves - Substitute", () => { }); it("should not allow Beak Blast to burn opponents when hit", async () => { - game.override.enemyMoveset(MoveId.TACKLE); - game.override.moveset([MoveId.BEAK_BLAST]); + game.override.enemyMoveset(MoveId.TACKLE).moveset([MoveId.BEAK_BLAST]); await game.classicMode.startBattle([SpeciesId.BLASTOISE]); @@ -484,8 +475,7 @@ describe("Moves - Substitute", () => { }); it("should cause incoming attacks to not activate Counter", async () => { - game.override.enemyMoveset(MoveId.TACKLE); - game.override.moveset([MoveId.COUNTER]); + game.override.enemyMoveset(MoveId.TACKLE).moveset([MoveId.COUNTER]); await game.classicMode.startBattle([SpeciesId.BLASTOISE]); diff --git a/test/moves/tackle.test.ts b/test/moves/tackle.test.ts index 83a267dc7e4..61780044f49 100644 --- a/test/moves/tackle.test.ts +++ b/test/moves/tackle.test.ts @@ -37,6 +37,7 @@ describe("Moves - Tackle", () => { it("TACKLE against ghost", async () => { const moveToUse = MoveId.TACKLE; game.override.enemySpecies(SpeciesId.GENGAR); + await game.classicMode.startBattle([SpeciesId.MIGHTYENA]); const hpOpponent = game.scene.currentBattle.enemyParty[0].hp; game.move.select(moveToUse); diff --git a/test/moves/tail_whip.test.ts b/test/moves/tail_whip.test.ts index 5118897a7cb..9c9ac736ece 100644 --- a/test/moves/tail_whip.test.ts +++ b/test/moves/tail_whip.test.ts @@ -25,13 +25,14 @@ describe("Moves - Tail whip", () => { beforeEach(() => { game = new GameManager(phaserGame); const moveToUse = MoveId.TAIL_WHIP; - game.override.battleStyle("single"); - game.override.enemySpecies(SpeciesId.RATTATA); - game.override.enemyAbility(AbilityId.INSOMNIA); - game.override.ability(AbilityId.INSOMNIA); - game.override.startingLevel(2000); - game.override.moveset([moveToUse]); - game.override.enemyMoveset(MoveId.SPLASH); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.RATTATA) + .enemyAbility(AbilityId.INSOMNIA) + .ability(AbilityId.INSOMNIA) + .startingLevel(2000) + .moveset([moveToUse]) + .enemyMoveset(MoveId.SPLASH); }); it("should lower DEF stat stage by 1", async () => { diff --git a/test/moves/thousand_arrows.test.ts b/test/moves/thousand_arrows.test.ts index 428e7c4fbe4..9ecdd94a94f 100644 --- a/test/moves/thousand_arrows.test.ts +++ b/test/moves/thousand_arrows.test.ts @@ -24,12 +24,13 @@ describe("Moves - Thousand Arrows", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.enemySpecies(SpeciesId.TOGETIC); - game.override.startingLevel(100); - game.override.enemyLevel(100); - game.override.moveset([MoveId.THOUSAND_ARROWS]); - game.override.enemyMoveset([MoveId.SPLASH, MoveId.SPLASH, MoveId.SPLASH, MoveId.SPLASH]); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.TOGETIC) + .startingLevel(100) + .enemyLevel(100) + .moveset([MoveId.THOUSAND_ARROWS]) + .enemyMoveset(MoveId.SPLASH); }); it("move should hit and ground Flying-type targets", async () => { @@ -50,8 +51,7 @@ describe("Moves - Thousand Arrows", () => { }); it("move should hit and ground targets with Levitate", async () => { - game.override.enemySpecies(SpeciesId.SNORLAX); - game.override.enemyAbility(AbilityId.LEVITATE); + game.override.enemySpecies(SpeciesId.SNORLAX).enemyAbility(AbilityId.LEVITATE); await game.classicMode.startBattle([SpeciesId.ILLUMISE]); diff --git a/test/moves/tidy_up.test.ts b/test/moves/tidy_up.test.ts index 8e79b6b130a..636946c2e6a 100644 --- a/test/moves/tidy_up.test.ts +++ b/test/moves/tidy_up.test.ts @@ -4,11 +4,11 @@ import { MoveEndPhase } from "#app/phases/move-end-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; -import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SubstituteTag } from "#app/data/battler-tags"; +import { SpeciesId } from "#enums/species-id"; describe("Moves - Tidy Up", () => { let phaserGame: Phaser.Game; @@ -26,14 +26,15 @@ describe("Moves - Tidy Up", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.enemySpecies(SpeciesId.MAGIKARP); - game.override.enemyAbility(AbilityId.BALL_FETCH); - game.override.enemyMoveset(MoveId.SPLASH); - game.override.starterSpecies(SpeciesId.FEEBAS); - game.override.ability(AbilityId.BALL_FETCH); - game.override.moveset([MoveId.TIDY_UP]); - game.override.startingLevel(50); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyMoveset(MoveId.SPLASH) + .starterSpecies(SpeciesId.FEEBAS) + .ability(AbilityId.BALL_FETCH) + .moveset([MoveId.TIDY_UP]) + .startingLevel(50); }); it("spikes are cleared", async () => { diff --git a/test/moves/toxic.test.ts b/test/moves/toxic.test.ts index eb23885b4b5..a7ba52eb963 100644 --- a/test/moves/toxic.test.ts +++ b/test/moves/toxic.test.ts @@ -75,8 +75,7 @@ describe("Moves - Toxic", () => { }); it("moves other than Toxic should not hit semi-invulnerable targets even if user is Poison-type", async () => { - game.override.moveset(MoveId.SWIFT); - game.override.enemyMoveset(MoveId.FLY); + game.override.moveset(MoveId.SWIFT).enemyMoveset(MoveId.FLY); await game.classicMode.startBattle([SpeciesId.TOXAPEX]); game.move.select(MoveId.SWIFT); diff --git a/test/moves/transform.test.ts b/test/moves/transform.test.ts index 8ee65802b37..4fbaf0136ab 100644 --- a/test/moves/transform.test.ts +++ b/test/moves/transform.test.ts @@ -154,8 +154,7 @@ describe("Moves - Transform", () => { }); it("should stay transformed with the correct form after reload", async () => { - game.override.enemyMoveset([]).moveset([]); - game.override.enemySpecies(SpeciesId.DARMANITAN); + game.override.enemyMoveset([]).moveset([]).enemySpecies(SpeciesId.DARMANITAN); await game.classicMode.startBattle([SpeciesId.DITTO]); diff --git a/test/moves/wide_guard.test.ts b/test/moves/wide_guard.test.ts index 65c3a0a805f..07c02158e94 100644 --- a/test/moves/wide_guard.test.ts +++ b/test/moves/wide_guard.test.ts @@ -25,16 +25,14 @@ describe("Moves - Wide Guard", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("double"); - - game.override.moveset([MoveId.WIDE_GUARD, MoveId.SPLASH, MoveId.SURF]); - - game.override.enemySpecies(SpeciesId.SNORLAX); - game.override.enemyMoveset([MoveId.SWIFT]); - game.override.enemyAbility(AbilityId.INSOMNIA); - - game.override.startingLevel(100); - game.override.enemyLevel(100); + game.override + .battleStyle("double") + .moveset([MoveId.WIDE_GUARD, MoveId.SPLASH, MoveId.SURF]) + .enemySpecies(SpeciesId.SNORLAX) + .enemyMoveset(MoveId.SWIFT) + .enemyAbility(AbilityId.INSOMNIA) + .startingLevel(100) + .enemyLevel(100); }); test("should protect the user and allies from multi-target attack moves", async () => { diff --git a/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts b/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts index d713db0aff8..e3368d339cd 100644 --- a/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts +++ b/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts @@ -38,10 +38,11 @@ describe("A Trainer's Test - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves(); const biomeMap = new Map([ [BiomeId.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]], diff --git a/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts b/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts index f5bc1a62528..304ca87e0e6 100644 --- a/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts +++ b/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts @@ -38,10 +38,11 @@ describe("Absolute Avarice - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves(); vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( new Map([ @@ -71,8 +72,7 @@ describe("Absolute Avarice - Mystery Encounter", () => { }); it("should not spawn outside of proper biomes", async () => { - game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT); - game.override.startingBiome(BiomeId.VOLCANO); + game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT).startingBiome(BiomeId.VOLCANO); await game.runToMysteryEncounter(); expect(game.scene.currentBattle.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.ABSOLUTE_AVARICE); @@ -87,8 +87,7 @@ describe("Absolute Avarice - Mystery Encounter", () => { }); it("should spawn if player has enough berries", async () => { - game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT); - game.override.startingHeldItems([ + game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT).startingHeldItems([ { name: "BERRY", count: 2, type: BerryType.SITRUS }, { name: "BERRY", count: 3, type: BerryType.GANLON }, { name: "BERRY", count: 2, type: BerryType.APICOT }, @@ -139,7 +138,6 @@ describe("Absolute Avarice - Mystery Encounter", () => { expect(enemyField.length).toBe(1); expect(enemyField[0].species.speciesId).toBe(SpeciesId.GREEDENT); const moveset = enemyField[0].moveset.map(m => m.moveId); - expect(moveset?.length).toBe(4); expect(moveset).toEqual([MoveId.THRASH, MoveId.CRUNCH, MoveId.BODY_PRESS, MoveId.SLACK_OFF]); const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]); diff --git a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts index d208a859825..8e8253fd912 100644 --- a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts +++ b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts @@ -79,8 +79,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { }); it("should not spawn outside of HUMAN_TRANSITABLE_BIOMES", async () => { - game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT); - game.override.startingBiome(BiomeId.VOLCANO); + game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT).startingBiome(BiomeId.VOLCANO); await game.runToMysteryEncounter(); expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe( diff --git a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts index 8e58a0ca242..d5527d208fd 100644 --- a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts +++ b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts @@ -154,10 +154,11 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves(); vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( new Map([[BiomeId.CAVE, [MysteryEncounterType.BUG_TYPE_SUPERFAN]]]), diff --git a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index 526a3a0ab67..9965b90a981 100644 --- a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -55,10 +55,11 @@ describe("Clowning Around - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves(); vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( new Map([[BiomeId.CAVE, [MysteryEncounterType.CLOWNING_AROUND]]]), diff --git a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts index ee4aefd9904..f74100d7e35 100644 --- a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts +++ b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts @@ -41,10 +41,11 @@ describe("Dancing Lessons - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves(); vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( new Map([ @@ -74,8 +75,7 @@ describe("Dancing Lessons - Mystery Encounter", () => { }); it("should not spawn outside of proper biomes", async () => { - game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT); - game.override.startingBiome(BiomeId.SPACE); + game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT).startingBiome(BiomeId.SPACE); await game.runToMysteryEncounter(); expect(game.scene.currentBattle.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.DANCING_LESSONS); diff --git a/test/mystery-encounter/encounters/delibirdy-encounter.test.ts b/test/mystery-encounter/encounters/delibirdy-encounter.test.ts index e569c1d7789..0b6f7453522 100644 --- a/test/mystery-encounter/encounters/delibirdy-encounter.test.ts +++ b/test/mystery-encounter/encounters/delibirdy-encounter.test.ts @@ -46,10 +46,11 @@ describe("Delibird-y - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves(); vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( new Map([[BiomeId.CAVE, [MysteryEncounterType.DELIBIRDY]]]), diff --git a/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts b/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts index 3feb44bbe91..470d41060fb 100644 --- a/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts +++ b/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts @@ -32,10 +32,11 @@ describe("Department Store Sale - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves(); const biomeMap = new Map([ [BiomeId.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]], @@ -74,8 +75,7 @@ describe("Department Store Sale - Mystery Encounter", () => { }); it("should not spawn outside of CIVILIZATION_ENCOUNTER_BIOMES", async () => { - game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON); - game.override.startingBiome(BiomeId.VOLCANO); + game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON).startingBiome(BiomeId.VOLCANO); await game.runToMysteryEncounter(); expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.DEPARTMENT_STORE_SALE); diff --git a/test/mystery-encounter/encounters/field-trip-encounter.test.ts b/test/mystery-encounter/encounters/field-trip-encounter.test.ts index f93de7dc955..e059827fa17 100644 --- a/test/mystery-encounter/encounters/field-trip-encounter.test.ts +++ b/test/mystery-encounter/encounters/field-trip-encounter.test.ts @@ -33,11 +33,12 @@ describe("Field Trip - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); - game.override.moveset([MoveId.TACKLE, MoveId.UPROAR, MoveId.SWORDS_DANCE]); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves() + .moveset([MoveId.TACKLE, MoveId.UPROAR, MoveId.SWORDS_DANCE]); vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( new Map([[BiomeId.CAVE, [MysteryEncounterType.FIELD_TRIP]]]), diff --git a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts index e0e8b3d90d6..bf5d58fdad3 100644 --- a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts +++ b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts @@ -40,10 +40,11 @@ describe("Fight or Flight - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves(); vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( new Map([[BiomeId.CAVE, [MysteryEncounterType.FIGHT_OR_FLIGHT]]]), diff --git a/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts b/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts index e31a3a5cc3c..9f2057080f9 100644 --- a/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts +++ b/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts @@ -42,10 +42,11 @@ describe("Fun And Games! - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves(); const biomeMap = new Map([ [BiomeId.VOLCANO, [MysteryEncounterType.FIGHT_OR_FLIGHT]], @@ -81,8 +82,7 @@ describe("Fun And Games! - Mystery Encounter", () => { }); it("should not spawn outside of CIVILIZATIONN biomes", async () => { - game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT); - game.override.startingBiome(BiomeId.VOLCANO); + game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT).startingBiome(BiomeId.VOLCANO); await game.runToMysteryEncounter(); expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.FUN_AND_GAMES); diff --git a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts index 0aa1886b8e1..02ffc56fb2e 100644 --- a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts +++ b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts @@ -37,10 +37,11 @@ describe("Global Trade System - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves(); const biomeMap = new Map([ [BiomeId.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]], @@ -85,8 +86,7 @@ describe("Global Trade System - Mystery Encounter", () => { }); it("should not spawn outside of CIVILIZATION_ENCOUNTER_BIOMES", async () => { - game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON); - game.override.startingBiome(BiomeId.VOLCANO); + game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON).startingBiome(BiomeId.VOLCANO); await game.runToMysteryEncounter(); expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.GLOBAL_TRADE_SYSTEM); diff --git a/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts b/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts index 8f041a14002..971cd938925 100644 --- a/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts +++ b/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts @@ -34,10 +34,11 @@ describe("Lost at Sea - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves(); vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( new Map([ @@ -67,8 +68,7 @@ describe("Lost at Sea - Mystery Encounter", () => { }); it("should not spawn outside of sea biome", async () => { - game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON); - game.override.startingBiome(BiomeId.MOUNTAIN); + game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON).startingBiome(BiomeId.MOUNTAIN); await game.runToMysteryEncounter(); expect(game.scene.currentBattle.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.LOST_AT_SEA); diff --git a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts index 9c5660cb25c..04b611e1143 100644 --- a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts +++ b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts @@ -43,10 +43,11 @@ describe("Mysterious Challengers - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves(); const biomeMap = new Map([ [BiomeId.VOLCANO, [MysteryEncounterType.FIGHT_OR_FLIGHT]], @@ -79,8 +80,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => { }); it("should not spawn outside of HUMAN_TRANSITABLE_BIOMES", async () => { - game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT); - game.override.startingBiome(BiomeId.VOLCANO); + game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT).startingBiome(BiomeId.VOLCANO); await game.runToMysteryEncounter(); expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.MYSTERIOUS_CHALLENGERS); diff --git a/test/mystery-encounter/encounters/part-timer-encounter.test.ts b/test/mystery-encounter/encounters/part-timer-encounter.test.ts index be985ea0593..4436f2e19fe 100644 --- a/test/mystery-encounter/encounters/part-timer-encounter.test.ts +++ b/test/mystery-encounter/encounters/part-timer-encounter.test.ts @@ -36,10 +36,11 @@ describe("Part-Timer - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves(); const biomeMap = new Map([ [BiomeId.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]], @@ -76,8 +77,7 @@ describe("Part-Timer - Mystery Encounter", () => { }); it("should not spawn outside of CIVILIZATION_ENCOUNTER_BIOMES", async () => { - game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON); - game.override.startingBiome(BiomeId.VOLCANO); + game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON).startingBiome(BiomeId.VOLCANO); await game.runToMysteryEncounter(); expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.PART_TIMER); diff --git a/test/mystery-encounter/encounters/safari-zone.test.ts b/test/mystery-encounter/encounters/safari-zone.test.ts index dcaf25dd512..a086272c721 100644 --- a/test/mystery-encounter/encounters/safari-zone.test.ts +++ b/test/mystery-encounter/encounters/safari-zone.test.ts @@ -38,10 +38,11 @@ describe("Safari Zone - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves(); const biomeMap = new Map([ [BiomeId.VOLCANO, [MysteryEncounterType.FIGHT_OR_FLIGHT]], @@ -72,8 +73,7 @@ describe("Safari Zone - Mystery Encounter", () => { }); it("should not spawn outside of the forest, swamp, or jungle biomes", async () => { - game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT); - game.override.startingBiome(BiomeId.VOLCANO); + game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT).startingBiome(BiomeId.VOLCANO); await game.runToMysteryEncounter(); expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.SAFARI_ZONE); diff --git a/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts b/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts index 6c8daed998c..12398049abd 100644 --- a/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts +++ b/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts @@ -82,8 +82,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { }); it("should run in waves that are X1", async () => { - game.override.startingWave(11); - game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON); + game.override.startingWave(11).mysteryEncounterTier(MysteryEncounterTier.COMMON); await game.runToMysteryEncounter(); @@ -91,8 +90,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { }); it("should run in waves that are X2", async () => { - game.override.startingWave(32); - game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON); + game.override.startingWave(32).mysteryEncounterTier(MysteryEncounterTier.COMMON); await game.runToMysteryEncounter(); @@ -100,8 +98,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { }); it("should run in waves that are X3", async () => { - game.override.startingWave(23); - game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON); + game.override.startingWave(23).mysteryEncounterTier(MysteryEncounterTier.COMMON); await game.runToMysteryEncounter(); diff --git a/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts b/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts index 0d5f67b8815..ed6d1c5bc4f 100644 --- a/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts @@ -40,10 +40,11 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves(); const biomeMap = new Map([ [BiomeId.VOLCANO, [MysteryEncounterType.FIGHT_OR_FLIGHT]], @@ -84,8 +85,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { }); it("should not spawn outside of HUMAN_TRANSITABLE_BIOMES", async () => { - game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT); - game.override.startingBiome(BiomeId.VOLCANO); + game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT).startingBiome(BiomeId.VOLCANO); await game.runToMysteryEncounter(); expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe( diff --git a/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts b/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts index 13d7c2502a6..ee2d7d8039b 100644 --- a/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts @@ -38,10 +38,11 @@ describe("The Pokemon Salesman - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves(); const biomeMap = new Map([ [BiomeId.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]], @@ -78,8 +79,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => { }); it("should not spawn outside of HUMAN_TRANSITABLE_BIOMES", async () => { - game.override.mysteryEncounterTier(MysteryEncounterTier.ULTRA); - game.override.startingBiome(BiomeId.VOLCANO); + game.override.mysteryEncounterTier(MysteryEncounterTier.ULTRA).startingBiome(BiomeId.VOLCANO); await game.runToMysteryEncounter(); expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.THE_POKEMON_SALESMAN); diff --git a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index 5926c1ed2e7..5f7c0f5a0fd 100644 --- a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -83,8 +83,7 @@ describe("The Strong Stuff - Mystery Encounter", () => { }); it("should not spawn outside of CAVE biome", async () => { - game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON); - game.override.startingBiome(BiomeId.MOUNTAIN); + game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON).startingBiome(BiomeId.MOUNTAIN); await game.runToMysteryEncounter(); expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.THE_STRONG_STUFF); diff --git a/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts b/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts index 87a3852615d..72f76358398 100644 --- a/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts @@ -44,10 +44,11 @@ describe("The Winstrate Challenge - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves(); const biomeMap = new Map([ [BiomeId.VOLCANO, [MysteryEncounterType.FIGHT_OR_FLIGHT]], @@ -86,8 +87,7 @@ describe("The Winstrate Challenge - Mystery Encounter", () => { }); it("should not spawn outside of HUMAN_TRANSITABLE_BIOMES", async () => { - game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT); - game.override.startingBiome(BiomeId.VOLCANO); + game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT).startingBiome(BiomeId.VOLCANO); await game.runToMysteryEncounter(); expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.THE_WINSTRATE_CHALLENGE); diff --git a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index 2014cb215a4..6e2da14dffc 100644 --- a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -51,10 +51,11 @@ describe("Trash to Treasure - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves(); vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( new Map([[BiomeId.CAVE, [MysteryEncounterType.TRASH_TO_TREASURE]]]), diff --git a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts index dc5c53a75e7..27d41ec66cb 100644 --- a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -38,10 +38,11 @@ describe("Weird Dream - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override + .mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves(); vi.spyOn(EncounterTransformationSequence, "doPokemonTransformationSequence").mockImplementation( () => new Promise(resolve => resolve()), ); diff --git a/test/mystery-encounter/mystery-encounter-utils.test.ts b/test/mystery-encounter/mystery-encounter-utils.test.ts index 80e2fb77f2b..f327d8f9e9c 100644 --- a/test/mystery-encounter/mystery-encounter-utils.test.ts +++ b/test/mystery-encounter/mystery-encounter-utils.test.ts @@ -250,6 +250,7 @@ describe("Mystery Encounter Utils", () => { it("gets species of specified types", () => { // Only 9 tiers are: Kyogre, Groudon, Rayquaza, Arceus, Zacian, Koraidon, Miraidon, Terapagos + // TODO: This has to be changed const result = getRandomSpeciesByStarterCost(9, undefined, [PokemonType.GROUND]); const pokeSpecies = getPokemonSpecies(result); expect(pokeSpecies.speciesId).toBe(SpeciesId.GROUDON); diff --git a/test/mystery-encounter/mystery-encounter.test.ts b/test/mystery-encounter/mystery-encounter.test.ts index 1f1d0f5826e..be1f153f8b1 100644 --- a/test/mystery-encounter/mystery-encounter.test.ts +++ b/test/mystery-encounter/mystery-encounter.test.ts @@ -24,8 +24,7 @@ describe("Mystery Encounters", () => { beforeEach(() => { game = new GameManager(phaserGame); scene = game.scene; - game.override.startingWave(11); - game.override.mysteryEncounterChance(100); + game.override.startingWave(11).mysteryEncounterChance(100); }); it("Spawns a mystery encounter", async () => { diff --git a/test/phases/mystery-encounter-phase.test.ts b/test/phases/mystery-encounter-phase.test.ts index d078c2398b4..b17682d6c74 100644 --- a/test/phases/mystery-encounter-phase.test.ts +++ b/test/phases/mystery-encounter-phase.test.ts @@ -27,10 +27,7 @@ describe("Mystery Encounter Phases", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.startingWave(11); - game.override.mysteryEncounterChance(100); - // Seed guarantees wild encounter to be replaced by ME - game.override.seed("test"); + game.override.startingWave(11).mysteryEncounterChance(100).seed("test"); // Seed guarantees wild encounter to be replaced by ME }); describe("MysteryEncounterPhase", () => { diff --git a/test/testUtils/helpers/moveHelper.ts b/test/testUtils/helpers/moveHelper.ts index 0f87fa9a4c1..d8c79c3bca8 100644 --- a/test/testUtils/helpers/moveHelper.ts +++ b/test/testUtils/helpers/moveHelper.ts @@ -21,6 +21,7 @@ export class MoveHelper extends GameManagerHelper { /** * Intercepts {@linkcode MoveEffectPhase} and mocks the phase's move's * accuracy to -1, guaranteeing a hit. + * @returns A promise that resolves once the next MoveEffectPhase has been reached (not run). */ public async forceHit(): Promise { await this.game.phaseInterceptor.to(MoveEffectPhase, false); @@ -31,7 +32,8 @@ export class MoveHelper extends GameManagerHelper { /** * Intercepts {@linkcode MoveEffectPhase} and mocks the phase's move's accuracy * to 0, guaranteeing a miss. - * @param firstTargetOnly - Whether the move should force miss on the first target only, in the case of multi-target moves. + * @param firstTargetOnly - Whether to only force a miss on the first target hit; default `false`. + * @returns A promise that resolves once the next MoveEffectPhase has been reached (not run). */ public async forceMiss(firstTargetOnly = false): Promise { await this.game.phaseInterceptor.to(MoveEffectPhase, false); diff --git a/test/ui/pokedex.test.ts b/test/ui/pokedex.test.ts index 573ce3fef89..53ebe04a4ed 100644 --- a/test/ui/pokedex.test.ts +++ b/test/ui/pokedex.test.ts @@ -502,13 +502,13 @@ describe("UI - Pokedex", () => { // Nab the pokemon that is selected for comparison later. // @ts-expect-error - `lastSpecies` is private - const selectedPokemon = pokedexHandler.lastSpecies.speciesId; + const selectedPokemon = pokedexHandler.lastSpeciesId.speciesId; for (let i = 0; i < 11; i++) { pokedexHandler.processInput(Button.DOWN); } // @ts-expect-error `lastSpecies` is private - expect(selectedPokemon).toEqual(pokedexHandler.lastSpecies.speciesId); + expect(selectedPokemon).toEqual(pokedexHandler.lastSpeciesId.speciesId); }, ); diff --git a/test/ui/transfer-item.test.ts b/test/ui/transfer-item.test.ts index b08c9823dcd..ef0fe502c6e 100644 --- a/test/ui/transfer-item.test.ts +++ b/test/ui/transfer-item.test.ts @@ -26,17 +26,18 @@ describe("UI - Transfer Items", () => { beforeEach(async () => { game = new GameManager(phaserGame); - game.override.battleStyle("single"); - game.override.startingLevel(100); - game.override.startingWave(1); - game.override.startingHeldItems([ - { name: "BERRY", count: 1, type: BerryType.SITRUS }, - { name: "BERRY", count: 2, type: BerryType.APICOT }, - { name: "BERRY", count: 2, type: BerryType.LUM }, - ]); - game.override.moveset([MoveId.DRAGON_CLAW]); - game.override.enemySpecies(SpeciesId.MAGIKARP); - game.override.enemyMoveset([MoveId.SPLASH]); + game.override + .battleStyle("single") + .startingLevel(100) + .startingWave(1) + .startingHeldItems([ + { name: "BERRY", count: 1, type: BerryType.SITRUS }, + { name: "BERRY", count: 2, type: BerryType.APICOT }, + { name: "BERRY", count: 2, type: BerryType.LUM }, + ]) + .moveset([MoveId.DRAGON_CLAW]) + .enemySpecies(SpeciesId.MAGIKARP) + .enemyMoveset([MoveId.SPLASH]); await game.classicMode.startBattle([SpeciesId.RAYQUAZA, SpeciesId.RAYQUAZA, SpeciesId.RAYQUAZA]); From 0918985a63cc8795bc9a45d8ceacdfaca5f12ee7 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sun, 15 Jun 2025 03:48:16 -0400 Subject: [PATCH 45/51] [Test] Remove unneeded `mockRestore` and `testTimeout` calls in tests https://github.com/pagefaultgames/pokerogue/pull/5927/ * Removed unnecessary test timeout parameters from test files We set it in vitest config anyways * Removed unneeded `mockRestore` calls We call `restoreAllMocks` after each test runs anyhow * Removed accidentall forgotten-about timeout * Revdrt magic bounce test file for now * Fixed ting * Fixed bug * Fixed import * Update test/data/status_effect.test.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update battle.test.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Ran bim --------- Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- test/abilities/ability_timing.test.ts | 2 +- test/abilities/beast_boost.test.ts | 6 +-- test/abilities/contrary.test.ts | 2 +- test/abilities/flash_fire.test.ts | 14 +++---- test/abilities/flower_veil.test.ts | 1 - test/abilities/healer.test.ts | 23 ++++------ test/abilities/intimidate.test.ts | 8 ++-- test/abilities/intrepid_sword.test.ts | 2 +- test/abilities/moxie.test.ts | 2 +- test/abilities/mycelium_might.test.ts | 6 +-- test/abilities/quick_draw.test.ts | 8 ++-- test/abilities/sand_spit.test.ts | 4 +- test/abilities/sheer_force.test.ts | 2 +- test/abilities/simple.test.ts | 2 +- test/abilities/stall.test.ts | 6 +-- test/abilities/super_luck.test.ts | 8 ++-- test/abilities/tera_shell.test.ts | 3 -- test/battle/battle-order.test.ts | 22 +++++----- test/battle/battle.test.ts | 42 ++++++++++--------- test/battle/double_battle.test.ts | 2 +- test/battle/special_battle.test.ts | 38 ++++++++--------- test/data/status_effect.test.ts | 6 +-- test/eggs/egg.test.ts | 1 - test/eggs/manaphy-egg.test.ts | 1 - test/escape-calculations.test.ts | 8 ++-- test/game-mode.test.ts | 2 - test/items/dire_hit.test.ts | 6 +-- test/items/exp_booster.test.ts | 2 +- test/items/leek.test.ts | 12 +++--- test/items/leftovers.test.ts | 2 +- test/items/light_ball.test.ts | 8 ++-- test/items/lock_capsule.test.ts | 2 +- test/items/metal_powder.test.ts | 8 ++-- test/items/scope_lens.test.ts | 4 +- test/items/temp_stat_stage_booster.test.ts | 10 ++--- test/moves/baton_pass.test.ts | 6 +-- test/moves/disable.test.ts | 12 +++--- test/moves/dynamax_cannon.test.ts | 16 +++---- test/moves/fusion_bolt.test.ts | 2 +- test/moves/fusion_flare_bolt.test.ts | 14 +++---- test/moves/gigaton_hammer.test.ts | 4 +- test/moves/growth.test.ts | 2 +- test/moves/guard_split.test.ts | 4 +- test/moves/magnet_rise.test.ts | 4 +- test/moves/power_split.test.ts | 4 +- test/moves/speed_swap.test.ts | 2 +- test/moves/spikes.test.ts | 8 ++-- test/moves/struggle.test.ts | 5 --- test/moves/tackle.test.ts | 4 +- test/moves/tail_whip.test.ts | 2 +- test/moves/tera_blast.test.ts | 2 +- test/moves/tidy_up.test.ts | 12 +++--- test/moves/u_turn.test.ts | 6 +-- .../a-trainers-test-encounter.test.ts | 2 - .../absolute-avarice-encounter.test.ts | 2 - ...an-offer-you-cant-refuse-encounter.test.ts | 2 - .../berries-abound-encounter.test.ts | 2 - .../bug-type-superfan-encounter.test.ts | 2 - .../clowning-around-encounter.test.ts | 2 - .../dancing-lessons-encounter.test.ts | 2 - .../encounters/delibirdy-encounter.test.ts | 2 - .../department-store-sale-encounter.test.ts | 2 - .../encounters/field-trip-encounter.test.ts | 2 - .../fiery-fallout-encounter.test.ts | 2 - .../fight-or-flight-encounter.test.ts | 2 - .../fun-and-games-encounter.test.ts | 2 - .../global-trade-system-encounter.test.ts | 2 - .../encounters/lost-at-sea-encounter.test.ts | 2 - .../mysterious-challengers-encounter.test.ts | 2 - .../encounters/part-timer-encounter.test.ts | 2 - .../encounters/safari-zone.test.ts | 2 - .../teleporting-hijinks-encounter.test.ts | 2 - .../the-expert-breeder-encounter.test.ts | 2 - .../the-pokemon-salesman-encounter.test.ts | 2 - .../the-strong-stuff-encounter.test.ts | 2 - .../the-winstrate-challenge-encounter.test.ts | 4 +- .../trash-to-treasure-encounter.test.ts | 2 - .../uncommon-breed-encounter.test.ts | 2 - .../encounters/weird-dream-encounter.test.ts | 2 - test/phases/phases.test.ts | 2 +- test/phases/select-modifier-phase.test.ts | 2 - test/reload.test.ts | 16 +++---- test/ui/pokedex.test.ts | 14 +++---- test/ui/starter-select.test.ts | 18 ++++---- test/ui/transfer-item.test.ts | 4 +- 85 files changed, 207 insertions(+), 287 deletions(-) diff --git a/test/abilities/ability_timing.test.ts b/test/abilities/ability_timing.test.ts index 2ba1e821f8a..124591597be 100644 --- a/test/abilities/ability_timing.test.ts +++ b/test/abilities/ability_timing.test.ts @@ -50,5 +50,5 @@ describe("Ability Timing", () => { await game.phaseInterceptor.to("MessagePhase"); expect(i18next.t).toHaveBeenCalledWith("battle:statFell", expect.objectContaining({ count: 1 })); - }, 5000); + }); }); diff --git a/test/abilities/beast_boost.test.ts b/test/abilities/beast_boost.test.ts index 1d9eb5c5f2e..8be9156ce07 100644 --- a/test/abilities/beast_boost.test.ts +++ b/test/abilities/beast_boost.test.ts @@ -47,7 +47,7 @@ describe("Abilities - Beast Boost", () => { await game.phaseInterceptor.to("VictoryPhase"); expect(playerPokemon.getStatStage(Stat.DEF)).toBe(1); - }, 20000); + }); it("should use in-battle overriden stats when determining the stat stage to raise by 1", async () => { game.override.enemyMoveset([MoveId.GUARD_SPLIT]); @@ -66,7 +66,7 @@ describe("Abilities - Beast Boost", () => { await game.phaseInterceptor.to("VictoryPhase"); expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1); - }, 20000); + }); it("should have order preference in case of stat ties", async () => { // Order preference follows the order of EFFECTIVE_STAT @@ -84,5 +84,5 @@ describe("Abilities - Beast Boost", () => { await game.phaseInterceptor.to("VictoryPhase"); expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1); - }, 20000); + }); }); diff --git a/test/abilities/contrary.test.ts b/test/abilities/contrary.test.ts index 0e47862f3e8..6962abb0fec 100644 --- a/test/abilities/contrary.test.ts +++ b/test/abilities/contrary.test.ts @@ -36,7 +36,7 @@ describe("Abilities - Contrary", () => { const enemyPokemon = game.scene.getEnemyPokemon()!; expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1); - }, 20000); + }); describe("With Clear Body", () => { it("should apply positive effects", async () => { diff --git a/test/abilities/flash_fire.test.ts b/test/abilities/flash_fire.test.ts index c3477388912..fe17013f49f 100644 --- a/test/abilities/flash_fire.test.ts +++ b/test/abilities/flash_fire.test.ts @@ -44,7 +44,7 @@ describe("Abilities - Flash Fire", () => { game.move.select(MoveId.SPLASH); await game.phaseInterceptor.to(TurnEndPhase); expect(blissey.hp).toBe(blissey.getMaxHp()); - }, 20000); + }); it("not activate if the Pokémon is protected from the Fire-type move", async () => { game.override.enemyMoveset([MoveId.EMBER]).moveset([MoveId.PROTECT]); @@ -55,7 +55,7 @@ describe("Abilities - Flash Fire", () => { game.move.select(MoveId.PROTECT); await game.phaseInterceptor.to(TurnEndPhase); expect(blissey!.getTag(BattlerTagType.FIRE_BOOST)).toBeUndefined(); - }, 20000); + }); it("activated by Will-O-Wisp", async () => { game.override.enemyMoveset([MoveId.WILL_O_WISP]).moveset(MoveId.SPLASH); @@ -70,7 +70,7 @@ describe("Abilities - Flash Fire", () => { await game.phaseInterceptor.to(TurnEndPhase); expect(blissey!.getTag(BattlerTagType.FIRE_BOOST)).toBeDefined(); - }, 20000); + }); it("activated after being frozen", async () => { game.override.enemyMoveset([MoveId.EMBER]).moveset(MoveId.SPLASH).statusEffect(StatusEffect.FREEZE); @@ -82,7 +82,7 @@ describe("Abilities - Flash Fire", () => { await game.phaseInterceptor.to(TurnEndPhase); expect(blissey!.getTag(BattlerTagType.FIRE_BOOST)).toBeDefined(); - }, 20000); + }); it("not passing with baton pass", async () => { game.override.enemyMoveset([MoveId.EMBER]).moveset([MoveId.BATON_PASS]); @@ -98,7 +98,7 @@ describe("Abilities - Flash Fire", () => { const chansey = game.scene.getPlayerPokemon()!; expect(game.scene.getPlayerPokemon()!.species.speciesId).toBe(SpeciesId.CHANSEY); expect(chansey!.getTag(BattlerTagType.FIRE_BOOST)).toBeUndefined(); - }, 20000); + }); it("boosts Fire-type move when the ability is activated", async () => { game.override @@ -126,7 +126,7 @@ describe("Abilities - Flash Fire", () => { const flashFireDmg = initialHP - blissey.hp; expect(flashFireDmg).toBeGreaterThan(originalDmg); - }, 20000); + }); it("still activates regardless of accuracy check", async () => { game.override @@ -158,5 +158,5 @@ describe("Abilities - Flash Fire", () => { const flashFireDmg = initialHP - blissey.hp; expect(flashFireDmg).toBeGreaterThan(originalDmg); - }, 20000); + }); }); diff --git a/test/abilities/flower_veil.test.ts b/test/abilities/flower_veil.test.ts index c59d8c6eb29..d3e99185c2f 100644 --- a/test/abilities/flower_veil.test.ts +++ b/test/abilities/flower_veil.test.ts @@ -89,7 +89,6 @@ describe("Abilities - Flower Veil", () => { await game.move.selectEnemyMove(MoveId.THUNDER_WAVE); await game.toNextTurn(); expect(game.scene.getPlayerPokemon()!.status).toBeUndefined(); - vi.spyOn(allMoves[MoveId.THUNDER_WAVE], "accuracy", "get").mockClear(); }); it("should not prevent status conditions for a non-grass user and its non-grass allies", async () => { diff --git a/test/abilities/healer.test.ts b/test/abilities/healer.test.ts index 9d252523cc8..94272848e16 100644 --- a/test/abilities/healer.test.ts +++ b/test/abilities/healer.test.ts @@ -4,17 +4,15 @@ import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it, vi, type MockInstance } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { isNullOrUndefined } from "#app/utils/common"; import { allAbilities } from "#app/data/data-lists"; import type Pokemon from "#app/field/pokemon"; -import type { PostTurnResetStatusAbAttr } from "#app/@types/ability-types"; +import { PostTurnResetStatusAbAttr } from "#app/data/abilities/ability"; describe("Abilities - Healer", () => { let phaserGame: Phaser.Game; let game: GameManager; - let healerAttrSpy: MockInstance; - let healerAttr: PostTurnResetStatusAbAttr; beforeAll(() => { phaserGame = new Phaser.Game({ @@ -24,7 +22,6 @@ describe("Abilities - Healer", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - healerAttrSpy.mockRestore(); }); beforeEach(() => { @@ -38,30 +35,28 @@ describe("Abilities - Healer", () => { .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); - healerAttr = allAbilities[AbilityId.HEALER].getAttrs("PostTurnResetStatusAbAttr")[0]; - healerAttrSpy = vi - .spyOn(healerAttr, "getCondition") - .mockReturnValue((pokemon: Pokemon) => !isNullOrUndefined(pokemon.getAlly())); + // Mock healer to have a 100% chance of healing its ally + vi.spyOn(allAbilities[AbilityId.HEALER].getAttrs("PostTurnResetStatusAbAttr")[0], "getCondition").mockReturnValue( + (pokemon: Pokemon) => !isNullOrUndefined(pokemon.getAlly()), + ); }); it("should not queue a message phase for healing if the ally has fainted", async () => { + const abSpy = vi.spyOn(PostTurnResetStatusAbAttr.prototype, "canApplyPostTurn"); game.override.moveset([MoveId.SPLASH, MoveId.LUNAR_DANCE]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]); + const user = game.scene.getPlayerPokemon()!; - // Only want one magikarp to have the ability. + // Only want one magikarp to have the ability vi.spyOn(user, "getAbility").mockReturnValue(allAbilities[AbilityId.HEALER]); game.move.select(MoveId.SPLASH); // faint the ally game.move.select(MoveId.LUNAR_DANCE, 1); - const abSpy = vi.spyOn(healerAttr, "canApplyPostTurn"); await game.phaseInterceptor.to("TurnEndPhase"); // It's not enough to just test that the ally still has its status. // We need to ensure that the ability failed to meet its condition expect(abSpy).toHaveReturnedWith(false); - - // Explicitly restore the mock to ensure pollution doesn't happen - abSpy.mockRestore(); }); it("should heal the status of an ally if the ally has a status", async () => { diff --git a/test/abilities/intimidate.test.ts b/test/abilities/intimidate.test.ts index cc75b257686..3dcd9bcd129 100644 --- a/test/abilities/intimidate.test.ts +++ b/test/abilities/intimidate.test.ts @@ -62,7 +62,7 @@ describe("Abilities - Intimidate", () => { expect(playerPokemon.species.speciesId).toBe(SpeciesId.POOCHYENA); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-2); - }, 20000); + }); it("should lower ATK stat stage by 1 for every enemy Pokemon in a double battle on entry", async () => { game.override.battleStyle("double").startingWave(3); @@ -85,7 +85,7 @@ describe("Abilities - Intimidate", () => { expect(enemyField[1].getStatStage(Stat.ATK)).toBe(-2); expect(playerField[0].getStatStage(Stat.ATK)).toBe(-2); expect(playerField[1].getStatStage(Stat.ATK)).toBe(-2); - }, 20000); + }); it("should not activate again if there is no switch or new entry", async () => { game.override.startingWave(2).moveset([MoveId.SPLASH]); @@ -102,7 +102,7 @@ describe("Abilities - Intimidate", () => { expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1); - }, 20000); + }); it("should lower ATK stat stage by 1 for every switch", async () => { game.override.moveset([MoveId.SPLASH]).enemyMoveset([MoveId.VOLT_SWITCH]).startingWave(5); @@ -129,5 +129,5 @@ describe("Abilities - Intimidate", () => { expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-3); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); - }, 200000); + }); }); diff --git a/test/abilities/intrepid_sword.test.ts b/test/abilities/intrepid_sword.test.ts index 3a24f44a78e..d9b81e9552e 100644 --- a/test/abilities/intrepid_sword.test.ts +++ b/test/abilities/intrepid_sword.test.ts @@ -39,5 +39,5 @@ describe("Abilities - Intrepid Sword", () => { expect(playerPokemon.getStatStage(Stat.ATK)).toBe(1); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1); - }, 20000); + }); }); diff --git a/test/abilities/moxie.test.ts b/test/abilities/moxie.test.ts index 7631a0dfd2d..a85ed081448 100644 --- a/test/abilities/moxie.test.ts +++ b/test/abilities/moxie.test.ts @@ -49,7 +49,7 @@ describe("Abilities - Moxie", () => { await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(VictoryPhase); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(1); - }, 20000); + }); // TODO: Activate this test when MOXIE is corrected to work on faint and not on battle victory it.todo( diff --git a/test/abilities/mycelium_might.test.ts b/test/abilities/mycelium_might.test.ts index 1f236f2c2fe..7bfb3fb7b81 100644 --- a/test/abilities/mycelium_might.test.ts +++ b/test/abilities/mycelium_might.test.ts @@ -63,7 +63,7 @@ describe("Abilities - Mycelium Might", () => { // Despite the opponent's ability (Clear Body), its ATK stat stage is still reduced. expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(-1); - }, 20000); + }); it("will still go first if a status move that is in a higher priority bracket than the opponent's move is used", async () => { game.override.enemyMoveset(MoveId.TACKLE); @@ -86,7 +86,7 @@ describe("Abilities - Mycelium Might", () => { await game.phaseInterceptor.to(TurnEndPhase); // Despite the opponent's ability (Clear Body), its ATK stat stage is still reduced. expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(-1); - }, 20000); + }); it("will not affect non-status moves", async () => { await game.classicMode.startBattle([SpeciesId.REGIELEKI]); @@ -105,5 +105,5 @@ describe("Abilities - Mycelium Might", () => { // This means that the commandOrder should be identical to the speedOrder expect(speedOrder).toEqual([playerIndex, enemyIndex]); expect(commandOrder).toEqual([playerIndex, enemyIndex]); - }, 20000); + }); }); diff --git a/test/abilities/quick_draw.test.ts b/test/abilities/quick_draw.test.ts index 02c2ff86a56..11418f31375 100644 --- a/test/abilities/quick_draw.test.ts +++ b/test/abilities/quick_draw.test.ts @@ -54,8 +54,8 @@ describe("Abilities - Quick Draw", () => { expect(pokemon.isFainted()).toBe(false); expect(enemy.isFainted()).toBe(true); - expect(pokemon.waveData.abilitiesApplied).contain(AbilityId.QUICK_DRAW); - }, 20000); + expect(pokemon.waveData.abilitiesApplied).toContain(AbilityId.QUICK_DRAW); + }); test( "does not triggered by non damage moves", @@ -96,6 +96,6 @@ describe("Abilities - Quick Draw", () => { expect(pokemon.isFainted()).toBe(true); expect(enemy.isFainted()).toBe(false); - expect(pokemon.waveData.abilitiesApplied).contain(AbilityId.QUICK_DRAW); - }, 20000); + expect(pokemon.waveData.abilitiesApplied).toContain(AbilityId.QUICK_DRAW); + }); }); diff --git a/test/abilities/sand_spit.test.ts b/test/abilities/sand_spit.test.ts index 9be23e7f7c2..62d5d782fc5 100644 --- a/test/abilities/sand_spit.test.ts +++ b/test/abilities/sand_spit.test.ts @@ -40,7 +40,7 @@ describe("Abilities - Sand Spit", () => { await game.toNextTurn(); expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SANDSTORM); - }, 20000); + }); it("should trigger even when fainting", async () => { game.override.enemyMoveset([MoveId.TACKLE]).enemyLevel(100).startingLevel(1); @@ -61,5 +61,5 @@ describe("Abilities - Sand Spit", () => { await game.toNextTurn(); expect(game.scene.arena.weather?.weatherType).not.toBe(WeatherType.SANDSTORM); - }, 20000); + }); }); diff --git a/test/abilities/sheer_force.test.ts b/test/abilities/sheer_force.test.ts index 922025d8be2..b597afd8293 100644 --- a/test/abilities/sheer_force.test.ts +++ b/test/abilities/sheer_force.test.ts @@ -69,7 +69,7 @@ describe("Abilities - Sheer Force", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(bindMove.calculateBattlePower).toHaveLastReturnedWith(bindMove.power); - }, 20000); + }); it("Sheer Force does not boost the base damage of moves with no secondary effect", async () => { game.override.moveset([MoveId.TACKLE]); diff --git a/test/abilities/simple.test.ts b/test/abilities/simple.test.ts index 9df70848f70..703f8fcffac 100644 --- a/test/abilities/simple.test.ts +++ b/test/abilities/simple.test.ts @@ -36,5 +36,5 @@ describe("Abilities - Simple", () => { const enemyPokemon = game.scene.getEnemyPokemon()!; expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-2); - }, 20000); + }); }); diff --git a/test/abilities/stall.test.ts b/test/abilities/stall.test.ts index df40bed3e90..74fd2f67f83 100644 --- a/test/abilities/stall.test.ts +++ b/test/abilities/stall.test.ts @@ -53,7 +53,7 @@ describe("Abilities - Stall", () => { // The opponent Pokemon (with Stall) goes last despite having higher speed than the player Pokemon. expect(speedOrder).toEqual([enemyIndex, playerIndex]); expect(commandOrder).toEqual([playerIndex, enemyIndex]); - }, 20000); + }); it("Pokemon with Stall will go first if a move that is in a higher priority bracket than the opponent's move is used", async () => { await game.classicMode.startBattle([SpeciesId.SHUCKLE]); @@ -71,7 +71,7 @@ describe("Abilities - Stall", () => { // The player Pokemon goes second because its move is in a lower priority bracket. expect(speedOrder).toEqual([enemyIndex, playerIndex]); expect(commandOrder).toEqual([enemyIndex, playerIndex]); - }, 20000); + }); it("If both Pokemon have stall and use the same move, speed is used to determine who goes first.", async () => { game.override.ability(AbilityId.STALL); @@ -91,5 +91,5 @@ describe("Abilities - Stall", () => { // The player Pokemon (with Stall) goes second because its speed is lower. expect(speedOrder).toEqual([enemyIndex, playerIndex]); expect(commandOrder).toEqual([enemyIndex, playerIndex]); - }, 20000); + }); }); diff --git a/test/abilities/super_luck.test.ts b/test/abilities/super_luck.test.ts index e94a4cf21f0..60ed2534fe9 100644 --- a/test/abilities/super_luck.test.ts +++ b/test/abilities/super_luck.test.ts @@ -30,13 +30,13 @@ describe("Abilities - Super Luck", () => { .enemyMoveset(MoveId.SPLASH); }); - it("should increase the crit stage of a user by 1", async () => { + it("should increase the user's crit stage by 1", async () => { await game.classicMode.startBattle([SpeciesId.MAGIKARP]); const enemy = game.scene.getEnemyPokemon()!; - const fn = vi.spyOn(enemy, "getCritStage"); + const critSpy = vi.spyOn(enemy, "getCritStage"); // crit stage is called on enemy + game.move.select(MoveId.TACKLE); await game.phaseInterceptor.to("BerryPhase"); - expect(fn).toHaveReturnedWith(1); - fn.mockRestore(); + expect(critSpy).toHaveReturnedWith(1); }); }); diff --git a/test/abilities/tera_shell.test.ts b/test/abilities/tera_shell.test.ts index 26babca240d..a52a01862ff 100644 --- a/test/abilities/tera_shell.test.ts +++ b/test/abilities/tera_shell.test.ts @@ -93,8 +93,6 @@ describe("Abilities - Tera Shell", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(spy).toHaveLastReturnedWith(1); expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp() - 40); - - spy.mockRestore(); }); it("should change the effectiveness of all strikes of a multi-strike move", async () => { @@ -114,6 +112,5 @@ describe("Abilities - Tera Shell", () => { expect(spy).toHaveLastReturnedWith(0.5); } expect(spy).toHaveReturnedTimes(2); - spy.mockRestore(); }); }); diff --git a/test/battle/battle-order.test.ts b/test/battle/battle-order.test.ts index c969e1767a3..114592c8c8e 100644 --- a/test/battle/battle-order.test.ts +++ b/test/battle/battle-order.test.ts @@ -49,7 +49,7 @@ describe("Battle order", () => { const order = phase.getCommandOrder(); expect(order[0]).toBe(enemyPokemonIndex); expect(order[1]).toBe(playerPokemonIndex); - }, 20000); + }); it("Player faster than opponent 150 vs 50", async () => { await game.classicMode.startBattle([SpeciesId.BULBASAUR]); @@ -68,7 +68,7 @@ describe("Battle order", () => { const order = phase.getCommandOrder(); expect(order[0]).toBe(playerPokemonIndex); expect(order[1]).toBe(enemyPokemonIndex); - }, 20000); + }); it("double - both opponents faster than player 50/50 vs 150/150", async () => { game.override.battleStyle("double"); @@ -92,7 +92,7 @@ describe("Battle order", () => { expect(order.slice(0, 2).includes(enemyIndices[1])).toBe(true); expect(order.slice(2, 4).includes(playerIndices[0])).toBe(true); expect(order.slice(2, 4).includes(playerIndices[1])).toBe(true); - }, 20000); + }); it("double - speed tie except 1 - 100/100 vs 100/150", async () => { game.override.battleStyle("double"); @@ -112,11 +112,10 @@ describe("Battle order", () => { const phase = game.scene.phaseManager.getCurrentPhase() as TurnStartPhase; const order = phase.getCommandOrder(); + // enemy 2 should be first, followed by some other assortment of the other 3 pokemon expect(order[0]).toBe(enemyIndices[1]); - expect(order.slice(1, 4).includes(enemyIndices[0])).toBe(true); - expect(order.slice(1, 4).includes(playerIndices[0])).toBe(true); - expect(order.slice(1, 4).includes(playerIndices[1])).toBe(true); - }, 20000); + expect(order.slice(1, 4)).toEqual(expect.arrayContaining([enemyIndices[0], ...playerIndices])); + }); it("double - speed tie 100/150 vs 100/150", async () => { game.override.battleStyle("double"); @@ -137,9 +136,8 @@ describe("Battle order", () => { const phase = game.scene.phaseManager.getCurrentPhase() as TurnStartPhase; const order = phase.getCommandOrder(); - expect(order.slice(0, 2).includes(playerIndices[1])).toBe(true); - expect(order.slice(0, 2).includes(enemyIndices[1])).toBe(true); - expect(order.slice(2, 4).includes(playerIndices[0])).toBe(true); - expect(order.slice(2, 4).includes(enemyIndices[0])).toBe(true); - }, 20000); + // P2/E2 should be randomly first/second, then P1/E1 randomly 3rd/4th + expect(order.slice(0, 2)).toStrictEqual(expect.arrayContaining([playerIndices[1], enemyIndices[1]])); + expect(order.slice(2, 4)).toStrictEqual(expect.arrayContaining([playerIndices[0], enemyIndices[0]])); + }); }); diff --git a/test/battle/battle.test.ts b/test/battle/battle.test.ts index 60e09607ffe..a71dca111e3 100644 --- a/test/battle/battle.test.ts +++ b/test/battle/battle.test.ts @@ -60,7 +60,7 @@ describe("Test Battle Phase", () => { expect(game.scene.ui?.getMode()).toBe(UiMode.TITLE); expect(game.scene.gameData.gender).toBe(PlayerGender.MALE); - }, 20000); + }); it("test phase interceptor with prompt with preparation for a future prompt", async () => { await game.phaseInterceptor.run(LoginPhase); @@ -81,20 +81,20 @@ describe("Test Battle Phase", () => { expect(game.scene.ui?.getMode()).toBe(UiMode.TITLE); expect(game.scene.gameData.gender).toBe(PlayerGender.MALE); - }, 20000); + }); it("newGame one-liner", async () => { await game.classicMode.startBattle(); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); - }, 20000); + expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); + }); it("do attack wave 3 - single battle - regular - OHKO", async () => { - game.override.enemySpecies(SpeciesId.RATTATA).startingLevel(2000).battleStyle("single"); + game.override.enemySpecies(SpeciesId.RATTATA).startingLevel(2000).battleStyle("single").startingWave(3); await game.classicMode.startBattle([SpeciesId.MEWTWO]); game.move.use(MoveId.TACKLE); await game.phaseInterceptor.to("SelectModifierPhase"); - }, 20000); + }); it("do attack wave 3 - single battle - regular - NO OHKO with opponent using non damage attack", async () => { game.override @@ -108,7 +108,7 @@ describe("Test Battle Phase", () => { await game.classicMode.startBattle([SpeciesId.MEWTWO]); game.move.select(MoveId.TACKLE); await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase, false); - }, 20000); + }); it("load 100% data file", async () => { await game.importData("./test/testUtils/saves/everything.prsv"); @@ -117,14 +117,16 @@ describe("Test Battle Phase", () => { return species.caughtAttr !== 0n; }).length; expect(caughtCount).toBe(Object.keys(allSpecies).length); - }, 20000); + }); it("start battle with selected team", async () => { await game.classicMode.startBattle([SpeciesId.CHARIZARD, SpeciesId.CHANSEY, SpeciesId.MEW]); - expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.CHARIZARD); - expect(game.scene.getPlayerParty()[1].species.speciesId).toBe(SpeciesId.CHANSEY); - expect(game.scene.getPlayerParty()[2].species.speciesId).toBe(SpeciesId.MEW); - }, 20000); + expect(game.scene.getPlayerParty().map(p => p.species.speciesId)).toEqual([ + SpeciesId.CHARIZARD, + SpeciesId.CHANSEY, + SpeciesId.MEW, + ]); + }); it("test remove random battle seed int", async () => { for (let i = 0; i < 10; i++) { @@ -138,12 +140,12 @@ describe("Test Battle Phase", () => { await game.phaseInterceptor.run(LoginPhase).catch(e => { expect(e).toBe("Wrong phase: this is SelectGenderPhase and not LoginPhase"); }); - }, 20000); + }); it("wrong phase but skip", async () => { await game.phaseInterceptor.run(LoginPhase); await game.phaseInterceptor.run(LoginPhase, () => game.isCurrentPhase(SelectGenderPhase)); - }, 20000); + }); it("good run", async () => { await game.phaseInterceptor.run(LoginPhase); @@ -158,7 +160,7 @@ describe("Test Battle Phase", () => { ); await game.phaseInterceptor.run(SelectGenderPhase, () => game.isCurrentPhase(TitlePhase)); await game.phaseInterceptor.run(TitlePhase); - }, 20000); + }); it("good run from select gender to title", async () => { await game.phaseInterceptor.run(LoginPhase); @@ -172,7 +174,7 @@ describe("Test Battle Phase", () => { () => game.isCurrentPhase(TitlePhase), ); await game.phaseInterceptor.runFrom(SelectGenderPhase).to(TitlePhase); - }, 20000); + }); it("good run to SummonPhase phase", async () => { await game.phaseInterceptor.run(LoginPhase); @@ -193,7 +195,7 @@ describe("Test Battle Phase", () => { selectStarterPhase.initBattle(starters); }); await game.phaseInterceptor.runFrom(SelectGenderPhase).to(SummonPhase); - }, 20000); + }); it("2vs1", async () => { game.override.battleStyle("single"); @@ -274,7 +276,7 @@ describe("Test Battle Phase", () => { game.move.select(moveToUse); await game.toNextTurn(); expect(game.scene.currentBattle.turn).toBeGreaterThan(turn); - }, 20000); + }); it("does not set new weather if staying in same biome", async () => { const moveToUse = MoveId.SPLASH; @@ -298,7 +300,7 @@ describe("Test Battle Phase", () => { await game.toNextWave(); expect(game.scene.arena.trySetWeather).not.toHaveBeenCalled(); expect(game.scene.currentBattle.waveIndex).toBeGreaterThan(waveIndex); - }, 20000); + }); it("does not force switch if active pokemon faints at same time as enemy mon and is revived in post-battle", async () => { const moveToUse = MoveId.TAKE_DOWN; @@ -329,5 +331,5 @@ describe("Test Battle Phase", () => { () => game.isCurrentPhase(NextEncounterPhase), ); await game.phaseInterceptor.to(SwitchPhase); - }, 20000); + }); }); diff --git a/test/battle/double_battle.test.ts b/test/battle/double_battle.test.ts index 54c573813d7..8e606a99ae0 100644 --- a/test/battle/double_battle.test.ts +++ b/test/battle/double_battle.test.ts @@ -56,7 +56,7 @@ describe("Double Battles", () => { await game.phaseInterceptor.to(TurnInitPhase); expect(game.scene.getPlayerField().filter(p => !p.isFainted())).toHaveLength(2); - }, 20000); + }); it("randomly chooses between single and double battles if there is no battle type override", async () => { let rngSweepProgress = 0; // Will simulate RNG rolls by slowly increasing from 0 to 1 diff --git a/test/battle/special_battle.test.ts b/test/battle/special_battle.test.ts index f0c77668093..4988e526b1e 100644 --- a/test/battle/special_battle.test.ts +++ b/test/battle/special_battle.test.ts @@ -1,4 +1,3 @@ -import { CommandPhase } from "#app/phases/command-phase"; import { UiMode } from "#enums/ui-mode"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; @@ -32,66 +31,67 @@ describe("Test Battle Phase", () => { .enemyMoveset(MoveId.TACKLE); }); + // TODO: Make these into `it.each`es it("startBattle 2vs1 boss", async () => { game.override.battleStyle("single").startingWave(10); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); - }, 20000); + expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); + }); it("startBattle 2vs2 boss", async () => { game.override.battleStyle("double").startingWave(10); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); - }, 20000); + expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); + }); it("startBattle 2vs2 trainer", async () => { game.override.battleStyle("double").startingWave(5); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); - }, 20000); + expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); + }); it("startBattle 2vs1 trainer", async () => { game.override.battleStyle("single").startingWave(5); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); - }, 20000); + expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); + }); it("startBattle 2vs1 rival", async () => { game.override.battleStyle("single").startingWave(8); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); - }, 20000); + expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); + }); it("startBattle 2vs2 rival", async () => { game.override.battleStyle("double").startingWave(8); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); - }, 20000); + expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); + }); it("startBattle 1vs1 trainer", async () => { game.override.battleStyle("single").startingWave(5); await game.classicMode.startBattle([SpeciesId.BLASTOISE]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); - }, 20000); + expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); + }); it("startBattle 2vs2 trainer", async () => { game.override.battleStyle("double").startingWave(5); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); - }, 20000); + expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); + }); it("startBattle 4vs2 trainer", async () => { game.override.battleStyle("double").startingWave(5); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD, SpeciesId.DARKRAI, SpeciesId.GABITE]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); - expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); - }, 20000); + expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); + }); }); diff --git a/test/data/status_effect.test.ts b/test/data/status_effect.test.ts index 06e11dfeb76..bc2936d8fe2 100644 --- a/test/data/status_effect.test.ts +++ b/test/data/status_effect.test.ts @@ -13,7 +13,7 @@ import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/testUtils/gameManager"; import { mockI18next } from "#test/testUtils/testUtils"; -import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; const pokemonName = "PKM"; const sourceText = "SOURCE"; @@ -294,10 +294,6 @@ describe("Status Effect Messages", () => { expect(text).toBe("statusEffect:burn.overlap"); }); }); - - afterEach(() => { - vi.resetAllMocks(); - }); }); describe("Status Effects", () => { diff --git a/test/eggs/egg.test.ts b/test/eggs/egg.test.ts index e37a7c00aa9..7792756a8a3 100644 --- a/test/eggs/egg.test.ts +++ b/test/eggs/egg.test.ts @@ -25,7 +25,6 @@ describe("Egg Generation Tests", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.restoreAllMocks(); }); beforeEach(async () => { diff --git a/test/eggs/manaphy-egg.test.ts b/test/eggs/manaphy-egg.test.ts index 4dc38ef10b6..a58dfa9beba 100644 --- a/test/eggs/manaphy-egg.test.ts +++ b/test/eggs/manaphy-egg.test.ts @@ -21,7 +21,6 @@ describe("Manaphy Eggs", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.restoreAllMocks(); }); beforeEach(async () => { diff --git a/test/escape-calculations.test.ts b/test/escape-calculations.test.ts index 8a81bf2c6b8..2cc0934f7c1 100644 --- a/test/escape-calculations.test.ts +++ b/test/escape-calculations.test.ts @@ -94,7 +94,7 @@ describe("Escape chance calculations", () => { phase.attemptRunAway(playerPokemon, enemyField, escapePercentage); expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance); } - }, 20000); + }); it("double non-boss opponent", async () => { game.override.battleStyle("double"); @@ -180,7 +180,7 @@ describe("Escape chance calculations", () => { escapeChances[i].pokemonSpeedRatio * totalEnemySpeed, ); } - }, 20000); + }); it("single boss opponent", async () => { game.override.startingWave(10); @@ -259,7 +259,7 @@ describe("Escape chance calculations", () => { phase.attemptRunAway(playerPokemon, enemyField, escapePercentage); expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance); } - }, 20000); + }); it("double boss opponent", async () => { game.override.battleStyle("double").startingWave(10); @@ -357,5 +357,5 @@ describe("Escape chance calculations", () => { escapeChances[i].pokemonSpeedRatio * totalEnemySpeed, ); } - }, 20000); + }); }); diff --git a/test/game-mode.test.ts b/test/game-mode.test.ts index 4a53739c45e..c5ce1e02852 100644 --- a/test/game-mode.test.ts +++ b/test/game-mode.test.ts @@ -15,8 +15,6 @@ describe("game-mode", () => { }); afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); beforeEach(() => { game = new GameManager(phaserGame); diff --git a/test/items/dire_hit.test.ts b/test/items/dire_hit.test.ts index f60adebac36..25fe9c8b876 100644 --- a/test/items/dire_hit.test.ts +++ b/test/items/dire_hit.test.ts @@ -37,7 +37,7 @@ describe("Items - Dire Hit", () => { .moveset([MoveId.POUND]) .startingHeldItems([{ name: "DIRE_HIT" }]) .battleStyle("single"); - }, 20000); + }); it("should raise CRIT stage by 1", async () => { await game.classicMode.startBattle([SpeciesId.GASTLY]); @@ -51,7 +51,7 @@ describe("Items - Dire Hit", () => { await game.phaseInterceptor.to(TurnEndPhase); expect(enemyPokemon.getCritStage).toHaveReturnedWith(1); - }, 20000); + }); it("should renew how many battles are left of existing DIRE_HIT when picking up new DIRE_HIT", async () => { game.override.itemRewards([{ name: "DIRE_HIT" }]); @@ -93,5 +93,5 @@ describe("Items - Dire Hit", () => { } } expect(count).toBe(1); - }, 20000); + }); }); diff --git a/test/items/exp_booster.test.ts b/test/items/exp_booster.test.ts index 96a8571785e..44d7721aba2 100644 --- a/test/items/exp_booster.test.ts +++ b/test/items/exp_booster.test.ts @@ -34,5 +34,5 @@ describe("EXP Modifier Items", () => { const expHolder = new NumberHolder(partyMember.exp); game.scene.applyModifiers(PokemonExpBoosterModifier, true, partyMember, expHolder); expect(expHolder.value).toBe(440); - }, 20000); + }); }); diff --git a/test/items/leek.test.ts b/test/items/leek.test.ts index 05ee77c991a..eedb6667b9b 100644 --- a/test/items/leek.test.ts +++ b/test/items/leek.test.ts @@ -43,7 +43,7 @@ describe("Items - Leek", () => { await game.phaseInterceptor.to(TurnEndPhase); expect(enemyMember.getCritStage).toHaveReturnedWith(2); - }, 20000); + }); it("should raise CRIT stage by 2 when held by GALAR_FARFETCHD", async () => { await game.classicMode.startBattle([SpeciesId.GALAR_FARFETCHD]); @@ -57,7 +57,7 @@ describe("Items - Leek", () => { await game.phaseInterceptor.to(TurnEndPhase); expect(enemyMember.getCritStage).toHaveReturnedWith(2); - }, 20000); + }); it("should raise CRIT stage by 2 when held by SIRFETCHD", async () => { await game.classicMode.startBattle([SpeciesId.SIRFETCHD]); @@ -71,7 +71,7 @@ describe("Items - Leek", () => { await game.phaseInterceptor.to(TurnEndPhase); expect(enemyMember.getCritStage).toHaveReturnedWith(2); - }, 20000); + }); it("should raise CRIT stage by 2 when held by FARFETCHD line fused with Pokemon", async () => { // Randomly choose from the Farfetch'd line @@ -99,7 +99,7 @@ describe("Items - Leek", () => { await game.phaseInterceptor.to(TurnEndPhase); expect(enemyMember.getCritStage).toHaveReturnedWith(2); - }, 20000); + }); it("should raise CRIT stage by 2 when held by Pokemon fused with FARFETCHD line", async () => { // Randomly choose from the Farfetch'd line @@ -127,7 +127,7 @@ describe("Items - Leek", () => { await game.phaseInterceptor.to(TurnEndPhase); expect(enemyMember.getCritStage).toHaveReturnedWith(2); - }, 20000); + }); it("should not raise CRIT stage when held by a Pokemon outside of FARFETCHD line", async () => { await game.classicMode.startBattle([SpeciesId.PIKACHU]); @@ -141,5 +141,5 @@ describe("Items - Leek", () => { await game.phaseInterceptor.to(TurnEndPhase); expect(enemyMember.getCritStage).toHaveReturnedWith(0); - }, 20000); + }); }); diff --git a/test/items/leftovers.test.ts b/test/items/leftovers.test.ts index 1f42dbcf537..21319d2c9d7 100644 --- a/test/items/leftovers.test.ts +++ b/test/items/leftovers.test.ts @@ -56,5 +56,5 @@ describe("Items - Leftovers", () => { // Check if leftovers heal us await game.phaseInterceptor.to(TurnEndPhase); expect(leadPokemon.hp).toBeGreaterThan(leadHpAfterDamage); - }, 20000); + }); }); diff --git a/test/items/light_ball.test.ts b/test/items/light_ball.test.ts index a0269506909..6dfed3389b9 100644 --- a/test/items/light_ball.test.ts +++ b/test/items/light_ball.test.ts @@ -108,7 +108,7 @@ describe("Items - Light Ball", () => { expect(atkValue.value / atkStat).toBe(2); expect(spAtkValue.value / spAtkStat).toBe(2); - }, 20000); + }); it("LIGHT_BALL held by fused PIKACHU (base)", async () => { await game.classicMode.startBattle([SpeciesId.PIKACHU, SpeciesId.MAROWAK]); @@ -147,7 +147,7 @@ describe("Items - Light Ball", () => { expect(atkValue.value / atkStat).toBe(2); expect(spAtkValue.value / spAtkStat).toBe(2); - }, 20000); + }); it("LIGHT_BALL held by fused PIKACHU (part)", async () => { await game.classicMode.startBattle([SpeciesId.MAROWAK, SpeciesId.PIKACHU]); @@ -186,7 +186,7 @@ describe("Items - Light Ball", () => { expect(atkValue.value / atkStat).toBe(2); expect(spAtkValue.value / spAtkStat).toBe(2); - }, 20000); + }); it("LIGHT_BALL not held by PIKACHU", async () => { await game.classicMode.startBattle([SpeciesId.MAROWAK]); @@ -215,5 +215,5 @@ describe("Items - Light Ball", () => { expect(atkValue.value / atkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1); - }, 20000); + }); }); diff --git a/test/items/lock_capsule.test.ts b/test/items/lock_capsule.test.ts index 640da4a299e..beacc3a3907 100644 --- a/test/items/lock_capsule.test.ts +++ b/test/items/lock_capsule.test.ts @@ -49,5 +49,5 @@ describe("Items - Lock Capsule", () => { game.doSelectModifier(); await game.phaseInterceptor.to("SelectModifierPhase"); - }, 20000); + }); }); diff --git a/test/items/metal_powder.test.ts b/test/items/metal_powder.test.ts index 576b4923d6d..e731f6e7295 100644 --- a/test/items/metal_powder.test.ts +++ b/test/items/metal_powder.test.ts @@ -102,7 +102,7 @@ describe("Items - Metal Powder", () => { game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(2); - }, 20000); + }); it("METAL_POWDER held by fused DITTO (base)", async () => { await game.classicMode.startBattle([SpeciesId.DITTO, SpeciesId.MAROWAK]); @@ -135,7 +135,7 @@ describe("Items - Metal Powder", () => { game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(2); - }, 20000); + }); it("METAL_POWDER held by fused DITTO (part)", async () => { await game.classicMode.startBattle([SpeciesId.MAROWAK, SpeciesId.DITTO]); @@ -168,7 +168,7 @@ describe("Items - Metal Powder", () => { game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(2); - }, 20000); + }); it("METAL_POWDER not held by DITTO", async () => { await game.classicMode.startBattle([SpeciesId.MAROWAK]); @@ -191,5 +191,5 @@ describe("Items - Metal Powder", () => { game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(1); - }, 20000); + }); }); diff --git a/test/items/scope_lens.test.ts b/test/items/scope_lens.test.ts index 2ec5260d092..16be8aab930 100644 --- a/test/items/scope_lens.test.ts +++ b/test/items/scope_lens.test.ts @@ -28,7 +28,7 @@ describe("Items - Scope Lens", () => { .moveset([MoveId.POUND]) .startingHeldItems([{ name: "SCOPE_LENS" }]) .battleStyle("single"); - }, 20000); + }); it("should raise CRIT stage by 1", async () => { await game.classicMode.startBattle([SpeciesId.GASTLY]); @@ -42,5 +42,5 @@ describe("Items - Scope Lens", () => { await game.phaseInterceptor.to(TurnEndPhase); expect(enemyPokemon.getCritStage).toHaveReturnedWith(1); - }, 20000); + }); }); diff --git a/test/items/temp_stat_stage_booster.test.ts b/test/items/temp_stat_stage_booster.test.ts index a58c2d611c9..b8cd0cde4eb 100644 --- a/test/items/temp_stat_stage_booster.test.ts +++ b/test/items/temp_stat_stage_booster.test.ts @@ -50,7 +50,7 @@ describe("Items - Temporary Stat Stage Boosters", () => { await game.phaseInterceptor.runFrom("EnemyCommandPhase").to(TurnEndPhase); expect(partyMember.getStatStageMultiplier).toHaveReturnedWith(1.3); - }, 20000); + }); it("should increase existing ACC stat stage by 1 for X_ACCURACY only", async () => { game.override.startingModifier([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ACC }]).ability(AbilityId.SIMPLE); @@ -72,7 +72,7 @@ describe("Items - Temporary Stat Stage Boosters", () => { // ACC at +3 stat stages yields a x2 multiplier expect(partyMember.getAccuracyMultiplier).toHaveReturnedWith(2); - }, 20000); + }); it("should increase existing stat stage multiplier by 3/10 for the rest of the boosters", async () => { await game.classicMode.startBattle([SpeciesId.PIKACHU]); @@ -92,7 +92,7 @@ describe("Items - Temporary Stat Stage Boosters", () => { // ATK at +1 stat stage yields a x1.5 multiplier, add 0.3 from X_ATTACK expect(partyMember.getStatStageMultiplier).toHaveReturnedWith(1.8); - }, 20000); + }); it("should not increase past maximum stat stage multiplier", async () => { game.override.startingModifier([ @@ -116,7 +116,7 @@ describe("Items - Temporary Stat Stage Boosters", () => { expect(partyMember.getAccuracyMultiplier).toHaveReturnedWith(3); expect(partyMember.getStatStageMultiplier).toHaveReturnedWith(4); - }, 20000); + }); it("should renew how many battles are left of existing booster when picking up new booster of same type", async () => { game.override.startingLevel(200).itemRewards([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ATK }]); @@ -161,5 +161,5 @@ describe("Items - Temporary Stat Stage Boosters", () => { } } expect(count).toBe(1); - }, 20000); + }); }); diff --git a/test/moves/baton_pass.test.ts b/test/moves/baton_pass.test.ts index 42e46352a11..a010636034a 100644 --- a/test/moves/baton_pass.test.ts +++ b/test/moves/baton_pass.test.ts @@ -55,7 +55,7 @@ describe("Moves - Baton Pass", () => { playerPokemon = game.scene.getPlayerPokemon()!; expect(playerPokemon.species.speciesId).toEqual(SpeciesId.SHUCKLE); expect(playerPokemon.getStatStage(Stat.SPATK)).toEqual(2); - }, 20000); + }); it("passes stat stage buffs when AI uses it", async () => { // arrange @@ -82,7 +82,7 @@ describe("Moves - Baton Pass", () => { "SummonPhase", "PostSummonPhase", ]); - }, 20000); + }); it("doesn't transfer effects that aren't transferrable", async () => { game.override.enemyMoveset([MoveId.SALT_CURE]); @@ -98,7 +98,7 @@ describe("Moves - Baton Pass", () => { await game.toNextTurn(); expect(player2.findTag(t => t.tagType === BattlerTagType.SALT_CURED)).toBeUndefined(); - }, 20000); + }); it("doesn't allow binding effects from the user to persist", async () => { game.override.moveset([MoveId.FIRE_SPIN, MoveId.BATON_PASS]); diff --git a/test/moves/disable.test.ts b/test/moves/disable.test.ts index a269a8177aa..127a5eaaa0c 100644 --- a/test/moves/disable.test.ts +++ b/test/moves/disable.test.ts @@ -60,7 +60,7 @@ describe("Moves - Disable", () => { result: MoveResult.FAIL, }); expect(enemyMon.isMoveRestricted(MoveId.SPLASH)).toBe(false); - }, 20000); + }); it("causes STRUGGLE if all usable moves are disabled", async () => { await game.classicMode.startBattle(); @@ -78,7 +78,7 @@ describe("Moves - Disable", () => { expect(enemyHistory).toHaveLength(2); expect(enemyHistory[0].move).toBe(MoveId.SPLASH); expect(enemyHistory[1].move).toBe(MoveId.STRUGGLE); - }, 20000); + }); it("cannot disable STRUGGLE", async () => { game.override.enemyMoveset([MoveId.STRUGGLE]); @@ -94,7 +94,7 @@ describe("Moves - Disable", () => { expect(playerMon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); expect(enemyMon.getLastXMoves()[0].move).toBe(MoveId.STRUGGLE); expect(enemyMon.isMoveRestricted(MoveId.STRUGGLE)).toBe(false); - }, 20000); + }); it("interrupts target's move when target moves after", async () => { await game.classicMode.startBattle(); @@ -116,7 +116,7 @@ describe("Moves - Disable", () => { result: MoveResult.SUCCESS, }); expect(enemyHistory[1].result).toBe(MoveResult.FAIL); - }, 20000); + }); it("disables NATURE POWER, not the move invoked by it", async () => { game.override.enemyMoveset([MoveId.NATURE_POWER]); @@ -130,7 +130,7 @@ describe("Moves - Disable", () => { expect(enemyMon.isMoveRestricted(MoveId.NATURE_POWER)).toBe(true); expect(enemyMon.isMoveRestricted(enemyMon.getLastXMoves(2)[0].move)).toBe(false); - }, 20000); + }); it("disables most recent move", async () => { game.override.enemyMoveset([MoveId.SPLASH, MoveId.TACKLE]); @@ -150,5 +150,5 @@ describe("Moves - Disable", () => { expect(enemyMon.isMoveRestricted(MoveId.TACKLE)).toBe(true); expect(enemyMon.isMoveRestricted(MoveId.SPLASH)).toBe(false); - }, 20000); + }); }); diff --git a/test/moves/dynamax_cannon.test.ts b/test/moves/dynamax_cannon.test.ts index c0b6d9e0b40..bf8a63f044c 100644 --- a/test/moves/dynamax_cannon.test.ts +++ b/test/moves/dynamax_cannon.test.ts @@ -51,7 +51,7 @@ describe("Moves - Dynamax Cannon", () => { expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(dynamaxCannon.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(100); - }, 20000); + }); it("should return 100 power against an enemy at level cap", async () => { game.override.enemyLevel(10); @@ -63,7 +63,7 @@ describe("Moves - Dynamax Cannon", () => { expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(dynamaxCannon.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(100); - }, 20000); + }); it("should return 120 power against an enemy 1% above level cap", async () => { game.override.enemyLevel(101); @@ -78,7 +78,7 @@ describe("Moves - Dynamax Cannon", () => { vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(120); - }, 20000); + }); it("should return 140 power against an enemy 2% above level capp", async () => { game.override.enemyLevel(102); @@ -93,7 +93,7 @@ describe("Moves - Dynamax Cannon", () => { vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(140); - }, 20000); + }); it("should return 160 power against an enemy 3% above level cap", async () => { game.override.enemyLevel(103); @@ -108,7 +108,7 @@ describe("Moves - Dynamax Cannon", () => { vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(160); - }, 20000); + }); it("should return 180 power against an enemy 4% above level cap", async () => { game.override.enemyLevel(104); @@ -123,7 +123,7 @@ describe("Moves - Dynamax Cannon", () => { vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(180); - }, 20000); + }); it("should return 200 power against an enemy 5% above level cap", async () => { game.override.enemyLevel(105); @@ -138,7 +138,7 @@ describe("Moves - Dynamax Cannon", () => { vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(200); - }, 20000); + }); it("should return 200 power against an enemy way above level cap", async () => { game.override.enemyLevel(999); @@ -151,5 +151,5 @@ describe("Moves - Dynamax Cannon", () => { expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(dynamaxCannon.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(200); - }, 20000); + }); }); diff --git a/test/moves/fusion_bolt.test.ts b/test/moves/fusion_bolt.test.ts index a5263bfa364..cb3d75f56eb 100644 --- a/test/moves/fusion_bolt.test.ts +++ b/test/moves/fusion_bolt.test.ts @@ -45,5 +45,5 @@ describe("Moves - Fusion Bolt", () => { await game.toNextTurn(); expect(initialHp - partyMember.hp).toBe(0); - }, 20000); + }); }); diff --git a/test/moves/fusion_flare_bolt.test.ts b/test/moves/fusion_flare_bolt.test.ts index f10ede8717c..f9e55b72f6c 100644 --- a/test/moves/fusion_flare_bolt.test.ts +++ b/test/moves/fusion_flare_bolt.test.ts @@ -64,7 +64,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200); - }, 20000); + }); it("FUSION_BOLT should double power of subsequent FUSION_FLARE", async () => { await game.classicMode.startBattle([SpeciesId.ZEKROM, SpeciesId.ZEKROM]); @@ -84,7 +84,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); - }, 20000); + }); it("FUSION_FLARE should double power of subsequent FUSION_BOLT if a move failed in between", async () => { await game.classicMode.startBattle([SpeciesId.ZEKROM, SpeciesId.ZEKROM]); @@ -109,7 +109,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200); - }, 20000); + }); it("FUSION_FLARE should not double power of subsequent FUSION_BOLT if a move succeeded in between", async () => { game.override.enemyMoveset(MoveId.SPLASH); @@ -134,7 +134,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionBolt.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100); - }, 20000); + }); it("FUSION_FLARE should double power of subsequent FUSION_BOLT if moves are aimed at allies", async () => { await game.classicMode.startBattle([SpeciesId.ZEKROM, SpeciesId.RESHIRAM]); @@ -154,7 +154,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); - }, 20000); + }); it("FUSION_FLARE and FUSION_BOLT alternating throughout turn should double power of subsequent moves", async () => { game.override.enemyMoveset(fusionFlare.id); @@ -208,7 +208,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); - }, 20000); + }); it("FUSION_FLARE and FUSION_BOLT alternating throughout turn should double power of subsequent moves if moves are aimed at allies", async () => { game.override.enemyMoveset(fusionFlare.id); @@ -262,5 +262,5 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { expect((game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase).move.id).toBe(fusionFlare.id); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); - }, 20000); + }); }); diff --git a/test/moves/gigaton_hammer.test.ts b/test/moves/gigaton_hammer.test.ts index be7adf53aa7..e8c9d316372 100644 --- a/test/moves/gigaton_hammer.test.ts +++ b/test/moves/gigaton_hammer.test.ts @@ -52,7 +52,7 @@ describe("Moves - Gigaton Hammer", () => { const enemy2 = game.scene.getEnemyPokemon()!; expect(enemy2.hp).toBe(enemy2.getMaxHp()); - }, 20000); + }); it("can be used again if recalled and sent back out", async () => { game.override.startingWave(4); @@ -75,5 +75,5 @@ describe("Moves - Gigaton Hammer", () => { const enemy2 = game.scene.getEnemyPokemon()!; expect(enemy2.hp).toBeLessThan(enemy2.getMaxHp()); - }, 20000); + }); }); diff --git a/test/moves/growth.test.ts b/test/moves/growth.test.ts index a7078961e2a..6eec30be49f 100644 --- a/test/moves/growth.test.ts +++ b/test/moves/growth.test.ts @@ -43,5 +43,5 @@ describe("Moves - Growth", () => { await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase); expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1); - }, 20000); + }); }); diff --git a/test/moves/guard_split.test.ts b/test/moves/guard_split.test.ts index 47edba49c9a..878bb80f251 100644 --- a/test/moves/guard_split.test.ts +++ b/test/moves/guard_split.test.ts @@ -50,7 +50,7 @@ describe("Moves - Guard Split", () => { expect(player.getStat(Stat.SPDEF, false)).toBe(avgSpDef); expect(enemy.getStat(Stat.SPDEF, false)).toBe(avgSpDef); - }, 20000); + }); it("should be idempotent", async () => { game.override.enemyMoveset([MoveId.GUARD_SPLIT]); @@ -73,5 +73,5 @@ describe("Moves - Guard Split", () => { expect(player.getStat(Stat.SPDEF, false)).toBe(avgSpDef); expect(enemy.getStat(Stat.SPDEF, false)).toBe(avgSpDef); - }, 20000); + }); }); diff --git a/test/moves/magnet_rise.test.ts b/test/moves/magnet_rise.test.ts index 16f449899ef..496c0609411 100644 --- a/test/moves/magnet_rise.test.ts +++ b/test/moves/magnet_rise.test.ts @@ -42,7 +42,7 @@ describe("Moves - Magnet Rise", () => { const finalHp = game.scene.getPlayerParty()[0].hp; const hpLost = finalHp - startingHp; expect(hpLost).toBe(0); - }, 20000); + }); it("MAGNET RISE - Gravity", async () => { await game.classicMode.startBattle(); @@ -58,5 +58,5 @@ describe("Moves - Magnet Rise", () => { finalHp = game.scene.getPlayerParty()[0].hp; hpLost = finalHp - startingHp; expect(hpLost).not.toBe(0); - }, 20000); + }); }); diff --git a/test/moves/power_split.test.ts b/test/moves/power_split.test.ts index 6e0763fb87d..c0fcd317182 100644 --- a/test/moves/power_split.test.ts +++ b/test/moves/power_split.test.ts @@ -50,7 +50,7 @@ describe("Moves - Power Split", () => { expect(player.getStat(Stat.SPATK, false)).toBe(avgSpAtk); expect(enemy.getStat(Stat.SPATK, false)).toBe(avgSpAtk); - }, 20000); + }); it("should be idempotent", async () => { game.override.enemyMoveset([MoveId.POWER_SPLIT]); @@ -73,5 +73,5 @@ describe("Moves - Power Split", () => { expect(player.getStat(Stat.SPATK, false)).toBe(avgSpAtk); expect(enemy.getStat(Stat.SPATK, false)).toBe(avgSpAtk); - }, 20000); + }); }); diff --git a/test/moves/speed_swap.test.ts b/test/moves/speed_swap.test.ts index aef0d981e98..3723e7db740 100644 --- a/test/moves/speed_swap.test.ts +++ b/test/moves/speed_swap.test.ts @@ -47,5 +47,5 @@ describe("Moves - Speed Swap", () => { expect(player.getStat(Stat.SPD, false)).toBe(enemySpd); expect(enemy.getStat(Stat.SPD, false)).toBe(playerSpd); - }, 20000); + }); }); diff --git a/test/moves/spikes.test.ts b/test/moves/spikes.test.ts index a2d72054020..49492701d18 100644 --- a/test/moves/spikes.test.ts +++ b/test/moves/spikes.test.ts @@ -49,7 +49,7 @@ describe("Moves - Spikes", () => { const player = game.scene.getPlayerParty()[0]; expect(player.hp).toBe(player.getMaxHp()); - }, 20000); + }); it("should damage opposing pokemon that are forced to switch in", async () => { game.override.startingWave(5); @@ -63,7 +63,7 @@ describe("Moves - Spikes", () => { const enemy = game.scene.getEnemyParty()[0]; expect(enemy.hp).toBeLessThan(enemy.getMaxHp()); - }, 20000); + }); it("should damage opposing pokemon that choose to switch in", async () => { game.override.startingWave(5); @@ -78,7 +78,7 @@ describe("Moves - Spikes", () => { const enemy = game.scene.getEnemyParty()[0]; expect(enemy.hp).toBeLessThan(enemy.getMaxHp()); - }, 20000); + }); it("should work when all targets fainted", async () => { game.override.enemySpecies(SpeciesId.DIGLETT).battleStyle("double").startingLevel(50); @@ -89,5 +89,5 @@ describe("Moves - Spikes", () => { await game.phaseInterceptor.to("TurnEndPhase"); expect(game.scene.arena.getTagOnSide(ArenaTrapTag, ArenaTagSide.ENEMY)).toBeDefined(); - }, 20000); + }); }); diff --git a/test/moves/struggle.test.ts b/test/moves/struggle.test.ts index cd3c65217ed..2175dc557b3 100644 --- a/test/moves/struggle.test.ts +++ b/test/moves/struggle.test.ts @@ -43,8 +43,6 @@ describe("Moves - Struggle", () => { await game.phaseInterceptor.to("BerryPhase"); expect(stabSpy).toHaveReturnedWith(1); - - stabSpy.mockRestore(); }); it("should ignore type effectiveness", async () => { @@ -55,11 +53,8 @@ describe("Moves - Struggle", () => { game.move.select(MoveId.STRUGGLE); const moveEffectivenessSpy = vi.spyOn(enemy, "getMoveEffectiveness"); - await game.phaseInterceptor.to("BerryPhase"); expect(moveEffectivenessSpy).toHaveReturnedWith(1); - - moveEffectivenessSpy.mockRestore(); }); }); diff --git a/test/moves/tackle.test.ts b/test/moves/tackle.test.ts index 61780044f49..96faa27ad38 100644 --- a/test/moves/tackle.test.ts +++ b/test/moves/tackle.test.ts @@ -44,7 +44,7 @@ describe("Moves - Tackle", () => { await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase); const hpLost = hpOpponent - game.scene.currentBattle.enemyParty[0].hp; expect(hpLost).toBe(0); - }, 20000); + }); it("TACKLE against not resistant", async () => { const moveToUse = MoveId.TACKLE; @@ -59,5 +59,5 @@ describe("Moves - Tackle", () => { const hpLost = hpOpponent - game.scene.currentBattle.enemyParty[0].hp; expect(hpLost).toBeGreaterThan(0); expect(hpLost).toBeLessThan(4); - }, 20000); + }); }); diff --git a/test/moves/tail_whip.test.ts b/test/moves/tail_whip.test.ts index 9c9ac736ece..e98194b52dd 100644 --- a/test/moves/tail_whip.test.ts +++ b/test/moves/tail_whip.test.ts @@ -46,5 +46,5 @@ describe("Moves - Tail whip", () => { await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase); expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(-1); - }, 20000); + }); }); diff --git a/test/moves/tera_blast.test.ts b/test/moves/tera_blast.test.ts index 2f3bdcebfcf..3372fd21539 100644 --- a/test/moves/tera_blast.test.ts +++ b/test/moves/tera_blast.test.ts @@ -62,7 +62,7 @@ describe("Moves - Tera Blast", () => { await game.phaseInterceptor.to("MoveEffectPhase"); expect(spy).toHaveReturnedWith(2); - }, 20000); + }); it("increases power if user is Stellar tera type", async () => { await game.classicMode.startBattle(); diff --git a/test/moves/tidy_up.test.ts b/test/moves/tidy_up.test.ts index 636946c2e6a..69c9df42b36 100644 --- a/test/moves/tidy_up.test.ts +++ b/test/moves/tidy_up.test.ts @@ -46,7 +46,7 @@ describe("Moves - Tidy Up", () => { game.move.select(MoveId.TIDY_UP); await game.phaseInterceptor.to(MoveEndPhase); expect(game.scene.arena.getTag(ArenaTagType.SPIKES)).toBeUndefined(); - }, 20000); + }); it("stealth rocks are cleared", async () => { game.override.moveset([MoveId.STEALTH_ROCK, MoveId.TIDY_UP]).enemyMoveset(MoveId.STEALTH_ROCK); @@ -57,7 +57,7 @@ describe("Moves - Tidy Up", () => { game.move.select(MoveId.TIDY_UP); await game.phaseInterceptor.to(MoveEndPhase); expect(game.scene.arena.getTag(ArenaTagType.STEALTH_ROCK)).toBeUndefined(); - }, 20000); + }); it("toxic spikes are cleared", async () => { game.override.moveset([MoveId.TOXIC_SPIKES, MoveId.TIDY_UP]).enemyMoveset(MoveId.TOXIC_SPIKES); @@ -68,7 +68,7 @@ describe("Moves - Tidy Up", () => { game.move.select(MoveId.TIDY_UP); await game.phaseInterceptor.to(MoveEndPhase); expect(game.scene.arena.getTag(ArenaTagType.TOXIC_SPIKES)).toBeUndefined(); - }, 20000); + }); it("sticky webs are cleared", async () => { game.override.moveset([MoveId.STICKY_WEB, MoveId.TIDY_UP]).enemyMoveset(MoveId.STICKY_WEB); @@ -80,7 +80,7 @@ describe("Moves - Tidy Up", () => { game.move.select(MoveId.TIDY_UP); await game.phaseInterceptor.to(MoveEndPhase); expect(game.scene.arena.getTag(ArenaTagType.STICKY_WEB)).toBeUndefined(); - }, 20000); + }); it("substitutes are cleared", async () => { game.override.moveset([MoveId.SUBSTITUTE, MoveId.TIDY_UP]).enemyMoveset(MoveId.SUBSTITUTE); @@ -97,7 +97,7 @@ describe("Moves - Tidy Up", () => { expect(p).toBeDefined(); expect(p!.getTag(SubstituteTag)).toBeUndefined(); }); - }, 20000); + }); it("user's stats are raised with no traps set", async () => { await game.classicMode.startBattle(); @@ -112,5 +112,5 @@ describe("Moves - Tidy Up", () => { expect(playerPokemon.getStatStage(Stat.ATK)).toBe(1); expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1); - }, 20000); + }); }); diff --git a/test/moves/u_turn.test.ts b/test/moves/u_turn.test.ts index 4212802298e..5d4cad3a5ad 100644 --- a/test/moves/u_turn.test.ts +++ b/test/moves/u_turn.test.ts @@ -50,7 +50,7 @@ describe("Moves - U-turn", () => { ); expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase"); expect(game.scene.getPlayerPokemon()!.species.speciesId).toBe(SpeciesId.SHUCKLE); - }, 20000); + }); it("triggers rough skin on the u-turn user before a new pokemon is switched in", async () => { // arrange @@ -68,7 +68,7 @@ describe("Moves - U-turn", () => { expect(game.scene.getEnemyPokemon()!.waveData.abilityRevealed).toBe(true); // proxy for asserting ability activated expect(playerPkm.species.speciesId).toEqual(SpeciesId.RAICHU); expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase"); - }, 20000); + }); it("triggers contact abilities on the u-turn user (eg poison point) before a new pokemon is switched in", async () => { // arrange @@ -86,7 +86,7 @@ describe("Moves - U-turn", () => { expect(playerPkm.species.speciesId).toEqual(SpeciesId.RAICHU); expect(game.scene.getEnemyPokemon()!.waveData.abilityRevealed).toBe(true); // proxy for asserting ability activated expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase"); - }, 20000); + }); it("still forces a switch if u-turn KO's the opponent", async () => { game.override.startingLevel(1000); // Ensure that U-Turn KO's the opponent diff --git a/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts b/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts index e3368d339cd..82cac197fe9 100644 --- a/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts +++ b/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts @@ -55,8 +55,6 @@ describe("A Trainer's Test - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts b/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts index 304ca87e0e6..b6c4e4d85fb 100644 --- a/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts +++ b/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts @@ -54,8 +54,6 @@ describe("Absolute Avarice - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts index 8e8253fd912..4b0f1d50b74 100644 --- a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts +++ b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts @@ -56,8 +56,6 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/berries-abound-encounter.test.ts b/test/mystery-encounter/encounters/berries-abound-encounter.test.ts index 566576995a5..8c2c6c608cd 100644 --- a/test/mystery-encounter/encounters/berries-abound-encounter.test.ts +++ b/test/mystery-encounter/encounters/berries-abound-encounter.test.ts @@ -57,8 +57,6 @@ describe("Berries Abound - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts index d5527d208fd..4da8ff7f643 100644 --- a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts +++ b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts @@ -167,8 +167,6 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index 9965b90a981..738b3a54067 100644 --- a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -68,8 +68,6 @@ describe("Clowning Around - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts index f74100d7e35..e47c7cc1a42 100644 --- a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts +++ b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts @@ -57,8 +57,6 @@ describe("Dancing Lessons - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/delibirdy-encounter.test.ts b/test/mystery-encounter/encounters/delibirdy-encounter.test.ts index 0b6f7453522..3ef8431cc2c 100644 --- a/test/mystery-encounter/encounters/delibirdy-encounter.test.ts +++ b/test/mystery-encounter/encounters/delibirdy-encounter.test.ts @@ -59,8 +59,6 @@ describe("Delibird-y - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts b/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts index 470d41060fb..a82734d0c03 100644 --- a/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts +++ b/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts @@ -49,8 +49,6 @@ describe("Department Store Sale - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/field-trip-encounter.test.ts b/test/mystery-encounter/encounters/field-trip-encounter.test.ts index e059827fa17..f4b3c52eb65 100644 --- a/test/mystery-encounter/encounters/field-trip-encounter.test.ts +++ b/test/mystery-encounter/encounters/field-trip-encounter.test.ts @@ -47,8 +47,6 @@ describe("Field Trip - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts b/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts index 76b15106ef1..5e6b8507a91 100644 --- a/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts +++ b/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts @@ -66,8 +66,6 @@ describe("Fiery Fallout - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts index bf5d58fdad3..28db869004c 100644 --- a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts +++ b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts @@ -53,8 +53,6 @@ describe("Fight or Flight - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts b/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts index 9f2057080f9..d5fe1ffc68a 100644 --- a/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts +++ b/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts @@ -59,8 +59,6 @@ describe("Fun And Games! - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts index 02ffc56fb2e..bb598f4ae6e 100644 --- a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts +++ b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts @@ -54,8 +54,6 @@ describe("Global Trade System - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts b/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts index 971cd938925..aa367277ac6 100644 --- a/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts +++ b/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts @@ -50,8 +50,6 @@ describe("Lost at Sea - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts index 04b611e1143..478648d88a7 100644 --- a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts +++ b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts @@ -60,8 +60,6 @@ describe("Mysterious Challengers - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/part-timer-encounter.test.ts b/test/mystery-encounter/encounters/part-timer-encounter.test.ts index 4436f2e19fe..d36b387cae1 100644 --- a/test/mystery-encounter/encounters/part-timer-encounter.test.ts +++ b/test/mystery-encounter/encounters/part-timer-encounter.test.ts @@ -53,8 +53,6 @@ describe("Part-Timer - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/safari-zone.test.ts b/test/mystery-encounter/encounters/safari-zone.test.ts index a086272c721..7ac26f48e59 100644 --- a/test/mystery-encounter/encounters/safari-zone.test.ts +++ b/test/mystery-encounter/encounters/safari-zone.test.ts @@ -55,8 +55,6 @@ describe("Safari Zone - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts b/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts index 12398049abd..2138298ee2b 100644 --- a/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts +++ b/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts @@ -64,8 +64,6 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts b/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts index ed6d1c5bc4f..c9d6f540191 100644 --- a/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts @@ -57,8 +57,6 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts b/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts index ee2d7d8039b..990e39014e2 100644 --- a/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts @@ -55,8 +55,6 @@ describe("The Pokemon Salesman - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index 5f7c0f5a0fd..d8626a7bf8a 100644 --- a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -65,8 +65,6 @@ describe("The Strong Stuff - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts b/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts index 72f76358398..87caa6ccd40 100644 --- a/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts @@ -61,8 +61,6 @@ describe("The Winstrate Challenge - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { @@ -307,7 +305,7 @@ describe("The Winstrate Challenge - Mystery Encounter", () => { ) as ModifierSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(1); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("MYSTERY_ENCOUNTER_MACHO_BRACE"); - }, 15000); + }); }); describe("Option 2 - Refuse the Challenge", () => { diff --git a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index 6e2da14dffc..2bffa26ff4a 100644 --- a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -64,8 +64,6 @@ describe("Trash to Treasure - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts index 60ca87d3ae2..65c266a5a6c 100644 --- a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts +++ b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts @@ -62,8 +62,6 @@ describe("Uncommon Breed - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts index 27d41ec66cb..475d5cc3c6e 100644 --- a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -54,8 +54,6 @@ describe("Weird Dream - Mystery Encounter", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - vi.clearAllMocks(); - vi.resetAllMocks(); }); it("should have the correct properties", async () => { diff --git a/test/phases/phases.test.ts b/test/phases/phases.test.ts index 8f7b1a1ea66..9d1c1804615 100644 --- a/test/phases/phases.test.ts +++ b/test/phases/phases.test.ts @@ -51,6 +51,6 @@ describe("Phases", () => { scene.phaseManager.unshiftPhase(unavailablePhase); await game.phaseInterceptor.to(UnavailablePhase); expect(scene.ui.getMode()).to.equal(UiMode.UNAVAILABLE); - }, 20000); + }); }); }); diff --git a/test/phases/select-modifier-phase.test.ts b/test/phases/select-modifier-phase.test.ts index 3639d34d25e..6e92861260e 100644 --- a/test/phases/select-modifier-phase.test.ts +++ b/test/phases/select-modifier-phase.test.ts @@ -42,8 +42,6 @@ describe("SelectModifierPhase", () => { afterEach(() => { game.phaseInterceptor.restoreOg(); - - vi.clearAllMocks(); }); it("should start a select modifier phase", async () => { diff --git a/test/reload.test.ts b/test/reload.test.ts index 8b817bbfe97..8e39df23f47 100644 --- a/test/reload.test.ts +++ b/test/reload.test.ts @@ -43,7 +43,7 @@ describe("Reload", () => { const postReloadRngState = Phaser.Math.RND.state(); expect(preReloadRngState).toBe(postReloadRngState); - }, 20000); + }); it("should not have RNG inconsistencies after a biome switch", async () => { game.override @@ -75,7 +75,7 @@ describe("Reload", () => { const postReloadRngState = Phaser.Math.RND.state(); expect(preReloadRngState).toBe(postReloadRngState); - }, 20000); + }); it("should not have weather inconsistencies after a biome switch", async () => { game.override @@ -101,7 +101,7 @@ describe("Reload", () => { const postReloadWeather = game.scene.arena.weather; expect(postReloadWeather).toStrictEqual(preReloadWeather); - }, 20000); + }); it("should not have RNG inconsistencies at a Daily run wild Pokemon fight", async () => { await game.dailyMode.startBattle(); @@ -113,7 +113,7 @@ describe("Reload", () => { const postReloadRngState = Phaser.Math.RND.state(); expect(preReloadRngState).toBe(postReloadRngState); - }, 20000); + }); it("should not have RNG inconsistencies at a Daily run double battle", async () => { game.override.battleStyle("double"); @@ -126,7 +126,7 @@ describe("Reload", () => { const postReloadRngState = Phaser.Math.RND.state(); expect(preReloadRngState).toBe(postReloadRngState); - }, 20000); + }); it("should not have RNG inconsistencies at a Daily run Gym Leader fight", async () => { game.override.battleStyle("single").startingWave(40); @@ -139,7 +139,7 @@ describe("Reload", () => { const postReloadRngState = Phaser.Math.RND.state(); expect(preReloadRngState).toBe(postReloadRngState); - }, 20000); + }); it("should not have RNG inconsistencies at a Daily run regular trainer fight", async () => { game.override.battleStyle("single").startingWave(45); @@ -152,7 +152,7 @@ describe("Reload", () => { const postReloadRngState = Phaser.Math.RND.state(); expect(preReloadRngState).toBe(postReloadRngState); - }, 20000); + }); it("should not have RNG inconsistencies at a Daily run wave 50 Boss fight", async () => { game.override.battleStyle("single").startingWave(50); @@ -165,5 +165,5 @@ describe("Reload", () => { const postReloadRngState = Phaser.Math.RND.state(); expect(preReloadRngState).toBe(postReloadRngState); - }, 20000); + }); }); diff --git a/test/ui/pokedex.test.ts b/test/ui/pokedex.test.ts index 53ebe04a4ed..d3fc4b11968 100644 --- a/test/ui/pokedex.test.ts +++ b/test/ui/pokedex.test.ts @@ -1,6 +1,6 @@ import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it, type MockInstance, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import PokedexUiHandler from "#app/ui/pokedex-ui-handler"; import { FilterTextRow } from "#app/ui/filter-text"; import { allAbilities } from "#app/data/data-lists"; @@ -46,7 +46,6 @@ function permutations(array: T[], length: number): T[][] { describe("UI - Pokedex", () => { let phaserGame: Phaser.Game; let game: GameManager; - const mocks: MockInstance[] = []; beforeAll(() => { phaserGame = new Phaser.Game({ @@ -55,9 +54,6 @@ describe("UI - Pokedex", () => { }); afterEach(() => { - while (mocks.length > 0) { - mocks.pop()?.mockRestore(); - } game.phaseInterceptor.restoreOg(); }); @@ -185,10 +181,10 @@ describe("UI - Pokedex", () => { checks.push(...pokemon.forms); } for (const p of checks) { - mocks.push(vi.spyOn(p, "ability1", "get").mockReturnValue(ability)); - mocks.push(vi.spyOn(p, "ability2", "get").mockReturnValue(ability2)); - mocks.push(vi.spyOn(p, "abilityHidden", "get").mockReturnValue(hidden)); - mocks.push(vi.spyOn(p, "getPassiveAbility").mockReturnValue(passive)); + vi.spyOn(p, "ability1", "get").mockReturnValue(ability); + vi.spyOn(p, "ability2", "get").mockReturnValue(ability2); + vi.spyOn(p, "abilityHidden", "get").mockReturnValue(hidden); + vi.spyOn(p, "getPassiveAbility").mockReturnValue(passive); } } diff --git a/test/ui/starter-select.test.ts b/test/ui/starter-select.test.ts index be508a2b69c..3e540c4e2c5 100644 --- a/test/ui/starter-select.test.ts +++ b/test/ui/starter-select.test.ts @@ -94,7 +94,7 @@ describe("UI - Starter select", () => { expect(game.scene.getPlayerParty()[0].shiny).toBe(true); expect(game.scene.getPlayerParty()[0].variant).toBe(2); expect(game.scene.getPlayerParty()[0].gender).toBe(Gender.MALE); - }, 20000); + }); it("Bulbasaur - shiny - variant 2 female hardy overgrow", async () => { await game.importData("./test/testUtils/saves/everything.prsv"); @@ -156,7 +156,7 @@ describe("UI - Starter select", () => { expect(game.scene.getPlayerParty()[0].variant).toBe(2); expect(game.scene.getPlayerParty()[0].nature).toBe(Nature.HARDY); expect(game.scene.getPlayerParty()[0].getAbility().id).toBe(AbilityId.OVERGROW); - }, 20000); + }); it("Bulbasaur - shiny - variant 2 female lonely chlorophyl", async () => { await game.importData("./test/testUtils/saves/everything.prsv"); @@ -221,7 +221,7 @@ describe("UI - Starter select", () => { expect(game.scene.getPlayerParty()[0].gender).toBe(Gender.FEMALE); expect(game.scene.getPlayerParty()[0].nature).toBe(Nature.LONELY); expect(game.scene.getPlayerParty()[0].getAbility().id).toBe(AbilityId.CHLOROPHYLL); - }, 20000); + }); it("Bulbasaur - shiny - variant 2 female", async () => { await game.importData("./test/testUtils/saves/everything.prsv"); @@ -282,7 +282,7 @@ describe("UI - Starter select", () => { expect(game.scene.getPlayerParty()[0].shiny).toBe(true); expect(game.scene.getPlayerParty()[0].variant).toBe(2); expect(game.scene.getPlayerParty()[0].gender).toBe(Gender.FEMALE); - }, 20000); + }); it("Bulbasaur - not shiny", async () => { await game.importData("./test/testUtils/saves/everything.prsv"); @@ -342,7 +342,7 @@ describe("UI - Starter select", () => { expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.BULBASAUR); expect(game.scene.getPlayerParty()[0].shiny).toBe(false); expect(game.scene.getPlayerParty()[0].variant).toBe(0); - }, 20000); + }); it("Bulbasaur - shiny - variant 1", async () => { await game.importData("./test/testUtils/saves/everything.prsv"); @@ -404,7 +404,7 @@ describe("UI - Starter select", () => { expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.BULBASAUR); expect(game.scene.getPlayerParty()[0].shiny).toBe(true); expect(game.scene.getPlayerParty()[0].variant).toBe(1); - }, 20000); + }); it("Bulbasaur - shiny - variant 0", async () => { await game.importData("./test/testUtils/saves/everything.prsv"); @@ -465,7 +465,7 @@ describe("UI - Starter select", () => { expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.BULBASAUR); expect(game.scene.getPlayerParty()[0].shiny).toBe(true); expect(game.scene.getPlayerParty()[0].variant).toBe(0); - }, 20000); + }); it("Check if first pokemon in party is caterpie from gen 1 and 1rd row, 3rd column", async () => { await game.importData("./test/testUtils/saves/everything.prsv"); @@ -529,7 +529,7 @@ describe("UI - Starter select", () => { }); await game.phaseInterceptor.whenAboutToRun(EncounterPhase); expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.CATERPIE); - }, 20000); + }); it("Check if first pokemon in party is nidoran_m from gen 1 and 2nd row, 4th column (cursor (9+4)-1)", async () => { await game.importData("./test/testUtils/saves/everything.prsv"); @@ -595,5 +595,5 @@ describe("UI - Starter select", () => { }); await game.phaseInterceptor.whenAboutToRun(EncounterPhase); expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.NIDORAN_M); - }, 20000); + }); }); diff --git a/test/ui/transfer-item.test.ts b/test/ui/transfer-item.test.ts index ef0fe502c6e..572d56c5903 100644 --- a/test/ui/transfer-item.test.ts +++ b/test/ui/transfer-item.test.ts @@ -77,7 +77,7 @@ describe("UI - Transfer Items", () => { }); await game.phaseInterceptor.to("SelectModifierPhase"); - }, 20000); + }); it("check transfer option for pokemon to transfer to", async () => { game.onNextPrompt("SelectModifierPhase", UiMode.PARTY, () => { @@ -98,5 +98,5 @@ describe("UI - Transfer Items", () => { }); await game.phaseInterceptor.to("SelectModifierPhase"); - }, 20000); + }); }); From f8d8a3de84037fc35a54b7dc9a96f5d7707855a8 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 15 Jun 2025 00:51:21 -0700 Subject: [PATCH 46/51] [Dev] Enable dependency cruiser workflow (#5966) * Update `.dependency-cruiser.cjs` config file * Update GitHub workflow * Have `depcruise` check `test/` as well * Remove circular import between `battle-anims.ts` and `battler-tags.ts` --- .dependency-cruiser.cjs | 22 +++++++++--------- .github/workflows/linting.yml | 42 +++++++++++++++++++++++++++++++++++ .github/workflows/quality.yml | 41 ---------------------------------- package.json | 2 +- src/data/battle-anims.ts | 18 ++++++++++----- 5 files changed, 66 insertions(+), 59 deletions(-) create mode 100644 .github/workflows/linting.yml delete mode 100644 .github/workflows/quality.yml diff --git a/.dependency-cruiser.cjs b/.dependency-cruiser.cjs index eccec089f1e..84d01599727 100644 --- a/.dependency-cruiser.cjs +++ b/.dependency-cruiser.cjs @@ -4,7 +4,7 @@ module.exports = { { name: "only-type-imports", severity: "error", - comment: "Files in enums and @types may only use type imports.", + comment: "Files in 'enums/' and '@types/' must only use type imports.", from: { path: ["(^|/)src/@types", "(^|/)src/enums"], }, @@ -14,7 +14,7 @@ module.exports = { }, { name: "no-circular-at-runtime", - severity: "warn", + severity: "error", comment: "This dependency is part of a circular relationship. You might want to revise " + "your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ", @@ -34,7 +34,7 @@ module.exports = { "add an exception for it in your dependency-cruiser configuration. By default " + "this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " + "files (.d.ts), tsconfig.json and some of the babel and webpack configs.", - severity: "warn", + severity: "error", from: { orphan: true, pathNot: [ @@ -42,8 +42,7 @@ module.exports = { "[.]d[.]ts$", // TypeScript declaration files "(^|/)tsconfig[.]json$", // TypeScript config "(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$", // other configs - // anything in src/@types - "(^|/)src/@types/", + "(^|/)test/.+[.]setup[.]ts", // Vitest setup files ], }, to: {}, @@ -53,7 +52,7 @@ module.exports = { comment: "A module depends on a node core module that has been deprecated. Find an alternative - these are " + "bound to exist - node doesn't deprecate lightly.", - severity: "warn", + severity: "error", from: {}, to: { dependencyTypes: ["core"], @@ -86,7 +85,7 @@ module.exports = { comment: "This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later " + "version of that module, or find an alternative. Deprecated modules are a security risk.", - severity: "warn", + severity: "error", from: {}, to: { dependencyTypes: ["deprecated"], @@ -122,7 +121,7 @@ module.exports = { "Likely this module depends on an external ('npm') package that occurs more than once " + "in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " + "maintenance problems later on.", - severity: "warn", + severity: "error", from: {}, to: { moreThanOneDependencyType: true, @@ -133,7 +132,7 @@ module.exports = { }, }, - /* rules you might want to tweak for your specific situation: */ + // rules you might want to tweak for your specific situation: { name: "not-to-spec", @@ -188,7 +187,7 @@ module.exports = { "in your package.json. This makes sense if your package is e.g. a plugin, but in " + "other cases - maybe not so much. If the use of a peer dependency is intentional " + "add an exception to your dependency-cruiser configuration.", - severity: "warn", + severity: "error", from: {}, to: { dependencyTypes: ["npm-peer"], @@ -196,6 +195,7 @@ module.exports = { }, ], options: { + exclude: ["src/plugins/vite/*", "src/vite.env.d.ts"], /* Which modules not to follow further when encountered */ doNotFollow: { /* path: an array of regular expressions in strings to match against */ @@ -235,7 +235,7 @@ module.exports = { true: also detect dependencies that only exist before typescript-to-javascript compilation "specify": for each dependency identify whether it only exists before compilation or also after */ - // tsPreCompilationDeps: false, + tsPreCompilationDeps: true, /* list of extensions to scan that aren't javascript or compile-to-javascript. Empty by default. Only put extensions in here that you want to take into diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml new file mode 100644 index 00000000000..0fdbc8b1952 --- /dev/null +++ b/.github/workflows/linting.yml @@ -0,0 +1,42 @@ +name: Linting + +on: + push: + branches: + - main + - beta + pull_request: + branches: + - main + - beta + merge_group: + types: [checks_requested] + +jobs: + run-linters: + name: Run linters + runs-on: ubuntu-latest + + steps: + - name: Check out Git repository + uses: actions/checkout@v4 + with: + submodules: 'recursive' + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install Node.js dependencies + run: npm ci + + - name: Run ESLint + run: npm run eslint-ci + + - name: Lint with Biome + run: npm run biome-ci + + - name: Check dependencies with depcruise + run: npm run depcruise \ No newline at end of file diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml deleted file mode 100644 index 36233248472..00000000000 --- a/.github/workflows/quality.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Biome Code Quality - -on: - # Trigger the workflow on push or pull request, - # but only for the main branch - push: - branches: - - main # Trigger on push events to the main branch - - beta # Trigger on push events to the beta branch - pull_request: - branches: - - main # Trigger on pull request events targeting the main branch - - beta # Trigger on pull request events targeting the beta branch - merge_group: - types: [checks_requested] - -jobs: - run-linters: # Define a job named "run-linters" - name: Run linters # Human-readable name for the job - runs-on: ubuntu-latest # Specify the latest Ubuntu runner for the job - - steps: - - name: Check out Git repository # Step to check out the repository - uses: actions/checkout@v4 # Use the checkout action version 4 - with: - submodules: 'recursive' - - - name: Set up Node.js # Step to set up Node.js environment - uses: actions/setup-node@v4 # Use the setup-node action version 4 - with: - node-version-file: '.nvmrc' - cache: 'npm' - - - name: Install Node.js dependencies # Step to install Node.js dependencies - run: npm ci # Use 'npm ci' to install dependencies - - - name: eslint # Step to run linters - run: npm run eslint-ci - - - name: Lint with Biome # Step to run linters - run: npm run biome-ci \ No newline at end of file diff --git a/package.json b/package.json index b927542788d..cd2b7b18494 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "biome": "biome check --write --changed --no-errors-on-unmatched", "biome-ci": "biome ci --diagnostic-level=error --reporter=github --no-errors-on-unmatched", "docs": "typedoc", - "depcruise": "depcruise src", + "depcruise": "depcruise src test", "depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg", "postinstall": "npx lefthook install && npx lefthook run post-merge", "update-version:patch": "npm version patch --force --no-git-tag-version", diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index 131086625df..be060b57e9c 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -1,12 +1,18 @@ import { globalScene } from "#app/global-scene"; -import { allMoves } from "./data-lists"; +import { allMoves } from "#app/data/data-lists"; import { MoveFlags } from "#enums/MoveFlags"; -import type Pokemon from "../field/pokemon"; -import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName, coerceArray } from "../utils/common"; +import type Pokemon from "#app/field/pokemon"; +import { + type nil, + getFrameMs, + getEnumKeys, + getEnumValues, + animationFileName, + coerceArray, + isNullOrUndefined, +} from "#app/utils/common"; import type { BattlerIndex } from "#enums/battler-index"; import { MoveId } from "#enums/move-id"; -import { SubstituteTag } from "./battler-tags"; -import { isNullOrUndefined } from "../utils/common"; import Phaser from "phaser"; import { EncounterAnim } from "#enums/encounter-anims"; import { AnimBlendType, AnimFrameTarget, AnimFocus, ChargeAnim, CommonAnim } from "#enums/move-anims-common"; @@ -845,7 +851,7 @@ export abstract class BattleAnim { return; } - const targetSubstitute = !!onSubstitute && user !== target ? target.getTag(SubstituteTag) : null; + const targetSubstitute = !!onSubstitute && user !== target ? target.getTag(BattlerTagType.SUBSTITUTE) : null; const userSprite = user.getSprite(); const targetSprite = targetSubstitute?.sprite ?? target.getSprite(); From 5efdb0dc0b5104ee54c35121710b8ac90adbfb3b Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sun, 15 Jun 2025 13:52:44 -0400 Subject: [PATCH 47/51] [Refactor] Fix issues with "last move selected" vs "used" (#5810) * Added `MoveUseType` and refactored MEP * Fixed Wimp out tests & ME code finally i think all the booleans are gone i hope * Added version migration for last resort and co. buh gumbug * Fixed various bugs and added tests for previous bugfixes * Reverted a couple doc changes * WIP * Update pokemon-species.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update pokemon-phase.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Fixed remaining tests (I think) * Reverted rollout test changes * Fixed command phase bug causing metronome test timeout * Revert early_bird.test.ts * Fix biome.jsonc * Made `MoveUseType` start at 1 As per @DayKev's request * Fixed a thing * Fixed bolt beak condition to be marginally less jank * Applied some review suggestions * Reverted move phase operations * Added helper functions complete with markdown tables * Fixed things * Update battler-tags.ts * Fixed random issues * Fixed code * Fixed comment * Fixed import issues * Fix disable.test.ts conflicts * Update instruct.test.ts * Update `biome.jsonc` * Renamed `MoveUseType` to `MoveUseMode`; applied review comments * Fixed space * Fixed phasemanager bugs * Fixed instruct test to not bork * Fixed gorilla tactics bug * Battler Tags doc fixes * Fixed formatting and suttff * Minor comment updates and remove unused imports in `move.ts` * Re-add `public`, remove unnecessary default value in `battler-tags.ts` * Restore `{}` in `turn-start-phase.ts` Fixes `lint/correctness/noSwitchDeclarations` * Remove extra space in TSDoc in `move-phase.ts` * Use `game.field` instead of `game.scene` in `instruct.test.ts` Also `game.toEndOfTurn()` instead of `game.phaseInterceptor.to("BerryPhase")` * Use `game.field` instead of `game.scene` in `metronome.test.ts` * Use `toEndOfTurn()` instead of `to("BerryPhase")` in `powder.test.ts` * Convert `MoveUseMode` enum to `const` object * Update move-phase.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Add `enumValueToKey` utility function * Apply Biome --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- biome.jsonc | 23 +- src/data/abilities/ability.ts | 59 ++- src/data/arena-tag.ts | 5 +- src/data/battler-tags.ts | 199 +++++---- src/data/moves/invalid-moves.ts | 27 +- src/data/moves/move.ts | 405 ++++++++++-------- src/data/moves/pokemon-move.ts | 7 +- .../encounters/absolute-avarice-encounter.ts | 3 +- .../encounters/clowning-around-encounter.ts | 10 +- .../encounters/dancing-lessons-encounter.ts | 3 +- .../encounters/fiery-fallout-encounter.ts | 8 +- .../slumbering-snorlax-encounter.ts | 3 +- .../encounters/the-strong-stuff-encounter.ts | 5 +- .../encounters/trash-to-treasure-encounter.ts | 5 +- .../encounters/uncommon-breed-encounter.ts | 3 +- .../mystery-encounters/mystery-encounter.ts | 6 +- .../utils/encounter-phase-utils.ts | 30 +- src/data/pokemon-species.ts | 7 +- src/enums/battler-tag-lapse-type.ts | 29 +- src/enums/move-use-mode.ts | 149 +++++++ src/field/arena.ts | 3 + src/field/pokemon.ts | 101 +++-- src/overrides.ts | 8 +- src/phase-manager.ts | 2 +- src/phases/command-phase.ts | 26 +- src/phases/move-charge-phase.ts | 60 ++- src/phases/move-effect-phase.ts | 140 +++--- src/phases/move-end-phase.ts | 2 +- src/phases/move-phase.ts | 281 ++++++------ src/phases/pokemon-phase.ts | 12 +- src/phases/pokemon-transform-phase.ts | 2 +- src/phases/turn-start-phase.ts | 70 ++- .../version_migration/version_converter.ts | 5 + .../version_migration/versions/v1_10_0.ts | 48 +++ src/ui/fight-ui-handler.ts | 83 ++-- src/ui/party-ui-handler.ts | 12 +- src/utils/common.ts | 22 + test/abilities/gorilla_tactics.test.ts | 54 ++- test/data/status_effect.test.ts | 3 +- test/moves/after_you.test.ts | 34 ++ test/moves/copycat.test.ts | 30 +- test/moves/dig.test.ts | 20 +- test/moves/disable.test.ts | 127 +++--- test/moves/electro_shot.test.ts | 2 +- test/moves/instruct.test.ts | 194 ++++++--- test/moves/last-resort.test.ts | 10 +- test/moves/metronome.test.ts | 48 ++- test/moves/powder.test.ts | 16 +- test/moves/quash.test.ts | 38 +- test/moves/spit_up.test.ts | 7 +- test/moves/stockpile.test.ts | 3 +- test/moves/swallow.test.ts | 7 +- .../fun-and-games-encounter.test.ts | 15 +- test/testUtils/gameManager.ts | 12 +- test/testUtils/helpers/moveHelper.ts | 11 +- 55 files changed, 1594 insertions(+), 900 deletions(-) create mode 100644 src/enums/move-use-mode.ts create mode 100644 src/system/version_migration/versions/v1_10_0.ts diff --git a/biome.jsonc b/biome.jsonc index f427debb198..d183334ad58 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -28,7 +28,6 @@ ".vscode/*", "*.css", // TODO? "*.html", // TODO? - "src/overrides.ts", // TODO: these files are too big and complex, ignore them until their respective refactors "src/data/moves/move.ts", @@ -120,6 +119,28 @@ } } } + }, + + // Overrides to prevent unused import removal inside `overrides.ts` and enums files (for TSDoc linkcodes) + { + "include": ["src/overrides.ts", "src/enums/*"], + "linter": { + "rules": { + "correctness": { + "noUnusedImports": "off" + } + } + } + }, + { + "include": ["src/overrides.ts"], + "linter": { + "rules": { + "style": { + "useImportType": "off" + } + } + } } ] } diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index ef4529c361e..c9684986c46 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -7,7 +7,6 @@ import { isNullOrUndefined, randSeedItem, randSeedInt, - type Constructor, randSeedFloat, coerceArray, } from "#app/utils/common"; @@ -35,12 +34,11 @@ import { Command } from "#enums/command"; import { BerryModifierType } from "#app/modifier/modifier-type"; import { getPokeballName } from "#app/data/pokeball"; import { BattleType } from "#enums/battle-type"; -import type { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { globalScene } from "#app/global-scene"; import { allAbilities } from "#app/data/data-lists"; // Enum imports -import { Stat, type BattleStat, BATTLE_STATS, EFFECTIVE_STATS, getStatKey, type EffectiveStat } from "#enums/stat"; +import { Stat, BATTLE_STATS, EFFECTIVE_STATS, getStatKey } from "#enums/stat"; import { PokemonType } from "#enums/pokemon-type"; import { PokemonAnimType } from "#enums/pokemon-anim-type"; import { StatusEffect } from "#enums/status-effect"; @@ -54,12 +52,16 @@ import { SwitchType } from "#enums/switch-type"; import { MoveFlags } from "#enums/MoveFlags"; import { MoveTarget } from "#enums/MoveTarget"; import { MoveCategory } from "#enums/MoveCategory"; -import type { BerryType } from "#enums/berry-type"; import { CommonAnim } from "#enums/move-anims-common"; -import { getBerryEffectFunc } from "../berry"; +import { getBerryEffectFunc } from "#app/data/berry"; import { BerryUsedEvent } from "#app/events/battle-scene"; +import { noAbilityTypeOverrideMoves } from "#app/data/moves/invalid-moves"; +import { MoveUseMode } from "#enums/move-use-mode"; // Type imports +import type { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; +import type { BattleStat, EffectiveStat } from "#enums/stat"; +import type { BerryType } from "#enums/berry-type"; import type { EnemyPokemon } from "#app/field/pokemon"; import type { PokemonMove } from "../moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; @@ -76,7 +78,7 @@ import type { import type { BattlerIndex } from "#enums/battler-index"; import type Move from "#app/data/moves/move"; import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag"; -import { noAbilityTypeOverrideMoves } from "../moves/invalid-moves"; +import type { Constructor } from "#app/utils/common"; import type { Localizable } from "#app/@types/locales"; import { applyAbAttrs } from "./apply-ab-attrs"; @@ -1915,7 +1917,7 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { _args: any[], ): boolean { return ( - attacker.getTag(BattlerTagType.DISABLED) === null && + isNullOrUndefined(attacker.getTag(BattlerTagType.DISABLED)) && move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) && (this.chance === -1 || pokemon.randBattleSeedInt(100) < this.chance) ); @@ -2733,7 +2735,6 @@ export class AllyStatMultiplierAbAttr extends AbAttr { /** * Takes effect whenever a move succesfully executes, such as gorilla tactics' move-locking. * (More specifically, whenever a move is pushed to the move history) - * @extends AbAttr */ export class ExecutedMoveAbAttr extends AbAttr { canApplyExecutedMove(_pokemon: Pokemon, _simulated: boolean): boolean { @@ -2744,16 +2745,16 @@ export class ExecutedMoveAbAttr extends AbAttr { } /** - * Ability attribute for Gorilla Tactics - * @extends ExecutedMoveAbAttr + * Ability attribute for {@linkcode AbilityId.GORILLA_TACTICS | Gorilla Tactics} + * to lock the user into its first selected move. */ export class GorillaTacticsAbAttr extends ExecutedMoveAbAttr { constructor(showAbility = false) { super(showAbility); } - override canApplyExecutedMove(pokemon: Pokemon, simulated: boolean): boolean { - return simulated || !pokemon.getTag(BattlerTagType.GORILLA_TACTICS); + override canApplyExecutedMove(pokemon: Pokemon, _simulated: boolean): boolean { + return !pokemon.getTag(BattlerTagType.GORILLA_TACTICS); } override applyExecutedMove(pokemon: Pokemon, simulated: boolean): void { @@ -6063,14 +6064,19 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { ): void { if (!simulated) { dancer.turnData.extraTurns++; - const phaseManager = globalScene.phaseManager; // If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance if (move.getMove().is("AttackMove") || move.getMove().is("StatusMove")) { const target = this.getTarget(dancer, source, targets); - phaseManager.unshiftNew("MovePhase", dancer, target, move, true, true); + globalScene.phaseManager.unshiftNew("MovePhase", dancer, target, move, MoveUseMode.INDIRECT); } else if (move.getMove().is("SelfStatusMove")) { // If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself - phaseManager.unshiftNew("MovePhase", dancer, [dancer.getBattlerIndex()], move, true, true); + globalScene.phaseManager.unshiftNew( + "MovePhase", + dancer, + [dancer.getBattlerIndex()], + move, + MoveUseMode.INDIRECT, + ); } } } @@ -7378,6 +7384,7 @@ class ForceSwitchOutHelper { * @param pokemon The {@linkcode Pokemon} attempting to switch out. * @returns `true` if the switch is successful */ + // TODO: Make this cancel pending move phases on the switched out target public switchOutLogic(pokemon: Pokemon): boolean { const switchOutTarget = pokemon; /** @@ -8378,10 +8385,10 @@ export function initAbilities() { .attr(WonderSkinAbAttr) .ignorable(), new Ability(AbilityId.ANALYTIC, 5) - .attr(MovePowerBoostAbAttr, (user, _target, _move) => { - const movePhase = globalScene.phaseManager.findPhase((phase) => phase.is("MovePhase") && phase.pokemon.id !== user?.id); - return isNullOrUndefined(movePhase); - }, 1.3), + .attr(MovePowerBoostAbAttr, (user) => + // Boost power if all other Pokemon have already moved (no other moves are slated to execute) + !globalScene.phaseManager.findPhase((phase) => phase.is("MovePhase") && phase.pokemon.id !== user?.id), + 1.3), new Ability(AbilityId.ILLUSION, 5) // The Pokemon generate an illusion if it's available .attr(IllusionPreSummonAbAttr, false) @@ -8655,7 +8662,13 @@ export function initAbilities() { .attr(PostFaintHPDamageAbAttr) .bypassFaint(), new Ability(AbilityId.DANCER, 7) - .attr(PostDancingMoveAbAttr), + .attr(PostDancingMoveAbAttr) + /* Incorrect interations with: + * Petal Dance (should not lock in or count down timer; currently does both) + * Flinches (due to tag being removed earlier) + * Failed/protected moves (should not trigger if original move is protected against) + */ + .edgeCase(), new Ability(AbilityId.BATTERY, 7) .attr(AllyMoveCategoryPowerBoostAbAttr, [ MoveCategory.SPECIAL ], 1.3), new Ability(AbilityId.FLUFFY, 7) @@ -8796,9 +8809,11 @@ export function initAbilities() { new Ability(AbilityId.WANDERING_SPIRIT, 8) .attr(PostDefendAbilitySwapAbAttr) .bypassFaint() - .edgeCase(), // interacts incorrectly with rock head. It's meant to switch abilities before recoil would apply so that a pokemon with rock head would lose rock head first and still take the recoil + .edgeCase(), // interacts incorrectly with rock head. It's meant to switch abilities before recoil would apply so that a pokemon with rock head would lose rock head first and still take the recoil new Ability(AbilityId.GORILLA_TACTICS, 8) - .attr(GorillaTacticsAbAttr), + .attr(GorillaTacticsAbAttr) + // TODO: Verify whether Gorilla Tactics increases struggle's power or not + .edgeCase(), new Ability(AbilityId.NEUTRALIZING_GAS, 8, 2) .attr(PostSummonAddArenaTagAbAttr, true, ArenaTagType.NEUTRALIZING_GAS, 0) .attr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index da1bbbda2e9..494a0438b18 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -20,6 +20,7 @@ import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; import { MoveId } from "#enums/move-id"; import { ArenaTagSide } from "#enums/arena-tag-side"; +import { MoveUseMode } from "#enums/move-use-mode"; export abstract class ArenaTag { constructor( @@ -875,13 +876,13 @@ export class DelayedAttackTag extends ArenaTag { const ret = super.lapse(arena); if (!ret) { + // TODO: This should not add to move history (for Spite) globalScene.phaseManager.unshiftNew( "MoveEffectPhase", this.sourceId!, [this.targetIndex], allMoves[this.sourceMove!], - false, - true, + MoveUseMode.FOLLOW_UP, ); // TODO: are those bangs correct? } diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 98cefb78bd5..8405fd1dd4d 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -31,8 +31,15 @@ import { EFFECTIVE_STATS, getStatKey, Stat, type BattleStat, type EffectiveStat import { StatusEffect } from "#enums/status-effect"; import { WeatherType } from "#enums/weather-type"; import { isNullOrUndefined } from "#app/utils/common"; +import { MoveUseMode } from "#enums/move-use-mode"; +import { invalidEncoreMoves } from "./moves/invalid-moves"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; +/** + * A {@linkcode BattlerTag} represents a semi-persistent effect that can be attached to a {@linkcode Pokemon}. + * Tags can trigger various effects throughout a turn, and are cleared on switching out + * or through their respective {@linkcode BattlerTag.lapse | lapse} methods. + */ export class BattlerTag { public tagType: BattlerTagType; public lapseTypes: BattlerTagLapseType[]; @@ -69,7 +76,7 @@ export class BattlerTag { /** * Tick down this {@linkcode BattlerTag}'s duration. - * @returns `true` if the tag should be kept (`turnCount` > 0`) + * @returns `true` if the tag should be kept (`turnCount > 0`) */ lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { // TODO: Maybe flip this (return `true` if tag needs removal) @@ -267,17 +274,18 @@ export class DisabledTag extends MoveRestrictionBattlerTag { /** * @override * - * Ensures that move history exists on `pokemon` and has a valid move. If so, sets the {@linkcode moveId} and shows a message. - * Otherwise the move ID will not get assigned and this tag will get removed next turn. + * Attempt to disable the target's last move by setting this tag's {@linkcode moveId} + * and showing a message. */ override onAdd(pokemon: Pokemon): void { - super.onAdd(pokemon); - - const move = pokemon.getLastXMoves(-1).find(m => !m.virtual); - if (isNullOrUndefined(move) || move.move === MoveId.STRUGGLE || move.move === MoveId.NONE) { + // Disable fails against struggle or an empty move history + // TODO: Confirm if this is redundant given Disable/Cursed Body's disable conditions + const move = pokemon.getLastNonVirtualMove(); + if (isNullOrUndefined(move) || move.move === MoveId.STRUGGLE) { return; } + super.onAdd(pokemon); this.moveId = move.move; globalScene.phaseManager.queueMessage( @@ -327,7 +335,6 @@ export class DisabledTag extends MoveRestrictionBattlerTag { /** * Tag used by Gorilla Tactics to restrict the user to using only one move. - * @extends MoveRestrictionBattlerTag */ export class GorillaTacticsTag extends MoveRestrictionBattlerTag { private moveId = MoveId.NONE; @@ -336,34 +343,30 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag { super(BattlerTagType.GORILLA_TACTICS, BattlerTagLapseType.CUSTOM, 0); } - /** @override */ override isMoveRestricted(move: MoveId): boolean { return move !== this.moveId; } /** - * @override - * @param {Pokemon} pokemon the {@linkcode Pokemon} to check if the tag can be added - * @returns `true` if the pokemon has a valid move and no existing {@linkcode GorillaTacticsTag}; `false` otherwise + * Ensures that move history exists on {@linkcode Pokemon} and has a valid move to lock into. + * @param pokemon - The {@linkcode Pokemon} to add the tag to + * @returns `true` if the tag can be added */ override canAdd(pokemon: Pokemon): boolean { - return this.getLastValidMove(pokemon) !== undefined && !pokemon.getTag(GorillaTacticsTag); + // Choice items ignore struggle, so Gorilla Tactics should too + const lastSelectedMove = pokemon.getLastNonVirtualMove(); + return !isNullOrUndefined(lastSelectedMove) && lastSelectedMove.move !== MoveId.STRUGGLE; } /** - * Ensures that move history exists on {@linkcode Pokemon} and has a valid move. - * If so, sets the {@linkcode moveId} and increases the user's Attack by 50%. - * @override - * @param {Pokemon} pokemon the {@linkcode Pokemon} to add the tag to + * Sets this tag's {@linkcode moveId} and increases the user's Attack by 50%. + * @param pokemon - The {@linkcode Pokemon} to add the tag to */ override onAdd(pokemon: Pokemon): void { - const lastValidMove = this.getLastValidMove(pokemon); + super.onAdd(pokemon); - if (!lastValidMove) { - return; - } - - this.moveId = lastValidMove; + // Bang is justified as tag is not added if prior move doesn't exist + this.moveId = pokemon.getLastNonVirtualMove()!.move; pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.ATK, false) * 1.5, false); } @@ -378,29 +381,16 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag { } /** - * - * @override - * @param {Pokemon} pokemon n/a - * @param {MoveId} _move {@linkcode MoveId} ID of the move being denied - * @returns {string} text to display when the move is denied + * Return the text displayed when a move is restricted. + * @param pokemon - The {@linkcode Pokemon} with this tag. + * @returns A string containing the text to display when the move is denied */ - override selectionDeniedText(pokemon: Pokemon, _move: MoveId): string { + override selectionDeniedText(pokemon: Pokemon): string { return i18next.t("battle:canOnlyUseMove", { moveName: allMoves[this.moveId].name, pokemonName: getPokemonNameWithAffix(pokemon), }); } - - /** - * Gets the last valid move from the pokemon's move history. - * @param {Pokemon} pokemon {@linkcode Pokemon} to get the last valid move from - * @returns {MoveId | undefined} the last valid move from the pokemon's move history - */ - getLastValidMove(pokemon: Pokemon): MoveId | undefined { - const move = pokemon.getLastXMoves().find(m => m.move !== MoveId.NONE && m.move !== MoveId.STRUGGLE && !m.virtual); - - return move?.move; - } } /** @@ -414,8 +404,8 @@ export class RechargingTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - // Queue a placeholder move for the Pokemon to "use" next turn - pokemon.getMoveQueue().push({ move: MoveId.NONE, targets: [] }); + // Queue a placeholder move for the Pokemon to "use" next turn. + pokemon.pushMoveQueue({ move: MoveId.NONE, targets: [], useMode: MoveUseMode.NORMAL }); } /** Cancels the source's move this turn and queues a "__ must recharge!" message */ @@ -660,6 +650,7 @@ export class InterruptedTag extends BattlerTag { move: MoveId.NONE, result: MoveResult.OTHER, targets: [], + useMode: MoveUseMode.NORMAL, }); } @@ -985,42 +976,45 @@ export class PowderTag extends BattlerTag { } /** - * Applies Powder's effects before the tag owner uses a Fire-type move. - * Also causes the tag to expire at the end of turn. - * @param pokemon {@linkcode Pokemon} the owner of this tag - * @param lapseType {@linkcode BattlerTagLapseType} the type of lapse functionality to carry out - * @returns `true` if the tag should not expire after this lapse; `false` otherwise. + * Applies Powder's effects before the tag owner uses a Fire-type move, damaging and canceling its action. + * Lasts until the end of the turn. + * @param pokemon - The {@linkcode Pokemon} with this tag. + * @param lapseType - The {@linkcode BattlerTagLapseType} dictating how this tag is being activated + * @returns `true` if the tag should remain active. */ lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { - if (lapseType === BattlerTagLapseType.PRE_MOVE) { - const movePhase = globalScene.phaseManager.getCurrentPhase(); - if (movePhase?.is("MovePhase")) { - const move = movePhase.move.getMove(); - const weather = globalScene.arena.weather; - if ( - pokemon.getMoveType(move) === PokemonType.FIRE && - !(weather && weather.weatherType === WeatherType.HEAVY_RAIN && !weather.isEffectSuppressed()) - ) { - movePhase.fail(); - movePhase.showMoveText(); + const movePhase = globalScene.phaseManager.getCurrentPhase(); + if (lapseType !== BattlerTagLapseType.PRE_MOVE || !movePhase?.is("MovePhase")) { + return false; + } - const idx = pokemon.getBattlerIndex(); - - globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER); - - const cancelDamage = new BooleanHolder(false); - applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelDamage); - if (!cancelDamage.value) { - pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT }); - } - - // "When the flame touched the powder\non the Pokémon, it exploded!" - globalScene.phaseManager.queueMessage(i18next.t("battlerTags:powderLapse", { moveName: move.name })); - } - } + const move = movePhase.move.getMove(); + const weather = globalScene.arena.weather; + if ( + pokemon.getMoveType(move) !== PokemonType.FIRE || + (weather?.weatherType === WeatherType.HEAVY_RAIN && !weather.isEffectSuppressed()) // Heavy rain takes priority over powder + ) { return true; } - return super.lapse(pokemon, lapseType); + + // Disable the target's fire type move and damage it (subject to Magic Guard) + movePhase.showMoveText(); + movePhase.fail(); + + const idx = pokemon.getBattlerIndex(); + + globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER); + + const cancelDamage = new BooleanHolder(false); + applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelDamage); + if (!cancelDamage.value) { + pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT }); + } + + // "When the flame touched the powder\non the Pokémon, it exploded!" + globalScene.phaseManager.queueMessage(i18next.t("battlerTags:powderLapse", { moveName: move.name })); + + return true; } } @@ -1115,34 +1109,22 @@ export class EncoreTag extends MoveRestrictionBattlerTag { } canAdd(pokemon: Pokemon): boolean { - const lastMoves = pokemon.getLastXMoves(1); - if (!lastMoves.length) { + const lastMove = pokemon.getLastNonVirtualMove(); + if (!lastMove) { return false; } - const repeatableMove = lastMoves[0]; - - if (!repeatableMove.move || repeatableMove.virtual) { + if (invalidEncoreMoves.has(lastMove.move)) { return false; } - switch (repeatableMove.move) { - case MoveId.MIMIC: - case MoveId.MIRROR_MOVE: - case MoveId.TRANSFORM: - case MoveId.STRUGGLE: - case MoveId.SKETCH: - case MoveId.SLEEP_TALK: - case MoveId.ENCORE: - return false; - } - - this.moveId = repeatableMove.move; + this.moveId = lastMove.move; return true; } onAdd(pokemon: Pokemon): void { + // TODO: shouldn't this be `onAdd`? super.onRemove(pokemon); globalScene.phaseManager.queueMessage( @@ -1158,7 +1140,13 @@ export class EncoreTag extends MoveRestrictionBattlerTag { const lastMove = pokemon.getLastXMoves(1)[0]; globalScene.phaseManager.tryReplacePhase( m => m.is("MovePhase") && m.pokemon === pokemon, - globalScene.phaseManager.create("MovePhase", pokemon, lastMove.targets ?? [], movesetMove), + globalScene.phaseManager.create( + "MovePhase", + pokemon, + lastMove.targets ?? [], + movesetMove, + MoveUseMode.NORMAL, + ), ); } } @@ -1884,24 +1872,29 @@ export class TruantTag extends AbilityBattlerTag { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (!pokemon.hasAbility(AbilityId.TRUANT)) { + // remove tag if mon lacks ability return super.lapse(pokemon, lapseType); } - const passive = pokemon.getAbility().id !== AbilityId.TRUANT; - const lastMove = pokemon.getLastXMoves().find(() => true); + const lastMove = pokemon.getLastXMoves()[0]; - if (lastMove && lastMove.move !== MoveId.NONE) { - (globalScene.phaseManager.getCurrentPhase() as MovePhase).cancel(); - // TODO: Ability displays should be handled by the ability - globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true); - globalScene.phaseManager.queueMessage( - i18next.t("battlerTags:truantLapse", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - }), - ); - globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false); + if (!lastMove) { + // Don't interrupt move if last move was `Moves.NONE` OR no prior move was found + return true; } + // Interrupt move usage in favor of slacking off + const passive = pokemon.getAbility().id !== AbilityId.TRUANT; + (globalScene.phaseManager.getCurrentPhase() as MovePhase).cancel(); + // TODO: Ability displays should be handled by the ability + globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true); + globalScene.phaseManager.queueMessage( + i18next.t("battlerTags:truantLapse", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + }), + ); + globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false); + return true; } } diff --git a/src/data/moves/invalid-moves.ts b/src/data/moves/invalid-moves.ts index 559b679752d..6d97d8faf1e 100644 --- a/src/data/moves/invalid-moves.ts +++ b/src/data/moves/invalid-moves.ts @@ -1,6 +1,6 @@ import { MoveId } from "#enums/move-id"; -/** Set of moves that cannot be called by {@linkcode MoveId.METRONOME Metronome} */ +/** Set of moves that cannot be called by {@linkcode MoveId.METRONOME | Metronome}. */ export const invalidMetronomeMoves: ReadonlySet = new Set([ MoveId.AFTER_YOU, MoveId.ASSIST, @@ -255,3 +255,28 @@ export const noAbilityTypeOverrideMoves: ReadonlySet = new Set([ MoveId.TECHNO_BLAST, MoveId.HIDDEN_POWER, ]); + +/** Set of all moves that cannot be copied by {@linkcode Moves.SKETCH}. */ +export const invalidSketchMoves: ReadonlySet = new Set([ + MoveId.NONE, + MoveId.CHATTER, + MoveId.MIRROR_MOVE, + MoveId.SLEEP_TALK, + MoveId.STRUGGLE, + MoveId.SKETCH, + MoveId.REVIVAL_BLESSING, + MoveId.TERA_STARSTORM, + MoveId.BREAKNECK_BLITZ__PHYSICAL, + MoveId.BREAKNECK_BLITZ__SPECIAL, +]); + +/** Set of all moves that cannot be locked into by {@linkcode Moves.ENCORE}. */ +export const invalidEncoreMoves: ReadonlySet = new Set([ + MoveId.MIMIC, + MoveId.MIRROR_MOVE, + MoveId.TRANSFORM, + MoveId.STRUGGLE, + MoveId.SKETCH, + MoveId.SLEEP_TALK, + MoveId.ENCORE, +]); diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index b46f109db9e..f61e8debc9f 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -70,13 +70,9 @@ import { getStatKey, Stat, } from "#app/enums/stat"; -import { BattleEndPhase } from "#app/phases/battle-end-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { MovePhase } from "#app/phases/move-phase"; -import { NewBattlePhase } from "#app/phases/new-battle-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; -import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; -import { SwitchPhase } from "#app/phases/switch-phase"; import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; import { SpeciesFormChangeRevertWeatherFormTrigger } from "../pokemon-forms/form-change-triggers"; import type { GameMode } from "#app/game-mode"; @@ -85,18 +81,14 @@ import { ChallengeType } from "#enums/challenge-type"; import { SwitchType } from "#enums/switch-type"; import { StatusEffect } from "#enums/status-effect"; import { globalScene } from "#app/global-scene"; -import { RevivalBlessingPhase } from "#app/phases/revival-blessing-phase"; -import { LoadMoveAnimPhase } from "#app/phases/load-move-anim-phase"; -import { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase"; -import { MoveAnimPhase } from "#app/phases/move-anim-phase"; import { loggedInUser } from "#app/account"; import { MoveCategory } from "#enums/MoveCategory"; import { MoveTarget } from "#enums/MoveTarget"; import { MoveFlags } from "#enums/MoveFlags"; import { MoveEffectTrigger } from "#enums/MoveEffectTrigger"; import { MultiHitType } from "#enums/MultiHitType"; -import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves } from "./invalid-moves"; -import { SelectBiomePhase } from "#app/phases/select-biome-phase"; +import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves, invalidSketchMoves } from "./invalid-moves"; +import { isVirtual, MoveUseMode } from "#enums/move-use-mode"; import { ChargingMove, MoveAttrMap, MoveAttrString, MoveKindString, MoveClassMap } from "#app/@types/move-types"; import { applyMoveAttrs } from "./apply-attrs"; import { frenzyMissFunc, getMoveTargets } from "./move-utils"; @@ -126,10 +118,10 @@ export default abstract class Move implements Localizable { /** * Check if the move is of the given subclass without requiring `instanceof`. - * + * * ⚠️ Does _not_ work for {@linkcode ChargingAttackMove} and {@linkcode ChargingSelfStatusMove} subclasses. For those, * use {@linkcode isChargingMove} instead. - * + * * @param moveKind - The string name of the move to check against * @returns Whether this move is of the provided type. */ @@ -3124,7 +3116,7 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr { overridden.value = true; globalScene.phaseManager.unshiftNew("MoveAnimPhase", new MoveChargeAnim(this.chargeAnim, move.id, 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, useMode: MoveUseMode.NORMAL }); const side = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; globalScene.arena.addTag(this.tagType, 3, move.id, user.id, side, false, target.getBattlerIndex()); } else { @@ -4081,15 +4073,26 @@ export class OpponentHighHpPowerAttr extends VariablePowerAttr { } } +/** + * Attribute to double this move's power if the target hasn't acted yet in the current turn. + * Used by {@linkcode Moves.BOLT_BEAK} and {@linkcode Moves.FISHIOUS_REND} + */ export class FirstAttackDoublePowerAttr extends VariablePowerAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - console.log(target.getLastXMoves(1), globalScene.currentBattle.turn); - if (!target.getLastXMoves(1).find(m => m.turn === globalScene.currentBattle.turn)) { - (args[0] as NumberHolder).value *= 2; - return true; + /** + * Double this move's power if the user is acting before the target. + * @param user - Unused + * @param target - The {@linkcode Pokemon} being targeted by this move + * @param move - Unused + * @param args `[0]` - A {@linkcode NumberHolder} containing move base power + * @returns Whether the attribute was successfully applied + */ + apply(_user: Pokemon, target: Pokemon, move: Move, args: [NumberHolder]): boolean { + if (target.turnData.acted) { + return false; } - return false; + args[0].value *= 2; + return true; } } @@ -5459,13 +5462,20 @@ export class FrenzyAttr extends MoveEffectAttr { return false; } - if (!user.getTag(BattlerTagType.FRENZY) && !user.getMoveQueue().length) { - const turnCount = user.randBattleSeedIntRange(1, 2); - new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true })); + // TODO: Disable if used via dancer + // TODO: Add support for moves that don't add the frenzy tag (Uproar, Rollout, etc.) + + // If frenzy is not active, add a tag and push 1-2 extra turns of attacks to the user's move queue. + // Otherwise, tick down the existing tag. + if (!user.getTag(BattlerTagType.FRENZY) && user.getMoveQueue().length === 0) { + const turnCount = user.randBattleSeedIntRange(1, 2); // excludes initial use + for (let i = 0; i < turnCount; i++) { + user.pushMoveQueue({ move: move.id, targets: [ target.getBattlerIndex() ], useMode: MoveUseMode.IGNORE_PP }); + } user.addTag(BattlerTagType.FRENZY, turnCount, move.id, user.id); } else { applyMoveAttrs("AddBattlerTagAttr", user, target, move, args); - user.lapseTag(BattlerTagType.FRENZY); // if FRENZY is already in effect (moveQueue.length > 0), lapse the tag + user.lapseTag(BattlerTagType.FRENZY); } return true; @@ -6220,6 +6230,7 @@ export class RevivalBlessingAttr extends MoveEffectAttr { 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 // (revived pokemon can't move in the turn they're brought back) + // TODO: might make sense to move this to `FaintPhase` after checking for Rev Seed (rather than handling it in the move) globalScene.phaseManager.findPhase((phase: MovePhase) => phase.pokemon === pokemon)?.cancel(); if (user.fieldPosition === FieldPosition.CENTER) { user.setFieldPosition(FieldPosition.LEFT); @@ -6761,20 +6772,26 @@ export class FirstMoveTypeAttr extends MoveEffectAttr { class CallMoveAttr extends OverrideMoveEffectAttr { protected invalidMoves: ReadonlySet; protected hasTarget: boolean; + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + // Get eligible targets for move, failing if we can't target anything const replaceMoveTarget = move.moveTarget === MoveTarget.NEAR_OTHER ? MoveTarget.NEAR_ENEMY : undefined; const moveTargets = getMoveTargets(user, move.id, replaceMoveTarget); if (moveTargets.targets.length === 0) { globalScene.phaseManager.queueMessage(i18next.t("battle:attackFailed")); - console.log("CallMoveAttr failed due to no targets."); return false; } + + // Spread moves and ones with only 1 valid target will use their normal targeting. + // If not, target the Mirror Move recipient or else a random enemy in our target list const targets = moveTargets.multiple || moveTargets.targets.length === 1 ? moveTargets.targets - : [ 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 }); + : [this.hasTarget + ? target.getBattlerIndex() + : moveTargets.targets[user.randBattleSeedInt(moveTargets.targets.length)]]; + globalScene.phaseManager.unshiftNew("LoadMoveAnimPhase", move.id); - globalScene.phaseManager.unshiftNew("MovePhase", user, targets, new PokemonMove(move.id, 0, 0, true), true, true); + globalScene.phaseManager.unshiftNew("MovePhase", user, targets, new PokemonMove(move.id), MoveUseMode.FOLLOW_UP); return true; } } @@ -6869,9 +6886,10 @@ export class RandomMovesetMoveAttr extends CallMoveAttr { } } +// TODO: extend CallMoveAttr export class NaturePowerAttr extends OverrideMoveEffectAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - let moveId; + let moveId = MoveId.NONE; switch (globalScene.arena.getTerrainType()) { // this allows terrains to 'override' the biome move case TerrainType.NONE: @@ -6996,14 +7014,14 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr { moveId = MoveId.PSYCHIC; break; default: - // Just in case there's no match + // Just in case there's no match moveId = MoveId.TRI_ATTACK; break; } - user.getMoveQueue().push({ move: moveId, targets: [ target.getBattlerIndex() ], ignorePP: true }); + // Load the move's animation if we didn't already and unshift a new usage phase globalScene.phaseManager.unshiftNew("LoadMoveAnimPhase", moveId); - globalScene.phaseManager.unshiftNew("MovePhase", user, [ target.getBattlerIndex() ], new PokemonMove(moveId, 0, 0, true), true); + globalScene.phaseManager.unshiftNew("MovePhase", user, [ target.getBattlerIndex() ], new PokemonMove(moveId), MoveUseMode.FOLLOW_UP); return true; } } @@ -7022,64 +7040,63 @@ export class CopyMoveAttr extends CallMoveAttr { this.invalidMoves = invalidMoves; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { this.hasTarget = this.mirrorMove; - const lastMove = this.mirrorMove ? target.getLastXMoves()[0].move : globalScene.currentBattle.lastMove; + // bang is correct as condition func returns `false` and fails move if no last move exists + const lastMove = this.mirrorMove ? target.getLastNonVirtualMove(false, false)!.move : globalScene.currentBattle.lastMove; return super.apply(user, target, allMoves[lastMove], args); } getCondition(): MoveConditionFunc { - return (user, target, move) => { - if (this.mirrorMove) { - const lastMove = target.getLastXMoves()[0]?.move; - return !!lastMove && !this.invalidMoves.has(lastMove); - } else { - const lastMove = globalScene.currentBattle.lastMove; - return lastMove !== undefined && !this.invalidMoves.has(lastMove); - } + return (_user, target, _move) => { + const lastMove = this.mirrorMove ? target.getLastNonVirtualMove(false, false)?.move : globalScene.currentBattle.lastMove; + return !isNullOrUndefined(lastMove) && !this.invalidMoves.has(lastMove); }; } } /** - * Attribute used for moves that causes the target to repeat their last used move. + * Attribute used for moves that cause the target to repeat their last used move. * * Used for [Instruct](https://bulbapedia.bulbagarden.net/wiki/Instruct_(move)). */ export class RepeatMoveAttr extends MoveEffectAttr { + private movesetMove: PokemonMove; constructor() { super(false, { trigger: MoveEffectTrigger.POST_APPLY }); // needed to ensure correct protect interaction } /** - * Forces the target to re-use their last used move again - * - * @param user {@linkcode Pokemon} that used the attack - * @param target {@linkcode Pokemon} targeted by the attack - * @param move N/A - * @param args N/A + * Forces the target to re-use their last used move again. + * @param user - The {@linkcode Pokemon} using the attack + * @param target - The {@linkcode Pokemon} being targeted by the attack * @returns `true` if the move succeeds */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon): boolean { // get the last move used (excluding status based failures) as well as the corresponding moveset slot - const lastMove = target.getLastXMoves(-1).find(m => m.move !== MoveId.NONE)!; - const movesetMove = target.getMoveset().find(m => m.moveId === lastMove.move)!; - // If the last move used can hit more than one target or has variable targets, - // re-compute the targets for the attack - // (mainly for alternating double/single battle shenanigans) - // Rampaging moves (e.g. Outrage) are not included due to being incompatible with Instruct - // TODO: Fix this once dragon darts gets smart targeting - let moveTargets = movesetMove.getMove().isMultiTarget() ? getMoveTargets(target, lastMove.move).targets : lastMove.targets; + // bangs are justified as Instruct fails if no prior move or moveset move exists + // TODO: How does instruct work when copying a move called via Copycat that the user itself knows? + const lastMove = target.getLastNonVirtualMove()!; + const movesetMove = target.getMoveset().find(m => m.moveId === lastMove?.move)! - /** In the event the instructed move's only target is a fainted opponent, redirect it to an alive ally if possible - Normally, all yet-unexecuted move phases would swap over when the enemy in question faints - (see `redirectPokemonMoves` in `battle-scene.ts`), - but since instruct adds a new move phase pre-emptively, we need to handle this interaction manually. - */ + // If the last move used can hit more than one target or has variable targets, + // re-compute the targets for the attack (mainly for alternating double/single battles) + // Rampaging moves (e.g. Outrage) are not included due to being incompatible with Instruct, + // nor is Dragon Darts (due to its smart targeting bypassing normal target selection) + let moveTargets = this.movesetMove.getMove().isMultiTarget() ? getMoveTargets(target, this.movesetMove.moveId).targets : lastMove.targets; + + // In the event the instructed move's only target is a fainted opponent, redirect it to an alive ally if possible. + // Normally, all yet-unexecuted move phases would swap targets after any foe faints or flees (see `redirectPokemonMoves` in `battle-scene.ts`), + // but since Instruct adds a new move phase _after_ all that occurs, we need to handle this interaction manually. const firstTarget = globalScene.getField()[moveTargets[0]]; - if (globalScene.currentBattle.double && moveTargets.length === 1 && firstTarget.isFainted() && firstTarget !== target.getAlly()) { + if ( + globalScene.currentBattle.double + && moveTargets.length === 1 + && firstTarget.isFainted() + && firstTarget !== target.getAlly() + ) { const ally = firstTarget.getAlly(); - if (!isNullOrUndefined(ally) && ally.isActive()) { // ally exists, is not dead and can sponge the blast + if (!isNullOrUndefined(ally) && ally.isActive()) { moveTargets = [ ally.getBattlerIndex() ]; } } @@ -7088,15 +7105,15 @@ export class RepeatMoveAttr extends MoveEffectAttr { userPokemonName: getPokemonNameWithAffix(user), targetPokemonName: getPokemonNameWithAffix(target) })); - target.getMoveQueue().unshift({ move: lastMove.move, targets: moveTargets, ignorePP: false }); target.turnData.extraTurns++; - globalScene.phaseManager.appendNewToPhase("MoveEndPhase", "MovePhase", target, moveTargets, movesetMove); + globalScene.phaseManager.appendNewToPhase("MoveEndPhase", "MovePhase", target, moveTargets, movesetMove, MoveUseMode.NORMAL); return true; } getCondition(): MoveConditionFunc { - return (user, target, move) => { - const lastMove = target.getLastXMoves(-1).find(m => m.move !== MoveId.NONE); + return (_user, target, _move) => { + // TODO: Check instruct behavior with struggle - ignore, fail or success + const lastMove = target.getLastNonVirtualMove(); const movesetMove = target.getMoveset().find(m => m.moveId === lastMove?.move); const uninstructableMoves = [ // Locking/Continually Executed moves @@ -7106,6 +7123,7 @@ export class RepeatMoveAttr extends MoveEffectAttr { MoveId.PETAL_DANCE, MoveId.THRASH, MoveId.ICE_BALL, + MoveId.UPROAR, // Multi-turn Moves MoveId.BIDE, MoveId.SHELL_TRAP, @@ -7143,23 +7161,34 @@ export class RepeatMoveAttr extends MoveEffectAttr { MoveId.SOLAR_BEAM, MoveId.SOLAR_BLADE, MoveId.METEOR_BEAM, - // Other moves + // Copying/Move-Calling moves + MoveId.ASSIST, + MoveId.COPYCAT, + MoveId.ME_FIRST, + MoveId.METRONOME, + MoveId.MIRROR_MOVE, + MoveId.NATURE_POWER, + MoveId.SLEEP_TALK, + MoveId.SNATCH, MoveId.INSTRUCT, + // Misc moves MoveId.KINGS_SHIELD, MoveId.SKETCH, MoveId.TRANSFORM, MoveId.MIMIC, MoveId.STRUGGLE, - // TODO: Add Max/G-Move blockage if or when they are implemented + // TODO: Add Max/G-Max/Z-Move blockage if or when they are implemented ]; if (!lastMove?.move // no move to instruct || !movesetMove // called move not in target's moveset (forgetting the move, etc.) || movesetMove.ppUsed === movesetMove.getMovePp() // move out of pp + // TODO: This next line is likely redundant as all charging moves are in the above list || allMoves[lastMove.move].isChargingMove() // called move is a charging/recharging move || uninstructableMoves.includes(lastMove.move)) { // called move is in the banlist return false; } + this.movesetMove = movesetMove; return true; }; } @@ -7190,53 +7219,48 @@ export class ReducePpMoveAttr extends MoveEffectAttr { /** * Reduces the PP of the target's last-used move by an amount based on this attribute instance's {@linkcode reduction}. * - * @param user {@linkcode Pokemon} that used the attack - * @param target {@linkcode Pokemon} targeted by the attack - * @param move N/A - * @param args N/A - * @returns `true` + * @param user - N/A + * @param target - The {@linkcode Pokemon} targeted by the attack + * @param move - N/A + * @param args - N/A + * @returns always `true` */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - // Null checks can be skipped due to condition function - const lastMove = target.getLastXMoves()[0]; - const movesetMove = target.getMoveset().find(m => m.moveId === lastMove.move)!; + /** The last move the target themselves used */ + const lastMove = target.getLastNonVirtualMove(); + const movesetMove = target.getMoveset().find(m => m.moveId === lastMove?.move)!; // bang is correct as condition prevents this from being nullish const lastPpUsed = movesetMove.ppUsed; - movesetMove.ppUsed = Math.min((lastPpUsed) + this.reduction, movesetMove.getMovePp()); + movesetMove.ppUsed = Math.min(lastPpUsed + this.reduction, movesetMove.getMovePp()); - 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.phaseManager.queueMessage(message); + globalScene.phaseManager.queueMessage(i18next.t("battle:ppReduced", { targetName: getPokemonNameWithAffix(target), moveName: movesetMove.getName(), reduction: (movesetMove.ppUsed) - lastPpUsed })); return true; } getCondition(): MoveConditionFunc { return (user, target, move) => { - const lastMove = target.getLastXMoves()[0]; - if (lastMove) { - const movesetMove = target.getMoveset().find(m => m.moveId === lastMove.move); - return !!movesetMove?.getPpRatio(); - } - return false; + const lastMove = target.getLastNonVirtualMove(); + const movesetMove = target.getMoveset().find(m => m.moveId === lastMove?.move) + return !!movesetMove?.getPpRatio(); }; } getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { - const lastMove = target.getLastXMoves()[0]; - if (lastMove) { - const movesetMove = target.getMoveset().find(m => m.moveId === lastMove.move); - if (movesetMove) { - const maxPp = movesetMove.getMovePp(); - const ppLeft = maxPp - movesetMove.ppUsed; - const value = -(8 - Math.ceil(Math.min(maxPp, 30) / 5)); - if (ppLeft < 4) { - return (value / 4) * ppLeft; - } - return value; - } + const lastMove = target.getLastNonVirtualMove(); + const movesetMove = target.getMoveset().find(m => m.moveId === lastMove?.move) + if (!movesetMove) { + return 0; } - return 0; + const maxPp = movesetMove.getMovePp(); + const ppLeft = maxPp - movesetMove.ppUsed; + const value = -(8 - Math.ceil(Math.min(maxPp, 30) / 5)); + if (ppLeft < 4) { + return (value / 4) * ppLeft; + } + return value; + } } @@ -7252,40 +7276,36 @@ export class AttackReducePpMoveAttr extends ReducePpMoveAttr { /** * Checks if the target has used a move prior to the attack. PP-reduction is applied through the super class if so. * - * @param user {@linkcode Pokemon} that used the attack - * @param target {@linkcode Pokemon} targeted by the attack - * @param move {@linkcode Move} being used - * @param args N/A - * @returns {boolean} true + * @param user - The {@linkcode Pokemon} using the move + * @param target -The {@linkcode Pokemon} targeted by the attack + * @param move - The {@linkcode Move} being used + * @param args - N/A + * @returns - always `true` */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const lastMove = target.getLastXMoves().find(() => true); - if (lastMove) { - const movesetMove = target.getMoveset().find(m => m.moveId === lastMove.move); - if (Boolean(movesetMove?.getPpRatio())) { - super.apply(user, target, move, args); - } + const lastMove = target.getLastNonVirtualMove(); + const movesetMove = target.getMoveset().find(m => m.moveId === lastMove?.move); + if (movesetMove?.getPpRatio()) { + super.apply(user, target, move, args); } return true; } - // Override condition function to always perform damage. Instead, perform pp-reduction condition check in apply function above - getCondition(): MoveConditionFunc { - return (user, target, move) => true; + /** + * Override condition function to always perform damage. + * Instead, perform pp-reduction condition check in {@linkcode apply}. + * (A failed condition will prevent damage which is not what we want here) + * @returns always `true` + */ + override getCondition(): MoveConditionFunc { + return () => true; } } -// TODO: Review this const targetMoveCopiableCondition: MoveConditionFunc = (user, target, move) => { - const targetMoves = target.getMoveHistory().filter(m => !m.virtual); - if (!targetMoves.length) { - return false; - } - - const copiableMove = targetMoves[0]; - - if (!copiableMove.move) { + const copiableMove = target.getLastNonVirtualMove(); + if (!copiableMove?.move) { return false; } @@ -7298,14 +7318,18 @@ const targetMoveCopiableCondition: MoveConditionFunc = (user, target, move) => { return true; }; +/** + * Attribute to temporarily copy the last move in the target's moveset. + * Used by {@linkcode Moves.MIMIC}. + */ export class MovesetCopyMoveAttr extends OverrideMoveEffectAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const targetMoves = target.getMoveHistory().filter(m => !m.virtual); - if (!targetMoves.length) { + const lastMove = target.getLastNonVirtualMove() + if (!lastMove?.move) { return false; } - const copiedMove = allMoves[targetMoves[0].move]; + const copiedMove = allMoves[lastMove.move]; const thisMoveIndex = user.getMoveset().findIndex(m => m.moveId === move.id); @@ -7313,8 +7337,9 @@ export class MovesetCopyMoveAttr extends OverrideMoveEffectAttr { return false; } + // Populate summon data with a copy of the current moveset, replacing the copying move with the copied move 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); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:copiedMove", { pokemonName: getPokemonNameWithAffix(user), moveName: copiedMove.name })); @@ -7352,9 +7377,9 @@ export class SketchAttr extends MoveEffectAttr { return false; } - const targetMove = target.getLastXMoves(-1) - .find(m => m.move !== MoveId.NONE && m.move !== MoveId.STRUGGLE && !m.virtual); + const targetMove = target.getLastNonVirtualMove() if (!targetMove) { + // failsafe for TS compiler return false; } @@ -7377,28 +7402,10 @@ export class SketchAttr extends MoveEffectAttr { return false; } - const targetMove = target.getMoveHistory().filter(m => !m.virtual).at(-1); - if (!targetMove) { - return false; - } - - const unsketchableMoves = [ - MoveId.CHATTER, - MoveId.MIRROR_MOVE, - MoveId.SLEEP_TALK, - MoveId.STRUGGLE, - MoveId.SKETCH, - MoveId.REVIVAL_BLESSING, - MoveId.TERA_STARSTORM, - MoveId.BREAKNECK_BLITZ__PHYSICAL, - MoveId.BREAKNECK_BLITZ__SPECIAL - ]; - - if (unsketchableMoves.includes(targetMove.move)) { - return false; - } - - return !user.getMoveset().some(m => m.moveId === targetMove.move); + const targetMove = target.getLastNonVirtualMove(); + return !isNullOrUndefined(targetMove) + && !invalidSketchMoves.has(targetMove.move) + && user.getMoveset().every(m => m.moveId !== targetMove.move) }; } } @@ -7838,19 +7845,19 @@ export class LastResortAttr extends MoveAttr { // TODO: Verify behavior as Bulbapedia page is _extremely_ poorly documented getCondition(): MoveConditionFunc { return (user: Pokemon, _target: Pokemon, move: Move) => { - const movesInMoveset = new Set(user.getMoveset().map(m => m.moveId)); - if (!movesInMoveset.delete(move.id) || !movesInMoveset.size) { + const otherMovesInMoveset = new Set(user.getMoveset().map(m => m.moveId)); + if (!otherMovesInMoveset.delete(move.id) || !otherMovesInMoveset.size) { return false; // Last resort fails if used when not in user's moveset or no other moves exist } - const movesInHistory = new Set( + const movesInHistory = new Set( user.getMoveHistory() - .filter(m => !m.virtual) // TODO: Change to (m) => m < MoveUseType.INDIRECT after Dancer PR refactors virtual into enum + .filter(m => !isVirtual(m.useMode)) // Last resort ignores virtual moves .map(m => m.move) ); - // Since `Set.intersection()` is only present in ESNext, we have to coerce it to an array to check inclusion - return [...movesInMoveset].every(m => movesInHistory.has(m)) + // Since `Set.intersection()` is only present in ESNext, we have to do this to check inclusion + return [...otherMovesInMoveset].every(m => movesInHistory.has(m)) }; } } @@ -7872,27 +7879,26 @@ export class VariableTargetAttr extends MoveAttr { } /** - * Attribute for {@linkcode MoveId.AFTER_YOU} + * Attribute to cause the target to move immediately after the user. * - * [After You - Move | Bulbapedia](https://bulbapedia.bulbagarden.net/wiki/After_You_(move)) + * Used by {@linkcode Moves.AFTER_YOU}. */ export class AfterYouAttr extends MoveEffectAttr { /** - * Allows the target of this move to act right after the user. - * - * @param user {@linkcode Pokemon} that is using the move. - * @param target {@linkcode Pokemon} that will move right after this move is used. - * @param move {@linkcode Move} {@linkcode MoveId.AFTER_YOU} - * @param _args N/A - * @returns true + * Cause the target of this move to act right after the user. + * @param user - Unused + * @param target - The {@linkcode Pokemon} targeted by this move + * @param _move - Unused + * @param _args - Unused + * @returns `true` */ override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { 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. - const nextAttackPhase = globalScene.phaseManager.findPhase((phase) => phase.pokemon === target); - if (nextAttackPhase && globalScene.phaseManager.tryRemovePhase((phase: MovePhase) => phase.pokemon === target)) { - globalScene.phaseManager.prependNewToPhase("MovePhase", "MovePhase", target, [ ...nextAttackPhase.targets ], nextAttackPhase.move); + // Will find next acting phase of the targeted pokémon, delete it and queue it right after us. + const targetNextPhase = globalScene.phaseManager.findPhase(phase => phase.pokemon === target); + if (targetNextPhase && globalScene.phaseManager.tryRemovePhase((phase: MovePhase) => phase.pokemon === target)) { + globalScene.phaseManager.prependToPhase(targetNextPhase, "MovePhase"); } return true; @@ -7917,6 +7923,7 @@ export class ForceLastAttr extends MoveEffectAttr { override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:forceLast", { targetPokemonName: getPokemonNameWithAffix(target) })); + // TODO: Refactor this to be more readable and less janky const targetMovePhase = globalScene.phaseManager.findPhase((phase) => 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 - @@ -7929,7 +7936,7 @@ export class ForceLastAttr extends MoveEffectAttr { globalScene.phaseManager.phaseQueue.splice( globalScene.phaseManager.phaseQueue.indexOf(prependPhase), 0, - globalScene.phaseManager.create("MovePhase", target, [ ...targetMovePhase.targets ], targetMovePhase.move, false, false, false, true) + globalScene.phaseManager.create("MovePhase", target, [ ...targetMovePhase.targets ], targetMovePhase.move, targetMovePhase.useMode, true) ); } } @@ -7937,7 +7944,13 @@ export class ForceLastAttr extends MoveEffectAttr { } } -/** Returns whether a {@linkcode MovePhase} has been forced last and the corresponding pokemon is slower than {@linkcode target} */ +/** + * Returns whether a {@linkcode MovePhase} has been forced last and the corresponding pokemon is slower than {@linkcode target}. + + * TODO: + - Make this a class method + - Make this look at speed order from TurnStartPhase +*/ const phaseForcedSlower = (phase: MovePhase, target: Pokemon, trickRoom: boolean): boolean => { let slower: boolean; // quashed pokemon still have speed ties @@ -8032,8 +8045,7 @@ export class UpperHandCondition extends MoveCondition { super((user, target, move) => { const targetCommand = globalScene.currentBattle.turnCommands[target.getBattlerIndex()]; - return !!targetCommand - && targetCommand.command === Command.FIGHT + return targetCommand?.command === Command.FIGHT && !target.turnData.acted && !!targetCommand.move?.move && allMoves[targetCommand.move.move].category !== MoveCategory.STATUS @@ -8066,6 +8078,7 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr { constructor() { super(true); } + /** * User changes its type to a random type that resists the target's last used move * @param {Pokemon} user Pokemon that used the move and will change types @@ -8079,7 +8092,8 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr { return false; } - const [ targetMove ] = target.getLastXMoves(1); // target's most recent move + // TODO: Confirm how this interacts with status-induced failures and called moves + const targetMove = target.getLastXMoves(1)[0]; // target's most recent move if (!targetMove) { return false; } @@ -8121,9 +8135,9 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr { } getCondition(): MoveConditionFunc { + // TODO: Does this count dancer? return (user, target, move) => { - const moveHistory = target.getLastXMoves(); - return moveHistory.length !== 0; + return target.getLastXMoves(-1).some(tm => tm.move !== MoveId.NONE); }; } } @@ -8541,9 +8555,9 @@ export function initMoves() { .attr(FixedDamageAttr, 20), new StatusMove(MoveId.DISABLE, PokemonType.NORMAL, 100, 20, -1, 0, 1) .attr(AddBattlerTagAttr, BattlerTagType.DISABLED, false, true) - .condition((user, target, move) => { - const lastRealMove = target.getLastXMoves(-1).find(m => !m.virtual); - return !isNullOrUndefined(lastRealMove) && lastRealMove.move !== MoveId.NONE && lastRealMove.move !== MoveId.STRUGGLE; + .condition((_user, target, _move) => { + const lastNonVirtualMove = target.getLastNonVirtualMove(); + return !isNullOrUndefined(lastNonVirtualMove) && lastNonVirtualMove.move !== MoveId.STRUGGLE; }) .ignoresSubstitute() .reflectable(), @@ -9092,7 +9106,10 @@ export function initMoves() { .attr(AddBattlerTagAttr, BattlerTagType.ENCORE, false, true) .ignoresSubstitute() .condition((user, target, move) => new EncoreTag(user.id).canAdd(target)) - .reflectable(), + .reflectable() + // Can lock infinitely into struggle; has incorrect interactions with Blood Moon/Gigaton Hammer + // Also may or may not incorrectly select targets for replacement move (needs verification) + .edgeCase(), new AttackMove(MoveId.PURSUIT, PokemonType.DARK, MoveCategory.PHYSICAL, 40, 100, 20, -1, 0, 2) .partial(), // No effect implemented new AttackMove(MoveId.RAPID_SPIN, PokemonType.NORMAL, MoveCategory.PHYSICAL, 50, 100, 40, 100, 0, 2) @@ -9974,7 +9991,14 @@ export function initMoves() { .chargeAttr(SemiInvulnerableAttr, BattlerTagType.FLYING) .condition(failOnGravityCondition) .condition((user, target, move) => !target.getTag(BattlerTagType.SUBSTITUTE)) - .partial(), // Should immobilize the target, Flying types should take no damage. cf https://bulbapedia.bulbagarden.net/wiki/Sky_Drop_(move) and https://www.smogon.com/dex/sv/moves/sky-drop/ + /* + * Cf https://bulbapedia.bulbagarden.net/wiki/Sky_Drop_(move) and https://www.smogon.com/dex/sv/moves/sky-drop/: + * Should immobilize and give target semi-invulnerability + * Flying types should take no damage + * Should fail on targets above a certain weight threshold + * Should remove all redirection effects on successful takeoff (Rage Poweder, etc.) + */ + .partial(), new SelfStatusMove(MoveId.SHIFT_GEAR, PokemonType.STEEL, -1, 10, -1, 0, 5) .attr(StatStageChangeAttr, [ Stat.ATK ], 1, true) .attr(StatStageChangeAttr, [ Stat.SPD ], 2, true), @@ -10545,9 +10569,12 @@ export function initMoves() { new StatusMove(MoveId.INSTRUCT, PokemonType.PSYCHIC, -1, 15, -1, 0, 7) .ignoresSubstitute() .attr(RepeatMoveAttr) - // incorrect interactions with Gigaton Hammer, Blood Moon & Torment - // Also has incorrect interactions with Dancer due to the latter - // erroneously adding copied moves to move history. + /* + * Incorrect interactions with Gigaton Hammer, Blood Moon & Torment due to them _failing on use_, not merely being unselectable. + * Incorrectly ticks down Encore's fail counter + * TODO: Verify whether Instruct can repeat Struggle + * TODO: Verify whether Instruct can fail when using a copied move also in one's own moveset + */ .edgeCase(), new AttackMove(MoveId.BEAK_BLAST, PokemonType.FLYING, MoveCategory.PHYSICAL, 100, 100, 15, -1, -3, 7) .attr(BeakBlastHeaderAttr) @@ -10605,7 +10632,13 @@ export function initMoves() { .bitingMove() .attr(RemoveScreensAttr), new AttackMove(MoveId.STOMPING_TANTRUM, PokemonType.GROUND, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 7) - .attr(MovePowerMultiplierAttr, (user, target, move) => user.getLastXMoves(2)[1]?.result === MoveResult.MISS || user.getLastXMoves(2)[1]?.result === MoveResult.FAIL ? 2 : 1), + .attr(MovePowerMultiplierAttr, (user) => { + // Stomping tantrum triggers on most failures (including sleep/freeze) + const lastNonDancerMove = user.getLastXMoves(2)[1] as TurnMove | undefined; + return lastNonDancerMove && (lastNonDancerMove.result === MoveResult.MISS || lastNonDancerMove.result === MoveResult.FAIL) ? 2 : 1 + }) + // TODO: Review mainline accuracy and draft tests as needed + .edgeCase(), new AttackMove(MoveId.SHADOW_BONE, PokemonType.GHOST, MoveCategory.PHYSICAL, 85, 100, 10, 20, 0, 7) .attr(StatStageChangeAttr, [ Stat.DEF ], -1) .makesContact(false), diff --git a/src/data/moves/pokemon-move.ts b/src/data/moves/pokemon-move.ts index da46caa819f..daad199fbbd 100644 --- a/src/data/moves/pokemon-move.ts +++ b/src/data/moves/pokemon-move.ts @@ -21,7 +21,6 @@ export class PokemonMove { public moveId: MoveId; public ppUsed: number; public ppUp: number; - public virtual: boolean; /** * If defined and nonzero, overrides the maximum PP of the move (e.g., due to move being copied by Transform). @@ -29,11 +28,10 @@ export class PokemonMove { */ public maxPpOverride?: number; - constructor(moveId: MoveId, ppUsed = 0, ppUp = 0, virtual = false, maxPpOverride?: number) { + constructor(moveId: MoveId, ppUsed = 0, ppUp = 0, maxPpOverride?: number) { this.moveId = moveId; this.ppUsed = ppUsed; this.ppUp = ppUp; - this.virtual = virtual; this.maxPpOverride = maxPpOverride; } @@ -47,6 +45,7 @@ export class PokemonMove { * @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`. */ isUsable(pokemon: Pokemon, ignorePp = false, ignoreRestrictionTags = false): boolean { + // TODO: Add Sky Drop's 1 turn stall if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon)) { return false; } @@ -88,6 +87,6 @@ export class PokemonMove { * @returns A valid {@linkcode PokemonMove} object */ static loadMove(source: PokemonMove | any): PokemonMove { - return new PokemonMove(source.moveId, source.ppUsed, source.ppUp, source.virtual, source.maxPpOverride); + return new PokemonMove(source.moveId, source.ppUsed, source.ppUp, source.maxPpOverride); } } diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index 3eda96e4028..e3741226335 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -38,6 +38,7 @@ import type HeldModifierConfig from "#app/@types/held-modifier-config"; import type { BerryType } from "#enums/berry-type"; import { Stat } from "#enums/stat"; import i18next from "i18next"; +import { MoveUseMode } from "#enums/move-use-mode"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/absoluteAvarice"; @@ -307,7 +308,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde sourceBattlerIndex: BattlerIndex.ENEMY, targets: [BattlerIndex.ENEMY], move: new PokemonMove(MoveId.STUFF_CHEEKS), - ignorePp: true, + useMode: MoveUseMode.IGNORE_PP, }); await transitionMysteryEncounterIntroVisuals(true, true, 500); diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index 542bd163713..b873e30fe0c 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -13,7 +13,6 @@ import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTem import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { ModifierTier } from "#enums/modifier-tier"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/data/data-lists"; import { ModifierPoolType } from "#enums/modifier-pool-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; @@ -49,7 +48,8 @@ import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { EncounterAnim } from "#enums/encounter-anims"; import { Challenges } from "#enums/challenges"; -import { allAbilities } from "#app/data/data-lists"; +import { MoveUseMode } from "#enums/move-use-mode"; +import { allAbilities, modifierTypes } from "#app/data/data-lists"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/clowningAround"; @@ -210,19 +210,19 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder sourceBattlerIndex: BattlerIndex.ENEMY, targets: [BattlerIndex.ENEMY_2], move: new PokemonMove(MoveId.ROLE_PLAY), - ignorePp: true, + useMode: MoveUseMode.IGNORE_PP, }, { sourceBattlerIndex: BattlerIndex.ENEMY_2, targets: [BattlerIndex.PLAYER], move: new PokemonMove(MoveId.TAUNT), - ignorePp: true, + useMode: MoveUseMode.IGNORE_PP, }, { sourceBattlerIndex: BattlerIndex.ENEMY_2, targets: [BattlerIndex.PLAYER_2], move: new PokemonMove(MoveId.TAUNT), - ignorePp: true, + useMode: MoveUseMode.IGNORE_PP, }, ); diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index 4444a2e6b1b..42af5339a80 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -40,6 +40,7 @@ import { PokeballType } from "#enums/pokeball"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; import i18next from "i18next"; +import { MoveUseMode } from "#enums/move-use-mode"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/dancingLessons"; @@ -214,7 +215,7 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder sourceBattlerIndex: BattlerIndex.ENEMY, targets: [BattlerIndex.PLAYER], move: new PokemonMove(MoveId.REVELATION_DANCE), - ignorePp: true, + useMode: MoveUseMode.IGNORE_PP, }); await hideOricorioPokemon(); diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 4b24bf9cada..94dc4ab6153 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -10,7 +10,6 @@ import { generateModifierType, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/data/data-lists"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; @@ -46,7 +45,8 @@ import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Stat } from "#enums/stat"; import { FIRE_RESISTANT_ABILITIES } from "#app/data/mystery-encounters/requirements/requirement-groups"; -import { allAbilities } from "#app/data/data-lists"; +import { MoveUseMode } from "#enums/move-use-mode"; +import { allAbilities, modifierTypes } from "#app/data/data-lists"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/fieryFallout"; @@ -201,13 +201,13 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w sourceBattlerIndex: BattlerIndex.ENEMY, targets: [BattlerIndex.PLAYER], move: new PokemonMove(MoveId.FIRE_SPIN), - ignorePp: true, + useMode: MoveUseMode.IGNORE_PP, }, { sourceBattlerIndex: BattlerIndex.ENEMY_2, targets: [BattlerIndex.PLAYER_2], move: new PokemonMove(MoveId.FIRE_SPIN), - ignorePp: true, + useMode: MoveUseMode.IGNORE_PP, }, ); await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]); diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index 483c577e851..6b6cd71af1f 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -31,6 +31,7 @@ import { BerryType } from "#enums/berry-type"; import { Stat } from "#enums/stat"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { randSeedInt } from "#app/utils/common"; +import { MoveUseMode } from "#enums/move-use-mode"; /** i18n namespace for the encounter */ const namespace = "mysteryEncounters/slumberingSnorlax"; @@ -137,7 +138,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil sourceBattlerIndex: BattlerIndex.ENEMY, targets: [BattlerIndex.PLAYER], move: new PokemonMove(MoveId.SNORE), - ignorePp: true, + useMode: MoveUseMode.IGNORE_PP, }); await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]); }, diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index 61d9dcfccfd..adf4f9dde8f 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -28,6 +28,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; +import { MoveUseMode } from "#enums/move-use-mode"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/theStrongStuff"; @@ -214,13 +215,13 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder sourceBattlerIndex: BattlerIndex.ENEMY, targets: [BattlerIndex.PLAYER], move: new PokemonMove(MoveId.GASTRO_ACID), - ignorePp: true, + useMode: MoveUseMode.IGNORE_PP, }, { sourceBattlerIndex: BattlerIndex.ENEMY, targets: [BattlerIndex.PLAYER], move: new PokemonMove(MoveId.STEALTH_ROCK), - ignorePp: true, + useMode: MoveUseMode.IGNORE_PP, }, ); diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index 1416c63dd28..62413b96523 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -28,6 +28,7 @@ import { BattlerIndex } from "#enums/battler-index"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { randSeedInt } from "#app/utils/common"; +import { MoveUseMode } from "#enums/move-use-mode"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/trashToTreasure"; @@ -207,13 +208,13 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde sourceBattlerIndex: BattlerIndex.ENEMY, targets: [BattlerIndex.PLAYER], move: new PokemonMove(MoveId.TOXIC), - ignorePp: true, + useMode: MoveUseMode.IGNORE_PP, }, { sourceBattlerIndex: BattlerIndex.ENEMY, targets: [BattlerIndex.ENEMY], move: new PokemonMove(MoveId.STOCKPILE), - ignorePp: true, + useMode: MoveUseMode.IGNORE_PP, }, ); await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]); diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index c48f93a9a9d..44e578540dd 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -36,6 +36,7 @@ import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encoun import { BerryModifier } from "#app/modifier/modifier"; import { Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; +import { MoveUseMode } from "#enums/move-use-mode"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/uncommonBreed"; @@ -180,7 +181,7 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder. sourceBattlerIndex: BattlerIndex.ENEMY, targets: [target], move: pokemonMove, - ignorePp: true, + useMode: MoveUseMode.IGNORE_PP, }); } diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index bd3082afe19..fa97a7f4d40 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -29,14 +29,14 @@ import type { GameModes } from "#enums/game-modes"; import type { EncounterAnim } from "#enums/encounter-anims"; import type { Challenges } from "#enums/challenges"; import { globalScene } from "#app/global-scene"; +import type { MoveUseMode } from "#enums/move-use-mode"; export interface EncounterStartOfBattleEffect { sourcePokemon?: Pokemon; sourceBattlerIndex?: BattlerIndex; targets: BattlerIndex[]; move: PokemonMove; - ignorePp: boolean; - followUp?: boolean; + useMode: MoveUseMode; // TODO: This should always be ignore PP... } const DEFAULT_MAX_ALLOWED_ENCOUNTERS = 2; @@ -254,7 +254,7 @@ export default class MysteryEncounter implements IMysteryEncounter { */ selectedOption?: MysteryEncounterOption; /** - * Will be set by option select handlers automatically, and can be used to refer to which option was chosen by later phases + * Array containing data pertaining to free moves used at the start of a battle mystery envounter. */ startOfBattleEffects: EncounterStartOfBattleEffect[] = []; /** diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index e2b92230985..eaa4f08ef70 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -1,5 +1,4 @@ import type Battle from "#app/battle"; -import { BattlerIndex } from "#enums/battler-index"; import { BattleType } from "#enums/battle-type"; import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes"; import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; @@ -974,33 +973,8 @@ export function handleMysteryEncounterBattleStartEffects() { ) { const effects = encounter.startOfBattleEffects; effects.forEach(effect => { - let source: EnemyPokemon | Pokemon; - if (effect.sourcePokemon) { - source = effect.sourcePokemon; - } else if (!isNullOrUndefined(effect.sourceBattlerIndex)) { - if (effect.sourceBattlerIndex === BattlerIndex.ATTACKER) { - source = globalScene.getEnemyField()[0]; - } else if (effect.sourceBattlerIndex === BattlerIndex.ENEMY) { - source = globalScene.getEnemyField()[0]; - } else if (effect.sourceBattlerIndex === BattlerIndex.ENEMY_2) { - source = globalScene.getEnemyField()[1]; - } else if (effect.sourceBattlerIndex === BattlerIndex.PLAYER) { - source = globalScene.getPlayerField()[0]; - } else if (effect.sourceBattlerIndex === BattlerIndex.PLAYER_2) { - source = globalScene.getPlayerField()[1]; - } - } else { - source = globalScene.getEnemyField()[0]; - } - globalScene.phaseManager.pushNew( - "MovePhase", - // @ts-expect-error: source is guaranteed to be defined - source, - effect.targets, - effect.move, - effect.followUp, - effect.ignorePp, - ); + const source = effect.sourcePokemon ?? globalScene.getField()[effect.sourceBattlerIndex ?? 0]; + globalScene.phaseManager.pushNew("MovePhase", source, effect.targets, effect.move, effect.useMode); }); // Pseudo turn end phase to reset flinch states, Endure, etc. diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 56dc649afac..bb6ede7731d 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -84,13 +84,14 @@ export const normalForm: SpeciesId[] = [ /** * Gets the {@linkcode PokemonSpecies} object associated with the {@linkcode SpeciesId} enum given - * @param species The species to fetch + * @param species - The {@linkcode SpeciesId} to fetch. + * If an array of `SpeciesId`s is passed (such as for named trainer spawn pools), + * one will be selected at random. * @returns The associated {@linkcode PokemonSpecies} object */ export function getPokemonSpecies(species: SpeciesId | SpeciesId[]): PokemonSpecies { - // If a special pool (named trainers) is used here it CAN happen that they have a array as species (which means choose one of those two). So we catch that with this code block if (Array.isArray(species)) { - // Pick a random species from the list + // TODO: this RNG roll should not be handled by this function species = species[Math.floor(Math.random() * species.length)]; } if (species >= 2000) { diff --git a/src/enums/battler-tag-lapse-type.ts b/src/enums/battler-tag-lapse-type.ts index 355a084148b..4375e87e4e0 100644 --- a/src/enums/battler-tag-lapse-type.ts +++ b/src/enums/battler-tag-lapse-type.ts @@ -1,12 +1,37 @@ +/** + * Enum representing the possible ways a given BattlerTag can activate and/or tick down. + * Each tag can have multiple different behaviors attached to different lapse types. + */ export enum BattlerTagLapseType { + // TODO: This is unused... FAINT, + /** + * Tag activate before the holder uses a non-virtual move, possibly interrupting its action. + * @see MoveUseMode for more information + */ MOVE, + /** Tag activates before the holder uses **any** move, triggering effects or interrupting its action. */ PRE_MOVE, + /** Tag activates immediately after the holder's move finishes triggering (successful or not). */ AFTER_MOVE, + /** + * Tag activates before move effects are applied. + * TODO: Stop using this as a catch-all "semi-invulnerability" tag + */ MOVE_EFFECT, + /** Tag activates at the end of the turn. */ TURN_END, + /** + * Tag activates after the holder is hit by an attack, but before damage is applied. + * Occurs even if the user's {@linkcode SubstituteTag | Substitute} is hit. + */ HIT, - /** Tag lapses AFTER_HIT, applying its effects even if the user faints */ + /** + * Tag activates after the holder is directly hit by an attack. + * Does **not** occur on hits to the holder's {@linkcode SubstituteTag | Substitute}, + * but still triggers on being KO'd. + */ AFTER_HIT, - CUSTOM + /** The tag has some other custom activation or removal condition. */ + CUSTOM, } diff --git a/src/enums/move-use-mode.ts b/src/enums/move-use-mode.ts new file mode 100644 index 00000000000..31694ad4081 --- /dev/null +++ b/src/enums/move-use-mode.ts @@ -0,0 +1,149 @@ +import type { PostDancingMoveAbAttr } from "#app/data/abilities/ability"; +import type { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; + +/** + * Enum representing all the possible means through which a given move can be executed. + * Each one inherits the properties (or exclusions) of all types preceding it. + * Properties newly found on a given use mode will be **bolded**, + * while oddities breaking a previous trend will be listed in _italics_. + + * Callers should refrain from performing non-equality checks on `MoveUseMode`s directly, + * instead using the available helper functions + * ({@linkcode isVirtual}, {@linkcode isIgnoreStatus}, {@linkcode isIgnorePP} and {@linkcode isReflected}). + */ +export const MoveUseMode = { + /** + * This move was used normally (i.e. clicking on the button) or called via Instruct. + * It deducts PP from the user's moveset (failing if out of PP), and interacts normally with other moves and abilities. + */ + NORMAL: 1, + + /** + * This move was called by an effect that ignores PP, such as a consecutively executed move (e.g. Outrage). + * + * PP-ignoring moves (as their name implies) **do not consume PP** when used + * and **will not fail** if none is left prior to execution. + * All other effects remain identical to {@linkcode MoveUseMode.NORMAL}. + * + * PP can still be reduced by other effects (such as Spite or Eerie Spell). + */ + IGNORE_PP: 2, + + /** + * This move was called indirectly by an out-of-turn effect other than Instruct or the user's previous move. + * Currently only used by {@linkcode PostDancingMoveAbAttr | Dancer}. + * + * Indirect moves ignore PP checks similar to {@linkcode MoveUseMode.IGNORE_PP}, but additionally **cannot be copied** + * by all move-copying effects (barring reflection). + * They are also **"skipped over" by most moveset and move history-related effects** (PP reduction, Last Resort, etc). + * + * They still respect the user's volatile status conditions and confusion (though will uniquely _cure freeze and sleep before use_). + */ + INDIRECT: 3, + + /** + * This move was called as part of another move's effect (such as for most {@link https://bulbapedia.bulbagarden.net/wiki/Category:Moves_that_call_other_moves | Move-calling moves}). + + * Follow-up moves **bypass cancellation** from all **non-volatile status conditions** and **{@linkcode BattlerTagLapseType.MOVE}-type effects** + * (having been checked already on the calling move). + + * They are _not ignored_ by other move-calling moves and abilities (unlike {@linkcode MoveUseMode.FOLLOW_UP} and {@linkcode MoveUseMode.REFLECTED}), + * but still inherit the former's disregard for moveset-related effects. + */ + FOLLOW_UP: 4, + + /** + * This move was reflected by Magic Coat or Magic Bounce. + + * Reflected moves ignore all the same cancellation checks as {@linkcode MoveUseMode.INDIRECT} + * and retain the same copy prevention as {@linkcode MoveUseMode.FOLLOW_UP}, but additionally + * **cannot be reflected by other reflecting effects**. + */ + REFLECTED: 5 + // TODO: Add use type TRANSPARENT for Future Sight and Doom Desire to prevent move history pushing +} as const; + +export type MoveUseMode = (typeof MoveUseMode)[keyof typeof MoveUseMode]; + +// # HELPER FUNCTIONS +// Please update the markdown tables if any new `MoveUseMode`s get added. + +/** + * Check if a given {@linkcode MoveUseMode} is virtual (i.e. called by another move or effect). + * Virtual moves are ignored by most moveset-related effects due to not being executed directly. + * @returns Whether {@linkcode useMode} is virtual. + * @remarks + * This function is equivalent to the following truth table: + * + * | Use Type | Returns | + * |------------------------------------|---------| + * | {@linkcode MoveUseMode.NORMAL} | `false` | + * | {@linkcode MoveUseMode.IGNORE_PP} | `false` | + * | {@linkcode MoveUseMode.INDIRECT} | `true` | + * | {@linkcode MoveUseMode.FOLLOW_UP} | `true` | + * | {@linkcode MoveUseMode.REFLECTED} | `true` | + */ +export function isVirtual(useMode: MoveUseMode): boolean { + return useMode >= MoveUseMode.INDIRECT +} + +/** + * Check if a given {@linkcode MoveUseMode} should ignore pre-move cancellation checks + * from {@linkcode StatusEffect.PARALYSIS} and {@linkcode BattlerTagLapseType.MOVE}-type effects. + * @param useMode - The {@linkcode MoveUseMode} to check. + * @returns Whether {@linkcode useMode} should ignore status and otehr cancellation checks. + * @remarks + * This function is equivalent to the following truth table: + * + * | Use Type | Returns | + * |------------------------------------|---------| + * | {@linkcode MoveUseMode.NORMAL} | `false` | + * | {@linkcode MoveUseMode.IGNORE_PP} | `false` | + * | {@linkcode MoveUseMode.INDIRECT} | `false` | + * | {@linkcode MoveUseMode.FOLLOW_UP} | `true` | + * | {@linkcode MoveUseMode.REFLECTED} | `true` | + */ +export function isIgnoreStatus(useMode: MoveUseMode): boolean { + return useMode >= MoveUseMode.FOLLOW_UP; +} + +/** + * Check if a given {@linkcode MoveUseMode} should ignore PP. + * PP-ignoring moves will ignore normal PP consumption as well as associated failure checks. + * @param useMode - The {@linkcode MoveUseMode} to check. + * @returns Whether {@linkcode useMode} ignores PP. + * @remarks + * This function is equivalent to the following truth table: + * + * | Use Type | Returns | + * |------------------------------------|---------| + * | {@linkcode MoveUseMode.NORMAL} | `false` | + * | {@linkcode MoveUseMode.IGNORE_PP} | `true` | + * | {@linkcode MoveUseMode.INDIRECT} | `true` | + * | {@linkcode MoveUseMode.FOLLOW_UP} | `true` | + * | {@linkcode MoveUseMode.REFLECTED} | `true` | + */ +export function isIgnorePP(useMode: MoveUseMode): boolean { + return useMode >= MoveUseMode.IGNORE_PP; +} + +/** + * Check if a given {@linkcode MoveUseMode} is reflected. + * Reflected moves cannot be reflected, copied, or cancelled by status effects, + * nor will they trigger {@linkcode PostDancingMoveAbAttr | Dancer}. + * @param useMode - The {@linkcode MoveUseMode} to check. + * @returns Whether {@linkcode useMode} is reflected. + * @remarks + * This function is equivalent to the following truth table: + * + * | Use Type | Returns | + * |------------------------------------|---------| + * | {@linkcode MoveUseMode.NORMAL} | `false` | + * | {@linkcode MoveUseMode.IGNORE_PP} | `false` | + * | {@linkcode MoveUseMode.INDIRECT} | `false` | + * | {@linkcode MoveUseMode.FOLLOW_UP} | `false` | + * | {@linkcode MoveUseMode.REFLECTED} | `true` | + */ +export function isReflected(useMode: MoveUseMode): boolean { + return useMode === MoveUseMode.REFLECTED; +} diff --git a/src/field/arena.ts b/src/field/arena.ts index 22cb9e32863..aece908d653 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -765,6 +765,9 @@ export class Arena { ); } + // TODO: Add an overload similar to `Array.prototype.find` if the predicate func is of the form + // `(x): x is T` + /** * Uses {@linkcode findTagsOnSide} to filter (using the parameter function) for specific tags that apply to both sides * @param tagPredicate a function mapping {@linkcode ArenaTag}s to `boolean`s diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 0205b5bf4b2..730d2ab2c2a 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -38,7 +38,6 @@ import { deltaRgb, isBetween, randSeedFloat, - type nil, type Constructor, randSeedIntRange, coerceArray, @@ -186,6 +185,7 @@ import { doShinySparkleAnim } from "#app/field/anims"; import { MoveFlags } from "#enums/MoveFlags"; import { timedEventManager } from "#app/global-event-manager"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; +import { isVirtual, isIgnorePP, MoveUseMode } from "#enums/move-use-mode"; import { FieldPosition } from "#enums/field-position"; import { LearnMoveSituation } from "#enums/learn-move-situation"; import { HitResult } from "#enums/hit-result"; @@ -324,7 +324,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { super(globalScene, x, y); if (!species.isObtainable() && this.isPlayer()) { - throw `Cannot create a player Pokemon for species '${species.getName(formIndex)}'`; + throw `Cannot create a player Pokemon for species "${species.getName(formIndex)}"`; } this.species = species; @@ -3137,7 +3137,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { while (rand > stabMovePool[index][1]) { rand -= stabMovePool[index++][1]; } - this.moveset.push(new PokemonMove(stabMovePool[index][0], 0, 0)); + this.moveset.push(new PokemonMove(stabMovePool[index][0])); } while (baseWeights.length > this.moveset.length && this.moveset.length < 4) { @@ -3190,7 +3190,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { while (rand > movePool[index][1]) { rand -= movePool[index++][1]; } - this.moveset.push(new PokemonMove(movePool[index][0], 0, 0)); + this.moveset.push(new PokemonMove(movePool[index][0])); } // Trigger FormChange, except for enemy Pokemon during Mystery Encounters, to avoid crashes @@ -4109,7 +4109,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /**@overload */ - getTag(tagType: BattlerTagType.GRUDGE): GrudgeTag | nil; + getTag(tagType: BattlerTagType.GRUDGE): GrudgeTag | undefined; /** @overload */ getTag(tagType: BattlerTagType.SUBSTITUTE): SubstituteTag | undefined; @@ -4326,10 +4326,41 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return moveHistory.slice(0).reverse(); } + /** + * Return the most recently executed {@linkcode TurnMove} this {@linkcode Pokemon} has used that is: + * - Not {@linkcode MoveId.NONE} + * - Non-virtual ({@linkcode MoveUseMode | useMode} < {@linkcode MoveUseMode.INDIRECT}) + * @param ignoreStruggle - Whether to additionally ignore {@linkcode Moves.STRUGGLE}; default `false` + * @param ignoreFollowUp - Whether to ignore moves with a use type of {@linkcode MoveUseMode.FOLLOW_UP} + * (e.g. ones called by Copycat/Mirror Move); default `true`. + * @returns The last move this Pokemon has used satisfying the aforementioned conditions, + * or `undefined` if no applicable moves have been used since switching in. + */ + getLastNonVirtualMove(ignoreStruggle = false, ignoreFollowUp = true): TurnMove | undefined { + return this.getLastXMoves(-1).find( + m => + m.move !== MoveId.NONE && + (!ignoreStruggle || m.move !== MoveId.STRUGGLE) && + (!isVirtual(m.useMode) || (!ignoreFollowUp && m.useMode === MoveUseMode.FOLLOW_UP)), + ); + } + + /** + * Return this Pokemon's move queue, consisting of all the moves it is slated to perform. + * @returns An array of {@linkcode TurnMove}, as described above + */ getMoveQueue(): TurnMove[] { return this.summonData.moveQueue; } + /** + * Add a new entry to the end of this Pokemon's move queue. + * @param queuedMove - A {@linkcode TurnMove} to push to this Pokemon's queue. + */ + pushMoveQueue(queuedMove: TurnMove): void { + this.summonData.moveQueue.push(queuedMove); + } + changeForm(formChange: SpeciesFormChange): Promise { return new Promise(resolve => { this.formIndex = Math.max( @@ -5987,7 +6018,7 @@ export class PlayerPokemon extends Pokemon { copyMoveset(): PokemonMove[] { const newMoveset: PokemonMove[] = []; this.moveset.forEach(move => { - newMoveset.push(new PokemonMove(move.moveId, 0, move.ppUp, move.virtual, move.maxPpOverride)); + newMoveset.push(new PokemonMove(move.moveId, 0, move.ppUp, move.maxPpOverride)); }); return newMoveset; @@ -6167,33 +6198,39 @@ export class EnemyPokemon extends Pokemon { * the Pokemon the move will target. * @returns this Pokemon's next move in the format {move, moveTargets} */ + // TODO: split this up and move it elsewhere getNextMove(): TurnMove { - // If this Pokemon has a move already queued, return it. + // If this Pokemon has a usable move already queued, return it, + // removing all unusable moves before it in the queue. const moveQueue = this.getMoveQueue(); - if (moveQueue.length !== 0) { - const queuedMove = moveQueue[0]; - if (queuedMove) { - const moveIndex = this.getMoveset().findIndex(m => m.moveId === queuedMove.move); - if ( - (moveIndex > -1 && this.getMoveset()[moveIndex].isUsable(this, queuedMove.ignorePP)) || - queuedMove.virtual - ) { - return queuedMove; - } - this.getMoveQueue().shift(); - return this.getNextMove(); + for (const [i, queuedMove] of moveQueue.entries()) { + const movesetMove = this.getMoveset().find(m => m.moveId === queuedMove.move); + // If the queued move was called indirectly, ignore all PP and usability checks. + // Otherwise, ensure that the move being used is actually usable & in our moveset. + // TODO: What should happen if a pokemon forgets a charging move mid-use? + if (isVirtual(queuedMove.useMode) || movesetMove?.isUsable(this, isIgnorePP(queuedMove.useMode))) { + moveQueue.splice(0, i); // TODO: This should not be done here + return queuedMove; } } + // We went through the entire queue without a match; clear the entire thing. + this.summonData.moveQueue = []; + // Filter out any moves this Pokemon cannot use let movePool = this.getMoveset().filter(m => m.isUsable(this)); // If no moves are left, use Struggle. Otherwise, continue with move selection if (movePool.length) { // If there's only 1 move in the move pool, use it. if (movePool.length === 1) { - return { move: movePool[0].moveId, targets: this.getNextTargets(movePool[0].moveId) }; + return { + move: movePool[0].moveId, + targets: this.getNextTargets(movePool[0].moveId), + useMode: MoveUseMode.NORMAL, + }; } // If a move is forced because of Encore, use it. + // Said moves are executed normally const encoreTag = this.getTag(EncoreTag) as EncoreTag; if (encoreTag) { const encoreMove = movePool.find(m => m.moveId === encoreTag.moveId); @@ -6201,6 +6238,7 @@ export class EnemyPokemon extends Pokemon { return { move: encoreMove.moveId, targets: this.getNextTargets(encoreMove.moveId), + useMode: MoveUseMode.NORMAL, }; } } @@ -6208,7 +6246,7 @@ export class EnemyPokemon extends Pokemon { // No enemy should spawn with this AI type in-game case AiType.RANDOM: { const moveId = movePool[globalScene.randBattleSeedInt(movePool.length)].moveId; - return { move: moveId, targets: this.getNextTargets(moveId) }; + return { move: moveId, targets: this.getNextTargets(moveId), useMode: MoveUseMode.NORMAL }; } case AiType.SMART_RANDOM: case AiType.SMART: { @@ -6377,14 +6415,20 @@ export class EnemyPokemon extends Pokemon { r, sortedMovePool.map(m => m.getName()), ); - return { move: sortedMovePool[r]!.moveId, targets: moveTargets[sortedMovePool[r]!.moveId] }; + return { + move: sortedMovePool[r]!.moveId, + targets: moveTargets[sortedMovePool[r]!.moveId], + useMode: MoveUseMode.NORMAL, + }; } } } + // No moves left means struggle return { move: MoveId.STRUGGLE, targets: this.getNextTargets(MoveId.STRUGGLE), + useMode: MoveUseMode.IGNORE_PP, }; } @@ -6730,10 +6774,9 @@ interface IllusionData { export interface TurnMove { move: MoveId; targets: BattlerIndex[]; + useMode: MoveUseMode; result?: MoveResult; - virtual?: boolean; turn?: number; - ignorePP?: boolean; } export interface AttackMoveResult { @@ -6752,6 +6795,12 @@ export interface AttackMoveResult { export class PokemonSummonData { /** [Atk, Def, SpAtk, SpDef, Spd, Acc, Eva] */ public statStages: number[] = [0, 0, 0, 0, 0, 0, 0]; + /** + * A queue of moves yet to be executed, used by charging, recharging and frenzy moves. + * So long as this array is nonempty, this Pokemon's corresponding `CommandPhase` will be skipped over entirely + * in favor of using the queued move. + * TODO: Clean up a lot of the code surrounding the move queue. + */ public moveQueue: TurnMove[] = []; public tags: BattlerTag[] = []; public abilitySuppressed = false; @@ -6871,7 +6920,6 @@ export class PokemonWaveData { * Resets at the start of a new turn, as well as on switch. */ export class PokemonTurnData { - public flinched = false; public acted = false; /** How many times the current move should hit the target(s) */ public hitCount = 0; @@ -6893,8 +6941,9 @@ export class PokemonTurnData { public failedRunAway = false; public joinedRound = false; /** + * The amount of times this Pokemon has acted again and used a move in the current turn. * Used to make sure multi-hits occur properly when the user is - * forced to act again in the same turn + * forced to act again in the same turn, and **must be incremented** by any effects that grant extra actions. */ public extraTurns = 0; /** diff --git a/src/overrides.ts b/src/overrides.ts index 6086f75a58e..5858be77dac 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -285,17 +285,17 @@ export const defaultOverrides = new DefaultOverrides(); export default { ...defaultOverrides, - ...overrides + ...overrides, } satisfies InstanceType; export type BattleStyle = "double" | "single" | "even-doubles" | "odd-doubles"; export type RandomTrainerOverride = { /** The Type of trainer to force */ - trainerType: Exclude, + trainerType: Exclude; /* If the selected trainer type has a double version, it will always use its double version. */ - alwaysDouble?: boolean -} + alwaysDouble?: boolean; +}; /** The type of the {@linkcode DefaultOverrides} class */ export type OverridesType = typeof DefaultOverrides; diff --git a/src/phase-manager.ts b/src/phase-manager.ts index 8c22a45758c..9390e6dd75d 100644 --- a/src/phase-manager.ts +++ b/src/phase-manager.ts @@ -397,7 +397,7 @@ export class PhaseManager { * @returns the found phase or undefined if none found */ findPhase

(phaseFilter: (phase: P) => boolean): P | undefined { - return this.phaseQueue.find(phaseFilter) as P; + return this.phaseQueue.find(phaseFilter) as P | undefined; } tryReplacePhase(phaseFilter: (phase: Phase) => boolean, phase: Phase): boolean { diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index e796f22921c..754d54de70a 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -22,6 +22,7 @@ import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { isNullOrUndefined } from "#app/utils/common"; import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagType } from "#app/enums/arena-tag-type"; +import { isVirtual, isIgnorePP, MoveUseMode } from "#enums/move-use-mode"; export class CommandPhase extends FieldPhase { public readonly phaseName = "CommandPhase"; @@ -80,7 +81,7 @@ export class CommandPhase extends FieldPhase { ) { globalScene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.FIGHT, - move: { move: MoveId.NONE, targets: [] }, + move: { move: MoveId.NONE, targets: [], useMode: MoveUseMode.NORMAL }, skip: true, }; } @@ -103,29 +104,31 @@ export class CommandPhase extends FieldPhase { moveQueue.length && moveQueue[0] && moveQueue[0].move && - !moveQueue[0].virtual && + !isVirtual(moveQueue[0].useMode) && (!playerPokemon.getMoveset().find(m => m.moveId === moveQueue[0].move) || !playerPokemon .getMoveset() [playerPokemon.getMoveset().findIndex(m => m.moveId === moveQueue[0].move)].isUsable( playerPokemon, - moveQueue[0].ignorePP, + isIgnorePP(moveQueue[0].useMode), )) ) { moveQueue.shift(); } + // TODO: Refactor this. I did a few simple find/replace matches but this is just ABHORRENTLY structured if (moveQueue.length > 0) { const queuedMove = moveQueue[0]; if (!queuedMove.move) { - this.handleCommand(Command.FIGHT, -1); + this.handleCommand(Command.FIGHT, -1, MoveUseMode.NORMAL); } else { const moveIndex = playerPokemon.getMoveset().findIndex(m => m.moveId === queuedMove.move); if ( - (moveIndex > -1 && playerPokemon.getMoveset()[moveIndex].isUsable(playerPokemon, queuedMove.ignorePP)) || - queuedMove.virtual + (moveIndex > -1 && + playerPokemon.getMoveset()[moveIndex].isUsable(playerPokemon, isIgnorePP(queuedMove.useMode))) || + isVirtual(queuedMove.useMode) ) { - this.handleCommand(Command.FIGHT, moveIndex, queuedMove.ignorePP, queuedMove); + this.handleCommand(Command.FIGHT, moveIndex, queuedMove.useMode, queuedMove); } else { globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); } @@ -143,18 +146,23 @@ export class CommandPhase extends FieldPhase { } } + /** + * TODO: Remove `args` and clean this thing up + * Code will need to be copied over from pkty except replacing the `virtual` and `ignorePP` args with a corresponding `MoveUseMode`. + */ handleCommand(command: Command, cursor: number, ...args: any[]): boolean { const playerPokemon = globalScene.getPlayerField()[this.fieldIndex]; let success = false; switch (command) { + // TODO: We don't need 2 args for this - moveUseMode is carried over from queuedMove case Command.TERA: case Command.FIGHT: { let useStruggle = false; const turnMove: TurnMove | undefined = args.length === 2 ? (args[1] as TurnMove) : undefined; if ( cursor === -1 || - playerPokemon.trySelectMove(cursor, args[0] as boolean) || + playerPokemon.trySelectMove(cursor, isIgnorePP(args[0] as MoveUseMode)) || (useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m.isUsable(playerPokemon)).length) ) { let moveId: MoveId; @@ -171,7 +179,7 @@ export class CommandPhase extends FieldPhase { const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, - move: { move: moveId, targets: [], ignorePP: args[0] }, + move: { move: moveId, targets: [], useMode: args[0] }, args: args, }; const preTurnCommand: TurnCommand = { diff --git a/src/phases/move-charge-phase.ts b/src/phases/move-charge-phase.ts index 15a98ebabd2..51b8fe96ff6 100644 --- a/src/phases/move-charge-phase.ts +++ b/src/phases/move-charge-phase.ts @@ -8,10 +8,11 @@ import { MoveResult } from "#enums/move-result"; import { BooleanHolder } from "#app/utils/common"; import { PokemonPhase } from "#app/phases/pokemon-phase"; import { BattlerTagType } from "#enums/battler-tag-type"; +import type { MoveUseMode } from "#enums/move-use-mode"; +import type { ChargingMove } from "#app/@types/move-types"; /** * Phase for the "charging turn" of two-turn moves (e.g. Dig). - * @extends {@linkcode PokemonPhase} */ export class MoveChargePhase extends PokemonPhase { public readonly phaseName = "MoveChargePhase"; @@ -20,10 +21,21 @@ export class MoveChargePhase extends PokemonPhase { /** The field index targeted by the move (Charging moves assume single target) */ public targetIndex: BattlerIndex; - constructor(battlerIndex: BattlerIndex, targetIndex: BattlerIndex, move: PokemonMove) { + /** The {@linkcode MoveUseMode} of the move that triggered the charge; passed on from move phase */ + private useMode: MoveUseMode; + + /** + * Create a new MoveChargePhase. + * @param battlerIndex - The {@linkcode BattlerIndex} of the user. + * @param targetIndex - The {@linkcode BattlerIndex} of the target. + * @param move - The {@linkcode PokemonMove} being used + * @param useMode - The move's {@linkcode MoveUseMode} + */ + constructor(battlerIndex: BattlerIndex, targetIndex: BattlerIndex, move: PokemonMove, useMode: MoveUseMode) { super(battlerIndex); this.move = move; this.targetIndex = targetIndex; + this.useMode = useMode; } public override start() { @@ -37,7 +49,8 @@ export class MoveChargePhase extends PokemonPhase { // immediately end this phase. if (!target || !move.isChargingMove()) { console.warn("Invalid parameters for MoveChargePhase"); - return super.end(); + super.end(); + return; } new MoveChargeAnim(move.chargeAnim, move.id, user).play(false, () => { @@ -52,29 +65,30 @@ export class MoveChargePhase extends PokemonPhase { /** Checks the move's instant charge conditions, then ends this phase. */ public override end() { const user = this.getUserPokemon(); - const move = this.move.getMove(); + // Checked for `ChargingMove` in `this.start()` + const move = this.move.getMove() as ChargingMove; - if (move.isChargingMove()) { - const instantCharge = new BooleanHolder(false); + const instantCharge = new BooleanHolder(false); + applyMoveChargeAttrs("InstantChargeAttr", user, null, move, instantCharge); - applyMoveChargeAttrs("InstantChargeAttr", user, null, move, instantCharge); - - if (instantCharge.value) { - // this MoveEndPhase will be duplicated by the queued MovePhase if not removed - globalScene.phaseManager.tryRemovePhase(phase => phase.is("MoveEndPhase") && phase.getPokemon() === user); - // queue a new MovePhase for this move's attack phase - globalScene.phaseManager.unshiftNew("MovePhase", user, [this.targetIndex], this.move, false); - } else { - user.getMoveQueue().push({ move: move.id, targets: [this.targetIndex] }); - } - - // Add this move's charging phase to the user's move history - user.pushMoveHistory({ - move: this.move.moveId, - targets: [this.targetIndex], - result: MoveResult.OTHER, - }); + // If instantly charging, remove the pending MoveEndPhase and queue a new MovePhase for the "attack" portion of the move. + // Otherwise, add the attack portion to the user's move queue to execute next turn. + // TODO: This checks status twice for a single-turn usage... + if (instantCharge.value) { + globalScene.phaseManager.tryRemovePhase(phase => phase.is("MoveEndPhase") && phase.getPokemon() === user); + globalScene.phaseManager.unshiftNew("MovePhase", user, [this.targetIndex], this.move, this.useMode); + } else { + user.pushMoveQueue({ move: move.id, targets: [this.targetIndex], useMode: this.useMode }); } + + // Add this move's charging phase to the user's move history + user.pushMoveHistory({ + move: this.move.moveId, + targets: [this.targetIndex], + result: MoveResult.OTHER, + useMode: this.useMode, + }); + super.end(); } diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 770d9c79a2a..35c803ee1d7 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -54,20 +54,25 @@ import { HitCheckResult } from "#enums/hit-check-result"; import type Move from "#app/data/moves/move"; import { isFieldTargeted } from "#app/data/moves/move-utils"; import { DamageAchv } from "#app/system/achv"; +import { isVirtual, isReflected, MoveUseMode } from "#enums/move-use-mode"; -type HitCheckEntry = [HitCheckResult, TypeDamageMultiplier]; +export type HitCheckEntry = [HitCheckResult, TypeDamageMultiplier]; export class MoveEffectPhase extends PokemonPhase { public readonly phaseName = "MoveEffectPhase"; public move: Move; - private virtual = false; protected targets: BattlerIndex[]; - protected reflected = false; + protected useMode: MoveUseMode; /** The result of the hit check against each target */ private hitChecks: HitCheckEntry[]; - /** The move history entry for the move */ + /** + * Log to be entered into the user's move history once the move result is resolved. + + * Note that `result` logs whether the move was successfully + * used in the sense of "Does it have an effect on the user?". + */ private moveHistoryEntry: TurnMove; /** Is this the first strike of a move? */ @@ -75,19 +80,20 @@ export class MoveEffectPhase extends PokemonPhase { /** Is this the last strike of a move? */ private lastHit: boolean; - /** Phases queued during moves */ + /** + * Phases queued during moves; used to add a new MovePhase for reflected moves after triggering. + * TODO: Remove this and move the reflection logic to ability-side + */ private queuedPhases: Phase[] = []; /** - * @param reflected Indicates that the move was reflected by the user due to magic coat or magic bounce - * @param virtual Indicates that the move is a virtual move (i.e. called by metronome) + * @param useMode - The {@linkcode MoveUseMode} corresponding to how this move was used. */ - constructor(battlerIndex: BattlerIndex, targets: BattlerIndex[], move: Move, reflected = false, virtual = false) { + constructor(battlerIndex: BattlerIndex, targets: BattlerIndex[], move: Move, useMode: MoveUseMode) { super(battlerIndex); this.move = move; - this.virtual = virtual; + this.useMode = useMode; - this.reflected = reflected; /** * In double battles, if the right Pokemon selects a spread move and the left Pokemon dies * with no party members available to switch in, then the right Pokemon takes the index @@ -158,7 +164,7 @@ export class MoveEffectPhase extends PokemonPhase { * Queue the phaes that should occur when the target reflects the move back to the user * @param user - The {@linkcode Pokemon} using this phase's invoked move * @param target - The {@linkcode Pokemon} that is reflecting the move - * + * TODO: Rework this to use `onApply` of Magic Coat */ private queueReflectedMove(user: Pokemon, target: Pokemon): void { const newTargets = this.move.isMultiTarget() @@ -181,10 +187,8 @@ export class MoveEffectPhase extends PokemonPhase { "MovePhase", target, newTargets, - new PokemonMove(this.move.id, 0, 0, true), - true, - true, - true, + new PokemonMove(this.move.id), + MoveUseMode.REFLECTED, ), ); } @@ -278,8 +282,18 @@ export class MoveEffectPhase extends PokemonPhase { const overridden = new BooleanHolder(false); const move = this.move; - // Assume single target for override - applyMoveAttrs("OverrideMoveEffectAttr", user, this.getFirstTarget() ?? null, move, overridden, this.virtual); + // Apply effects to override a move effect. + // Assuming single target here works as this is (currently) + // only used for Future Sight, calling and Pledge moves. + // TODO: change if any other move effect overrides are introduced + applyMoveAttrs( + "OverrideMoveEffectAttr", + user, + this.getFirstTarget() ?? null, + move, + overridden, + isVirtual(this.useMode), + ); // If other effects were overriden, stop this phase before they can be applied if (overridden.value) { @@ -290,8 +304,8 @@ export class MoveEffectPhase extends PokemonPhase { // Lapse `MOVE_EFFECT` effects (i.e. semi-invulnerability) when applicable user.lapseTags(BattlerTagLapseType.MOVE_EFFECT); - // If the user is acting again (such as due to Instruct), reset hitsLeft/hitCount so that - // the move executes correctly (ensures all hits of a multi-hit are properly calculated) + // If the user is acting again (such as due to Instruct or Dancer), reset hitsLeft/hitCount and + // recalculate hit count for multi-hit moves. if (user.turnData.hitsLeft === 0 && user.turnData.hitCount > 0 && user.turnData.extraTurns > 0) { user.turnData.hitsLeft = -1; user.turnData.hitCount = 0; @@ -316,16 +330,11 @@ export class MoveEffectPhase extends PokemonPhase { user.turnData.hitsLeft = hitCount.value; } - /* - * Log to be entered into the user's move history once the move result is resolved. - * Note that `result` logs whether the move was successfully - * used in the sense of "Does it have an effect on the user?". - */ this.moveHistoryEntry = { move: this.move.id, targets: this.targets, result: MoveResult.PENDING, - virtual: this.virtual, + useMode: this.useMode, }; const fieldMove = isFieldTargeted(move); @@ -390,29 +399,35 @@ export class MoveEffectPhase extends PokemonPhase { public override end(): void { const user = this.getUserPokemon(); - /** - * If this phase isn't for the invoked move's last strike, - * unshift another MoveEffectPhase for the next strike. - * Otherwise, queue a message indicating the number of times the move has struck - * (if the move has struck more than once), then apply the heal from Shell Bell - * to the user. - */ - if (user) { - if (user.turnData.hitsLeft && --user.turnData.hitsLeft >= 1 && this.getFirstTarget()?.isActive()) { - globalScene.phaseManager.unshiftPhase(this.getNewHitPhase()); - } else { - // Queue message for number of hits made by multi-move - // If multi-hit attack only hits once, still want to render a message - const hitsTotal = user.turnData.hitCount - Math.max(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 - globalScene.phaseManager.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal })); - } - globalScene.applyModifiers(HitHealModifier, this.player, user); - this.getTargets().forEach(target => (target.turnData.moveEffectiveness = null)); - } + if (!user) { + super.end(); + return; } + /** + * If this phase isn't for the invoked move's last strike (and we still have something to hit), + * unshift another MoveEffectPhase for the next strike before ending this phase. + */ + if (--user.turnData.hitsLeft >= 1 && this.getFirstTarget()) { + this.addNextHitPhase(); + super.end(); + return; + } + + /** + * All hits of the move have resolved by now. + * Queue message for multi-strike moves before applying Shell Bell heals & proccing Dancer-like effects. + */ + const hitsTotal = user.turnData.hitCount - Math.max(user.turnData.hitsLeft, 0); + if (hitsTotal > 1 || user.turnData.hitsLeft > 0) { + // Queue message if multiple hits occurred or were slated to occur (such as a Triple Axel miss) + globalScene.phaseManager.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal })); + } + + globalScene.applyModifiers(HitHealModifier, this.player, user); + this.getTargets().forEach(target => { + target.turnData.moveEffectiveness = null; + }); super.end(); } @@ -422,7 +437,6 @@ export class MoveEffectPhase extends PokemonPhase { * @param user - The {@linkcode Pokemon} using this phase's invoked move * @param target - {@linkcode Pokemon} the current target of this phase's invoked move * @param hitResult - The {@linkcode HitResult} of the attempted move - * @returns a `Promise` intended to be passed into a `then()` call. */ protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): void { applyPostDefendAbAttrs("PostDefendAbAttr", target, user, this.move, hitResult); @@ -434,7 +448,6 @@ export class MoveEffectPhase extends PokemonPhase { * @param user - The {@linkcode Pokemon} using this phase's invoked move * @param target - {@linkcode Pokemon} the current target of this phase's invoked move * @param dealsDamage - `true` if the attempted move successfully dealt damage - * @returns a function intended to be passed into a `then()` call. */ protected applyHeldItemFlinchCheck(user: Pokemon, target: Pokemon, dealsDamage: boolean): void { if (this.move.hasAttr("FlinchAttr")) { @@ -458,8 +471,9 @@ export class MoveEffectPhase extends PokemonPhase { * @param user - The {@linkcode Pokemon} using this phase's invoked move * @param target - {@linkcode Pokemon} the target to check for protection * @param move - The {@linkcode Move} being used + * @returns Whether the pokemon was protected */ - private protectedCheck(user: Pokemon, target: Pokemon) { + private protectedCheck(user: Pokemon, target: Pokemon): boolean { /** The {@linkcode ArenaTagSide} to which the target belongs */ const targetSide = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; /** Has the invoked move been cancelled by conditional protection (e.g Quick Guard)? */ @@ -480,14 +494,15 @@ export class MoveEffectPhase extends PokemonPhase { ); } + // TODO: Break up this chunky boolean to make it more palatable return ( ![MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES].includes(this.move.moveTarget) && (bypassIgnoreProtect.value || !this.move.doesFlagEffectApply({ flag: MoveFlags.IGNORE_PROTECT, user, target })) && (hasConditionalProtectApplied.value || (!target.findTags(t => t instanceof DamageProtectedTag).length && - target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType))) || + target.findTags(t => t instanceof ProtectedTag).some(t => target.lapseTag(t.tagType))) || (this.move.category !== MoveCategory.STATUS && - target.findTags(t => t instanceof DamageProtectedTag).find(t => target.lapseTag(t.tagType)))) + target.findTags(t => t instanceof DamageProtectedTag).some(t => target.lapseTag(t.tagType)))) ); } @@ -547,7 +562,8 @@ export class MoveEffectPhase extends PokemonPhase { return [HitCheckResult.PROTECTED, 0]; } - if (!this.reflected && move.doesFlagEffectApply({ flag: MoveFlags.REFLECTABLE, user, target })) { + // Reflected moves cannot be reflected again + if (!isReflected(this.useMode) && move.doesFlagEffectApply({ flag: MoveFlags.REFLECTABLE, user, target })) { return [HitCheckResult.REFLECTED, 0]; } @@ -660,12 +676,17 @@ export class MoveEffectPhase extends PokemonPhase { return (this.player ? globalScene.getPlayerField() : globalScene.getEnemyField())[this.fieldIndex]; } - /** @returns An array of all {@linkcode Pokemon} targeted by this phase's invoked move */ + /** + * @returns An array of {@linkcode Pokemon} that are: + * - On-field and active + * - Non-fainted + * - Targeted by this phase's invoked move + */ public getTargets(): Pokemon[] { return globalScene.getField(true).filter(p => this.targets.indexOf(p.getBattlerIndex()) > -1); } - /** @returns The first target of this phase's invoked move */ + /** @returns The first active, non-fainted target of this phase's invoked move. */ public getFirstTarget(): Pokemon | undefined { return this.getTargets()[0]; } @@ -705,9 +726,12 @@ export class MoveEffectPhase extends PokemonPhase { } } - /** @returns A new `MoveEffectPhase` with the same properties as this phase */ - protected getNewHitPhase(): MoveEffectPhase { - return new MoveEffectPhase(this.battlerIndex, this.targets, this.move, this.reflected, this.virtual); + /** + * Unshifts a new `MoveEffectPhase` with the same properties as this phase. + * Used to queue the next hit of multi-strike moves. + */ + protected addNextHitPhase(): void { + globalScene.phaseManager.unshiftNew("MoveEffectPhase", this.battlerIndex, this.targets, this.move, this.useMode); } /** Removes all substitutes that were broken by this phase's invoked move */ @@ -729,7 +753,6 @@ export class MoveEffectPhase extends PokemonPhase { * @param firstTarget Whether the target is the first to be hit by the current strike * @param selfTarget If defined, limits the effects triggered to either self-targeted * effects (if set to `true`) or targeted effects (if set to `false`). - * @returns a `Promise` applying the relevant move effects. */ protected triggerMoveEffects( triggerType: MoveEffectTrigger, @@ -775,6 +798,7 @@ export class MoveEffectPhase extends PokemonPhase { const hitResult = this.applyMove(user, target, effectiveness); + // Apply effects to the user (always) and the target (if not blocked by substitute). this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, true); if (!this.move.hitsSubstitute(user, target)) { this.applyOnTargetEffects(user, target, hitResult, firstTarget); diff --git a/src/phases/move-end-phase.ts b/src/phases/move-end-phase.ts index 953f8eae4ce..8c8f2ac5239 100644 --- a/src/phases/move-end-phase.ts +++ b/src/phases/move-end-phase.ts @@ -25,9 +25,9 @@ export class MoveEndPhase extends PokemonPhase { if (!this.wasFollowUp && pokemon?.isActive(true)) { pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE); } - globalScene.arena.setIgnoreAbilities(false); // Remove effects which were set on a Pokemon which removes them on summon (i.e. via Mold Breaker) + globalScene.arena.setIgnoreAbilities(false); for (const target of this.targets) { if (target) { applyPostSummonAbAttrs("PostSummonRemoveEffectAbAttr", target); diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index a14327749f7..41a1042387b 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -5,7 +5,6 @@ import type { DelayedAttackTag } from "#app/data/arena-tag"; import { CommonAnim } from "#enums/move-anims-common"; import { CenterOfAttentionTag } from "#app/data/battler-tags"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; -import type { HealStatusEffectAttr } from "#app/data/moves/move"; import { applyMoveAttrs } from "#app/data/moves/apply-attrs"; import { allMoves } from "#app/data/data-lists"; import { MoveFlags } from "#enums/MoveFlags"; @@ -20,13 +19,14 @@ import { MoveResult } from "#enums/move-result"; import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; import { BattlePhase } from "#app/phases/battle-phase"; -import { NumberHolder } from "#app/utils/common"; +import { enumValueToKey, NumberHolder } from "#app/utils/common"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; import { MoveId } from "#enums/move-id"; import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; +import { isVirtual, isIgnorePP, isReflected, MoveUseMode, isIgnoreStatus } from "#enums/move-use-mode"; import { frenzyMissFunc } from "#app/data/moves/move-utils"; export class MovePhase extends BattlePhase { @@ -34,17 +34,19 @@ export class MovePhase extends BattlePhase { protected _pokemon: Pokemon; protected _move: PokemonMove; protected _targets: BattlerIndex[]; - protected followUp: boolean; - protected ignorePp: boolean; + public readonly useMode: MoveUseMode; // Made public for quash protected forcedLast: boolean; + + /** Whether the current move should fail but still use PP */ protected failed = false; + /** Whether the current move should cancel and retain PP */ protected cancelled = false; - protected reflected = false; public get pokemon(): Pokemon { return this._pokemon; } + // TODO: Do we need public getters but only protected setters? protected set pokemon(pokemon: Pokemon) { this._pokemon = pokemon; } @@ -66,51 +68,42 @@ export class MovePhase extends BattlePhase { } /** - * @param followUp Indicates that the move being used is a "follow-up" - for example, a move being used by Metronome or Dancer. - * Follow-ups bypass a few failure conditions, including flinches, sleep/paralysis/freeze and volatile status checks, etc. - * @param reflected Indicates that the move was reflected by Magic Coat or Magic Bounce. - * Reflected moves cannot be reflected again and will not trigger Dancer. + * Create a new MovePhase for using moves. + * @param pokemon - The {@linkcode Pokemon} using the move + * @param move - The {@linkcode PokemonMove} to use + * @param useMode - The {@linkcode MoveUseMode} corresponding to this move's means of execution (usually `MoveUseMode.NORMAL`). + * Not marked optional to ensure callers correctly pass on `useModes`. + * @param forcedLast - Whether to force this phase to occur last in order (for {@linkcode MoveId.QUASH}); default `false` */ - - constructor( - pokemon: Pokemon, - targets: BattlerIndex[], - move: PokemonMove, - followUp = false, - ignorePp = false, - reflected = false, - forcedLast = false, - ) { + constructor(pokemon: Pokemon, targets: BattlerIndex[], move: PokemonMove, useMode: MoveUseMode, forcedLast = false) { super(); this.pokemon = pokemon; this.targets = targets; this.move = move; - this.followUp = followUp; - this.ignorePp = ignorePp; - this.reflected = reflected; + this.useMode = useMode; this.forcedLast = forcedLast; } /** - * Checks if the pokemon is active, if the move is usable, and that the move is targetting something. + * Checks if the pokemon is active, if the move is usable, and that the move is targeting something. * @param ignoreDisableTags `true` to not check if the move is disabled * @returns `true` if all the checks pass */ public canMove(ignoreDisableTags = false): boolean { return ( this.pokemon.isActive(true) && - this.move.isUsable(this.pokemon, this.ignorePp, ignoreDisableTags) && - !!this.targets.length + this.move.isUsable(this.pokemon, isIgnorePP(this.useMode), ignoreDisableTags) && + this.targets.length > 0 ); } - /**Signifies the current move should fail but still use PP */ + /** Signifies the current move should fail but still use PP */ public fail(): void { this.failed = true; } - /**Signifies the current move should cancel and retain PP */ + /** Signifies the current move should cancel and retain PP */ public cancel(): void { this.cancelled = true; } @@ -118,7 +111,7 @@ export class MovePhase extends BattlePhase { /** * Shows whether the current move has been forced to the end of the turn * Needed for speed order, see {@linkcode MoveId.QUASH} - * */ + */ public isForcedLast(): boolean { return this.forcedLast; } @@ -126,9 +119,10 @@ export class MovePhase extends BattlePhase { public start(): void { super.start(); - console.log(MoveId[this.move.moveId]); + console.log(MoveId[this.move.moveId], enumValueToKey(MoveUseMode, this.useMode)); - // Check if move is unusable (e.g. because it's out of PP due to a mid-turn Spite). + // Check if move is unusable (e.g. running out of PP due to a mid-turn Spite + // or the user no longer being on field), ending the phase early if not. if (!this.canMove(true)) { if (this.pokemon.isActive(true)) { this.fail(); @@ -142,20 +136,20 @@ export class MovePhase extends BattlePhase { this.pokemon.turnData.acted = true; // Reset hit-related turn data when starting follow-up moves (e.g. Metronomed moves, Dancer repeats) - if (this.followUp) { + if (isVirtual(this.useMode)) { this.pokemon.turnData.hitsLeft = -1; this.pokemon.turnData.hitCount = 0; } // Check move to see if arena.ignoreAbilities should be true. - if (!this.followUp || this.reflected) { - if ( - this.move - .getMove() - .doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user: this.pokemon, isFollowUp: this.followUp }) - ) { - globalScene.arena.setIgnoreAbilities(true, this.pokemon.getBattlerIndex()); - } + if ( + this.move.getMove().doesFlagEffectApply({ + flag: MoveFlags.IGNORE_ABILITIES, + user: this.pokemon, + isFollowUp: isVirtual(this.useMode), // Sunsteel strike and co. don't work when called indirectly + }) + ) { + globalScene.arena.setIgnoreAbilities(true, this.pokemon.getBattlerIndex()); } this.resolveRedirectTarget(); @@ -188,7 +182,7 @@ export class MovePhase extends BattlePhase { if ( (targets.length === 0 && !this.move.getMove().hasAttr("AddArenaTrapTagAttr")) || - (moveQueue.length && moveQueue[0].move === MoveId.NONE) + (moveQueue.length > 0 && moveQueue[0].move === MoveId.NONE) ) { this.showMoveText(); this.showFailedText(); @@ -201,83 +195,98 @@ export class MovePhase extends BattlePhase { } /** - * Handles {@link StatusEffect.SLEEP Sleep}/{@link StatusEffect.PARALYSIS Paralysis}/{@link StatusEffect.FREEZE Freeze} rolls and side effects. + * Handles {@link StatusEffect.SLEEP | Sleep}/{@link StatusEffect.PARALYSIS | Paralysis}/{@link StatusEffect.FREEZE | Freeze} rolls and side effects. */ protected resolvePreMoveStatusEffects(): void { - if (!this.followUp && this.pokemon.status && !this.pokemon.status.isPostTurn()) { - this.pokemon.status.incrementTurn(); - let activated = false; - let healed = false; + // Skip for follow ups/reflected moves, no status condition or post turn statuses (e.g. Poison/Toxic) + if (!this.pokemon.status?.effect || this.pokemon.status.isPostTurn() || isIgnoreStatus(this.useMode)) { + return; + } - switch (this.pokemon.status.effect) { - case StatusEffect.PARALYSIS: - activated = - (!this.pokemon.randBattleSeedInt(4) || Overrides.STATUS_ACTIVATION_OVERRIDE === true) && - Overrides.STATUS_ACTIVATION_OVERRIDE !== false; - break; - case StatusEffect.SLEEP: { - applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove()); - const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0); - applyAbAttrs( - "ReduceStatusEffectDurationAbAttr", - this.pokemon, - null, - false, - this.pokemon.status.effect, - turnsRemaining, - ); - this.pokemon.status.sleepTurnsRemaining = turnsRemaining.value; - healed = this.pokemon.status.sleepTurnsRemaining <= 0; - activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP); - break; - } - case StatusEffect.FREEZE: - healed = - !!this.move - .getMove() - .findAttr( - attr => - attr.is("HealStatusEffectAttr") && - attr.selfTarget && - (attr as unknown as HealStatusEffectAttr).isOfEffect(StatusEffect.FREEZE), - ) || - (!this.pokemon.randBattleSeedInt(5) && Overrides.STATUS_ACTIVATION_OVERRIDE !== true) || - Overrides.STATUS_ACTIVATION_OVERRIDE === false; + if ( + this.useMode === MoveUseMode.INDIRECT && + [StatusEffect.SLEEP, StatusEffect.FREEZE].includes(this.pokemon.status.effect) + ) { + // Dancer thaws out or wakes up a frozen/sleeping user prior to use + this.pokemon.resetStatus(false); + return; + } - activated = !healed; - break; + this.pokemon.status.incrementTurn(); + + /** Whether to prevent us from using the move */ + let activated = false; + /** Whether to cure the status */ + let healed = false; + + switch (this.pokemon.status.effect) { + case StatusEffect.PARALYSIS: + activated = + (this.pokemon.randBattleSeedInt(4) === 0 || Overrides.STATUS_ACTIVATION_OVERRIDE === true) && + Overrides.STATUS_ACTIVATION_OVERRIDE !== false; + break; + case StatusEffect.SLEEP: { + applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove()); + const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0); + applyAbAttrs( + "ReduceStatusEffectDurationAbAttr", + this.pokemon, + null, + false, + this.pokemon.status.effect, + turnsRemaining, + ); + this.pokemon.status.sleepTurnsRemaining = turnsRemaining.value; + healed = this.pokemon.status.sleepTurnsRemaining <= 0; + activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP); + break; } + case StatusEffect.FREEZE: + healed = + !!this.move + .getMove() + .findAttr( + attr => attr.is("HealStatusEffectAttr") && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE), + ) || + (!this.pokemon.randBattleSeedInt(5) && Overrides.STATUS_ACTIVATION_OVERRIDE !== true) || + Overrides.STATUS_ACTIVATION_OVERRIDE === false; - if (activated) { - this.cancel(); - globalScene.phaseManager.queueMessage( - getStatusEffectActivationText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)), - ); - globalScene.phaseManager.unshiftNew( - "CommonAnimPhase", - this.pokemon.getBattlerIndex(), - undefined, - CommonAnim.POISON + (this.pokemon.status.effect - 1), - ); - } else if (healed) { - globalScene.phaseManager.queueMessage( - getStatusEffectHealText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)), - ); - this.pokemon.resetStatus(); - this.pokemon.updateInfo(); - } + activated = !healed; + break; + } + + if (activated) { + // Cancel move activation and play effect + this.cancel(); + globalScene.phaseManager.queueMessage( + getStatusEffectActivationText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)), + ); + globalScene.phaseManager.unshiftNew( + "CommonAnimPhase", + this.pokemon.getBattlerIndex(), + undefined, + CommonAnim.POISON + (this.pokemon.status.effect - 1), // offset anim # by effect # + ); + } else if (healed) { + // cure status and play effect + globalScene.phaseManager.queueMessage( + getStatusEffectHealText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)), + ); + this.pokemon.resetStatus(); + this.pokemon.updateInfo(); } } /** - * Lapse {@linkcode BattlerTagLapseType.PRE_MOVE PRE_MOVE} tags that trigger before a move is used, regardless of whether or not it failed. - * Also lapse {@linkcode BattlerTagLapseType.MOVE MOVE} tags if the move should be successful. + * Lapse {@linkcode BattlerTagLapseType.PRE_MOVE | PRE_MOVE} tags that trigger before a move is used, regardless of whether or not it failed. + * Also lapse {@linkcode BattlerTagLapseType.MOVE | MOVE} tags if the move is successful and not called indirectly. */ protected lapsePreMoveAndMoveTags(): void { this.pokemon.lapseTags(BattlerTagLapseType.PRE_MOVE); // TODO: does this intentionally happen before the no targets/MoveId.NONE on queue cancellation case is checked? - if (!this.followUp && this.canMove() && !this.cancelled) { + // (In other words, check if truant can proc on a move w/o targets) + if (!isIgnoreStatus(this.useMode) && this.canMove() && !this.cancelled) { this.pokemon.lapseTags(BattlerTagLapseType.MOVE); } } @@ -285,11 +294,12 @@ export class MovePhase extends BattlePhase { protected useMove(): void { const targets = this.getActiveTargetPokemon(); const moveQueue = this.pokemon.getMoveQueue(); + const move = this.move.getMove(); // form changes happen even before we know that the move wll execute. globalScene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger); - const isDelayedAttack = this.move.getMove().hasAttr("DelayedAttackAttr"); + const isDelayedAttack = move.hasAttr("DelayedAttackAttr"); if (isDelayedAttack) { // Check the player side arena if future sight is active const futureSightTags = globalScene.arena.findTags(t => t.tagType === ArenaTagType.FUTURE_SIGHT); @@ -329,21 +339,21 @@ export class MovePhase extends BattlePhase { this.showMoveText(); } - if (moveQueue.length > 0) { - // Using .shift here clears out two turn moves once they've been used - this.ignorePp = moveQueue.shift()?.ignorePP ?? false; - } - + // Clear out any two turn moves once they've been used. + // TODO: Refactor move queues and remove this assignment; + // Move queues should be handled by the calling `CommandPhase` or a manager for it + // @ts-expect-error - useMode is readonly and shouldn't normally be assigned to + this.useMode = moveQueue.shift()?.useMode ?? this.useMode; if (this.pokemon.getTag(BattlerTagType.CHARGING)?.sourceMove === this.move.moveId) { this.pokemon.lapseTag(BattlerTagType.CHARGING); } - // "commit" to using the move, deducting PP. - if (!this.ignorePp) { + if (!isIgnorePP(this.useMode)) { + // "commit" to using the move, deducting PP. const ppUsed = 1 + this.getPpIncreaseFromPressure(targets); this.move.usePp(ppUsed); - globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed)); + globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, move, this.move.ppUsed)); } /** @@ -357,8 +367,6 @@ export class MovePhase extends BattlePhase { * TODO: These steps are straightforward, but the implementation below is extremely convoluted. */ - const move = this.move.getMove(); - /** * Move conditions assume the move has a single target * TODO: is this sustainable? @@ -394,8 +402,7 @@ export class MovePhase extends BattlePhase { this.pokemon.getBattlerIndex(), this.targets, move, - this.reflected, - this.move.virtual, + this.useMode, ); } else { if ([MoveId.ROAR, MoveId.WHIRLWIND, MoveId.TRICK_OR_TREAT, MoveId.FORESTS_CURSE].includes(this.move.moveId)) { @@ -406,7 +413,7 @@ export class MovePhase extends BattlePhase { move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, - virtual: this.move.virtual, + useMode: this.useMode, }); const failureMessage = move.getFailedText(this.pokemon, targets[0], move); @@ -426,8 +433,10 @@ export class MovePhase extends BattlePhase { } // Handle Dancer, which triggers immediately after a move is used (rather than waiting on `this.end()`). - // Note that the `!this.followUp` check here prevents an infinite Dancer loop. - if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !this.followUp) { + // Note the MoveUseMode check here prevents an infinite Dancer loop. + const dancerModes: MoveUseMode[] = [MoveUseMode.INDIRECT, MoveUseMode.REFLECTED] as const; + if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !dancerModes.includes(this.useMode)) { + // TODO: Fix in dancer PR to move to MEP for hit checks globalScene.getField(true).forEach(pokemon => { applyPostMoveUsedAbAttrs("PostMoveUsedAbAttr", pokemon, this.move, this.pokemon, this.targets); }); @@ -439,23 +448,16 @@ export class MovePhase extends BattlePhase { const move = this.move.getMove(); const targets = this.getActiveTargetPokemon(); - if (move.applyConditions(this.pokemon, targets[0], move)) { - // Protean and Libero apply on the charging turn of charge moves - applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, this.move.getMove()); + this.showMoveText(); - this.showMoveText(); - globalScene.phaseManager.unshiftNew( - "MoveChargePhase", - this.pokemon.getBattlerIndex(), - this.targets[0], - this.move, - ); - } else { + // Conditions currently assume single target + // TODO: Is this sustainable? + if (!move.applyConditions(this.pokemon, targets[0], move)) { this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, - virtual: this.move.virtual, + useMode: this.useMode, }); const failureMessage = move.getFailedText(this.pokemon, targets[0], move); @@ -464,7 +466,19 @@ export class MovePhase extends BattlePhase { // Remove the user from its semi-invulnerable state (if applicable) this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); + return; } + + // Protean and Libero apply on the charging turn of charge moves + applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, this.move.getMove()); + + globalScene.phaseManager.unshiftNew( + "MoveChargePhase", + this.pokemon.getBattlerIndex(), + this.targets[0], + this.move, + this.useMode, + ); } /** @@ -475,7 +489,7 @@ export class MovePhase extends BattlePhase { "MoveEndPhase", this.pokemon.getBattlerIndex(), this.getActiveTargetPokemon(), - this.followUp, + isVirtual(this.useMode), ); super.end(); @@ -607,7 +621,7 @@ export class MovePhase extends BattlePhase { protected handlePreMoveFailures(): void { if (this.cancelled || this.failed) { if (this.failed) { - const ppUsed = this.ignorePp ? 0 : 1; + const ppUsed = isIgnorePP(this.useMode) ? 0 : 1; if (ppUsed) { this.move.usePp(); @@ -624,6 +638,7 @@ export class MovePhase extends BattlePhase { move: MoveId.NONE, result: MoveResult.FAIL, targets: this.targets, + useMode: this.useMode, }); this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); @@ -647,7 +662,7 @@ export class MovePhase extends BattlePhase { } globalScene.phaseManager.queueMessage( - i18next.t(this.reflected ? "battle:magicCoatActivated" : "battle:useMove", { + i18next.t(isReflected(this.useMode) ? "battle:magicCoatActivated" : "battle:useMove", { pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon), moveName: this.move.getName(), }), diff --git a/src/phases/pokemon-phase.ts b/src/phases/pokemon-phase.ts index d7fe58d0b80..cfa30f0d06e 100644 --- a/src/phases/pokemon-phase.ts +++ b/src/phases/pokemon-phase.ts @@ -4,6 +4,10 @@ import type Pokemon from "#app/field/pokemon"; import { FieldPhase } from "./field-phase"; export abstract class PokemonPhase extends FieldPhase { + /** + * The battler index this phase refers to, or the pokemon ID if greater than 3. + * TODO: Make this either use IDs or `BattlerIndex`es, not a weird mix of both + */ protected battlerIndex: BattlerIndex | number; public player: boolean; public fieldIndex: number; @@ -15,10 +19,12 @@ export abstract class PokemonPhase extends FieldPhase { battlerIndex ?? globalScene .getField() - .find(p => p?.isActive())! // TODO: is the bang correct here? - .getBattlerIndex(); + .find(p => p?.isActive()) + ?.getBattlerIndex(); if (battlerIndex === undefined) { - console.warn("There are no Pokemon on the field!"); // TODO: figure out a suitable fallback behavior + // TODO: figure out a suitable fallback behavior + console.warn("There are no Pokemon on the field!"); + battlerIndex = BattlerIndex.PLAYER; } this.battlerIndex = battlerIndex; diff --git a/src/phases/pokemon-transform-phase.ts b/src/phases/pokemon-transform-phase.ts index 65fccb24d99..938915309d9 100644 --- a/src/phases/pokemon-transform-phase.ts +++ b/src/phases/pokemon-transform-phase.ts @@ -53,7 +53,7 @@ export class PokemonTransformPhase extends PokemonPhase { user.summonData.moveset = target.getMoveset().map(m => { if (m) { // If PP value is less than 5, do nothing. If greater, we need to reduce the value to 5. - return new PokemonMove(m.moveId, 0, 0, false, Math.min(m.getMove().pp, 5)); + return new PokemonMove(m.moveId, 0, 0, Math.min(m.getMove().pp, 5)); } console.warn(`Transform: somehow iterating over a ${m} value when copying moveset!`); return new PokemonMove(MoveId.NONE); diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index 6f062cb5fbe..6219907fb68 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -63,7 +63,7 @@ export class TurnStartPhase extends FieldPhase { // This occurs before the main loop because of battles with more than two Pokemon const battlerBypassSpeed = {}; - globalScene.getField(true).map(p => { + globalScene.getField(true).forEach(p => { const bypassSpeed = new BooleanHolder(false); const canCheckHeldItems = new BooleanHolder(true); applyAbAttrs("BypassSpeedChanceAbAttr", p, null, false, bypassSpeed); @@ -124,6 +124,8 @@ export class TurnStartPhase extends FieldPhase { return moveOrder; } + // TODO: Refactor this alongside `CommandPhase.handleCommand` to use SEPARATE METHODS + // Also need a clearer distinction between "turn command" and queued moves start() { super.start(); @@ -157,44 +159,38 @@ export class TurnStartPhase extends FieldPhase { } switch (turnCommand?.command) { - case Command.FIGHT: - { - const queuedMove = turnCommand.move; - pokemon.turnData.order = orderIndex++; - if (!queuedMove) { - continue; - } - const move = - pokemon.getMoveset().find(m => m.moveId === queuedMove.move && m.ppUsed < m.getMovePp()) || - new PokemonMove(queuedMove.move); - if (move.getMove().hasAttr("MoveHeaderAttr")) { - phaseManager.unshiftNew("MoveHeaderPhase", pokemon, move); - } - if (pokemon.isPlayer()) { - if (turnCommand.cursor === -1) { - phaseManager.pushNew("MovePhase", pokemon, turnCommand.targets || turnCommand.move!.targets, move); - } else { - phaseManager.pushNew( - "MovePhase", - pokemon, - turnCommand.targets || turnCommand.move!.targets, // TODO: is the bang correct here? - move, - false, - queuedMove.ignorePP, - ); - } - } else { - phaseManager.pushNew( - "MovePhase", - pokemon, - turnCommand.targets || turnCommand.move!.targets, - move, - false, - queuedMove.ignorePP, - ); - } + case Command.FIGHT: { + const queuedMove = turnCommand.move; + pokemon.turnData.order = orderIndex++; + if (!queuedMove) { + continue; + } + const move = + pokemon.getMoveset().find(m => m.moveId === queuedMove.move && m.ppUsed < m.getMovePp()) ?? + new PokemonMove(queuedMove.move); + if (move.getMove().hasAttr("MoveHeaderAttr")) { + phaseManager.unshiftNew("MoveHeaderPhase", pokemon, move); + } + + if (pokemon.isPlayer() && turnCommand.cursor === -1) { + phaseManager.pushNew( + "MovePhase", + pokemon, + turnCommand.targets || turnCommand.move!.targets, + move, + turnCommand.move!.useMode, + ); //TODO: is the bang correct here? + } else { + phaseManager.pushNew( + "MovePhase", + pokemon, + turnCommand.targets || turnCommand.move!.targets, + move, + queuedMove.useMode, + ); // TODO: is the bang correct here? } break; + } case Command.BALL: phaseManager.unshiftNew("AttemptCapturePhase", turnCommand.targets![0] % 2, turnCommand.cursor!); //TODO: is the bang correct here? break; diff --git a/src/system/version_migration/version_converter.ts b/src/system/version_migration/version_converter.ts index 798115e0395..32e105aec66 100644 --- a/src/system/version_migration/version_converter.ts +++ b/src/system/version_migration/version_converter.ts @@ -63,6 +63,10 @@ import * as v1_8_3 from "./versions/v1_8_3"; // biome-ignore lint/style/noNamespaceImport: Convenience import * as v1_9_0 from "./versions/v1_9_0"; +// --- v1.10.0 PATCHES --- // +// biome-ignore lint/style/noNamespaceImport: Convenience +import * as v1_10_0 from "./versions/v1_10_0"; + /** Current game version */ const LATEST_VERSION = version; @@ -85,6 +89,7 @@ const sessionMigrators: SessionSaveMigrator[] = []; sessionMigrators.push(...v1_0_4.sessionMigrators); sessionMigrators.push(...v1_7_0.sessionMigrators); sessionMigrators.push(...v1_9_0.sessionMigrators); +sessionMigrators.push(...v1_10_0.sessionMigrators); /** All settings migrators */ const settingsMigrators: SettingsSaveMigrator[] = []; diff --git a/src/system/version_migration/versions/v1_10_0.ts b/src/system/version_migration/versions/v1_10_0.ts new file mode 100644 index 00000000000..4d1dedf701e --- /dev/null +++ b/src/system/version_migration/versions/v1_10_0.ts @@ -0,0 +1,48 @@ +import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator"; +import type { BattlerIndex } from "#enums/battler-index"; +import type { TurnMove } from "#app/field/pokemon"; +import type { MoveResult } from "#enums/move-result"; +import type { SessionSaveData } from "#app/system/game-data"; +import { MoveUseMode } from "#enums/move-use-mode"; +import type { MoveId } from "#enums/move-id"; + +/** Prior signature of `TurnMove`; used to ensure parity */ +interface OldTurnMove { + move: MoveId; + targets: BattlerIndex[]; + result?: MoveResult; + turn?: number; + virtual?: boolean; + ignorePP?: boolean; +} + +/** + * Fix player pokemon move history entries with updated `MoveUseModes`, + * based on the prior values of `virtual` and `ignorePP`. + * Needed to ensure Last Resort and move-calling moves still work OK. + * @param data - {@linkcode SystemSaveData} + */ +const fixMoveHistory: SessionSaveMigrator = { + version: "1.10.0", + migrate: (data: SessionSaveData): void => { + const mapTurnMove = (tm: OldTurnMove): TurnMove => ({ + move: tm.move, + targets: tm.targets, + result: tm.result, + turn: tm.turn, + // NOTE: This unfortuately has to mis-classify Dancer and Magic Bounce-induced moves as `FOLLOW_UP`, + // given we previously had _no way_ of distinguishing them from follow-up moves post hoc. + useMode: tm.virtual ? MoveUseMode.FOLLOW_UP : tm.ignorePP ? MoveUseMode.IGNORE_PP : MoveUseMode.NORMAL, + }); + data.party.forEach(pkmn => { + pkmn.summonData.moveHistory = (pkmn.summonData.moveHistory as OldTurnMove[]).map(mapTurnMove); + pkmn.summonData.moveQueue = (pkmn.summonData.moveQueue as OldTurnMove[]).map(mapTurnMove); + }); + data.enemyParty.forEach(pkmn => { + pkmn.summonData.moveHistory = (pkmn.summonData.moveHistory as OldTurnMove[]).map(mapTurnMove); + pkmn.summonData.moveQueue = (pkmn.summonData.moveQueue as OldTurnMove[]).map(mapTurnMove); + }); + }, +}; + +export const sessionMigrators: Readonly = [fixMoveHistory] as const; diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index f30c7a4935c..14cd10d0d6f 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -16,6 +16,7 @@ import type Pokemon from "#app/field/pokemon"; import type { CommandPhase } from "#app/phases/command-phase"; import MoveInfoOverlay from "./move-info-overlay"; import { BattleType } from "#enums/battle-type"; +import { MoveUseMode } from "#enums/move-use-mode"; export default class FightUiHandler extends UiHandler implements InfoToggle { public static readonly MOVES_CONTAINER_NAME = "moves"; @@ -139,53 +140,63 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { return true; } + /** + * Process the player inputting the selected {@linkcode Button}. + * @param button - The {@linkcode Button} being pressed + * @returns Whether the input was successful (ie did anything). + */ processInput(button: Button): boolean { const ui = this.getUi(); - + const cursor = this.getCursor(); let success = false; - const cursor = this.getCursor(); - - if (button === Button.CANCEL || button === Button.ACTION) { - if (button === Button.ACTION) { + switch (button) { + case Button.CANCEL: + { + // Attempts to back out of the move selection pane are blocked in certain MEs + // TODO: Should we allow showing the summary menu at least? + const { battleType, mysteryEncounter } = globalScene.currentBattle; + if (battleType !== BattleType.MYSTERY_ENCOUNTER || !mysteryEncounter?.skipToFightInput) { + ui.setMode(UiMode.COMMAND, this.fieldIndex); + success = true; + } + } + break; + case Button.ACTION: if ( - (globalScene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(this.fromCommand, cursor, false) + (globalScene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand( + this.fromCommand, + cursor, + MoveUseMode.NORMAL, + ) ) { success = true; } else { ui.playError(); } - } else { - // Cannot back out of fight menu if skipToFightInput is enabled - const { battleType, mysteryEncounter } = globalScene.currentBattle; - if (battleType !== BattleType.MYSTERY_ENCOUNTER || !mysteryEncounter?.skipToFightInput) { - ui.setMode(UiMode.COMMAND, this.fieldIndex); - success = true; + break; + case Button.UP: + if (cursor >= 2) { + success = this.setCursor(cursor - 2); } - } - } else { - switch (button) { - case Button.UP: - if (cursor >= 2) { - success = this.setCursor(cursor - 2); - } - break; - case Button.DOWN: - if (cursor < 2) { - success = this.setCursor(cursor + 2); - } - break; - case Button.LEFT: - if (cursor % 2 === 1) { - success = this.setCursor(cursor - 1); - } - break; - case Button.RIGHT: - if (cursor % 2 === 0) { - success = this.setCursor(cursor + 1); - } - break; - } + break; + case Button.DOWN: + if (cursor < 2) { + success = this.setCursor(cursor + 2); + } + break; + case Button.LEFT: + if (cursor % 2 === 1) { + success = this.setCursor(cursor - 1); + } + break; + case Button.RIGHT: + if (cursor % 2 === 0) { + success = this.setCursor(cursor + 1); + } + break; + default: + // other inputs do nothing while in fight menu } if (success) { diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 8e197c08ef3..cf6333f4580 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -1,4 +1,4 @@ -import type { PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon, TurnMove } from "#app/field/pokemon"; import type { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; import { MoveResult } from "#enums/move-result"; @@ -1167,13 +1167,13 @@ export default class PartyUiHandler extends MessageUiHandler { } // TODO: add FORCED_SWITCH (and perhaps also BATON_PASS_SWITCH) to the modes + // TODO: refactor once moves in flight become a thing... private isBatonPassMove(): boolean { - const moveHistory = globalScene.getPlayerField()[this.fieldIndex].getMoveHistory(); - return !!( + const lastMove: TurnMove | undefined = globalScene.getPlayerField()[this.fieldIndex].getLastXMoves()[0]; + return ( this.partyUiMode === PartyUiMode.FAINT_SWITCH && - moveHistory.length && - allMoves[moveHistory[moveHistory.length - 1].move].getAttrs("ForceSwitchOutAttr")[0]?.isBatonPass() && - moveHistory[moveHistory.length - 1].result === MoveResult.SUCCESS + lastMove?.result === MoveResult.SUCCESS && + allMoves[lastMove.move].getAttrs("ForceSwitchOutAttr")[0]?.isBatonPass() ); } diff --git a/src/utils/common.ts b/src/utils/common.ts index c8b37c4e3fd..e19e5976507 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -620,3 +620,25 @@ export function coerceArray(input: T): T extends any[] ? T : [T]; export function coerceArray(input: T): T | [T] { return Array.isArray(input) ? input : [input]; } + +/** + * Returns the name of the key that matches the enum [object] value. + * @param input - The enum [object] to check + * @param val - The value to get the key of + * @returns The name of the key with the specified value + * @example + * const thing = { + * one: 1, + * two: 2, + * } as const; + * console.log(enumValueToKey(thing, thing.two)); // output: "two" + * @throws An `Error` if an invalid enum value is passed to the function + */ +export function enumValueToKey>(input: T, val: T[keyof T]): keyof T { + for (const [key, value] of Object.entries(input)) { + if (val === value) { + return key as keyof T; + } + } + throw new Error(`Invalid value passed to \`enumValueToKey\`! Value: ${val}`); +} diff --git a/test/abilities/gorilla_tactics.test.ts b/test/abilities/gorilla_tactics.test.ts index 1759267f16d..a8b09461ea0 100644 --- a/test/abilities/gorilla_tactics.test.ts +++ b/test/abilities/gorilla_tactics.test.ts @@ -1,15 +1,19 @@ import { BattlerIndex } from "#enums/battler-index"; +import { RandomMoveAttr } from "#app/data/moves/move"; import { MoveId } from "#enums/move-id"; +import { AbilityId } from "#enums/ability-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#app/enums/stat"; -import { AbilityId } from "#enums/ability-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { MoveResult } from "#enums/move-result"; +import { MoveUseMode } from "#enums/move-use-mode"; describe("Abilities - Gorilla Tactics", () => { let phaserGame: Phaser.Game; let game: GameManager; + beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -25,10 +29,9 @@ describe("Abilities - Gorilla Tactics", () => { game.override .battleStyle("single") .enemyAbility(AbilityId.BALL_FETCH) - .enemyMoveset([MoveId.SPLASH, MoveId.DISABLE]) .enemySpecies(SpeciesId.MAGIKARP) .enemyLevel(30) - .moveset([MoveId.SPLASH, MoveId.TACKLE, MoveId.GROWL]) + .moveset([MoveId.SPLASH, MoveId.TACKLE, MoveId.GROWL, MoveId.METRONOME]) .ability(AbilityId.GORILLA_TACTICS); }); @@ -39,9 +42,8 @@ describe("Abilities - Gorilla Tactics", () => { const initialAtkStat = darmanitan.getStat(Stat.ATK); game.move.select(MoveId.SPLASH); - await game.move.selectEnemyMove(MoveId.SPLASH); - - await game.phaseInterceptor.to("TurnEndPhase"); + await game.move.forceEnemyMove(MoveId.SPLASH); + await game.toEndOfTurn(); expect(darmanitan.getStat(Stat.ATK, false)).toBeCloseTo(initialAtkStat * 1.5); // Other moves should be restricted @@ -52,32 +54,50 @@ describe("Abilities - Gorilla Tactics", () => { it("should struggle if the only usable move is disabled", async () => { await game.classicMode.startBattle([SpeciesId.GALAR_DARMANITAN]); - const darmanitan = game.scene.getPlayerPokemon()!; - const enemy = game.scene.getEnemyPokemon()!; + const darmanitan = game.field.getPlayerPokemon(); + const enemy = game.field.getEnemyPokemon(); // First turn, lock move to Growl game.move.select(MoveId.GROWL); - await game.move.selectEnemyMove(MoveId.SPLASH); - - // Second turn, Growl is interrupted by Disable + await game.move.forceEnemyMove(MoveId.SPLASH); await game.toNextTurn(); + // Second turn, Growl is interrupted by Disable game.move.select(MoveId.GROWL); - await game.move.selectEnemyMove(MoveId.DISABLE); + await game.move.forceEnemyMove(MoveId.DISABLE); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.toNextTurn(); - await game.phaseInterceptor.to("TurnEndPhase"); expect(enemy.getStatStage(Stat.ATK)).toBe(-1); // Only the effect of the first Growl should be applied // Third turn, Struggle is used - await game.toNextTurn(); - game.move.select(MoveId.TACKLE); await game.move.forceEnemyMove(MoveId.SPLASH); //prevent protect from being used by the enemy await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEndPhase"); + expect(darmanitan.hp).toBeLessThan(darmanitan.getMaxHp()); + + await game.toNextTurn(); + expect(darmanitan.getLastXMoves()[0].move).toBe(MoveId.STRUGGLE); + }); + + it("should lock into calling moves, even if also in moveset", async () => { + vi.spyOn(RandomMoveAttr.prototype, "getMoveOverride").mockReturnValue(MoveId.TACKLE); + await game.classicMode.startBattle([SpeciesId.GALAR_DARMANITAN]); + + const darmanitan = game.scene.getPlayerPokemon()!; + + game.move.select(MoveId.METRONOME); + await game.phaseInterceptor.to("TurnEndPhase"); + + // Gorilla Tactics should bypass dancer and instruct + expect(darmanitan.isMoveRestricted(MoveId.TACKLE)).toBe(true); + expect(darmanitan.isMoveRestricted(MoveId.METRONOME)).toBe(false); + expect(darmanitan.getLastXMoves(-1)).toEqual([ + expect.objectContaining({ move: MoveId.TACKLE, result: MoveResult.SUCCESS, useMode: MoveUseMode.FOLLOW_UP }), + expect.objectContaining({ move: MoveId.METRONOME, result: MoveResult.SUCCESS, useMode: MoveUseMode.NORMAL }), + ]); }); it("should activate when the opponenet protects", async () => { diff --git a/test/data/status_effect.test.ts b/test/data/status_effect.test.ts index bc2936d8fe2..a5bfb33c023 100644 --- a/test/data/status_effect.test.ts +++ b/test/data/status_effect.test.ts @@ -18,6 +18,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; const pokemonName = "PKM"; const sourceText = "SOURCE"; +// TODO: Make this a giant it.each describe("Status Effect Messages", () => { describe("NONE", () => { const statusEffect = StatusEffect.NONE; @@ -386,7 +387,7 @@ describe("Status Effects", () => { game.move.select(MoveId.SPLASH); await game.toNextTurn(); - expect(player.status?.effect).toBeUndefined(); + expect(player.status).toBeFalsy(); expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); }); }); diff --git a/test/moves/after_you.test.ts b/test/moves/after_you.test.ts index 78372de3fb6..37186dcc7a5 100644 --- a/test/moves/after_you.test.ts +++ b/test/moves/after_you.test.ts @@ -2,6 +2,7 @@ import { BattlerIndex } from "#enums/battler-index"; import { AbilityId } from "#enums/ability-id"; import { MoveResult } from "#enums/move-result"; import { MovePhase } from "#app/phases/move-phase"; +import { MoveUseMode } from "#enums/move-use-mode"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; @@ -60,4 +61,37 @@ describe("Moves - After You", () => { expect(game.scene.getPlayerField()[1].getLastXMoves(1)[0].result).toBe(MoveResult.FAIL); }); + + // TODO: Enable once rampaging moves and move queue are fixed. + // Currently does literally nothing because `MoveUseMode` is overridden from move queue + // within `MovePhase`, but should be enabled once that jank is removed + it.todo("should maintain PP ignore status of rampaging moves", async () => { + game.override.moveset([]); + await game.classicMode.startBattle([SpeciesId.ACCELGOR, SpeciesId.RATTATA]); + + const [accelgor, rattata] = game.scene.getPlayerField(); + expect(accelgor).toBeDefined(); + expect(rattata).toBeDefined(); + + game.move.changeMoveset(accelgor, [MoveId.SPLASH, MoveId.AFTER_YOU]); + game.move.changeMoveset(rattata, MoveId.OUTRAGE); + + game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER); + game.move.select(MoveId.OUTRAGE, BattlerIndex.PLAYER_2); + await game.phaseInterceptor.to("TurnEndPhase"); + + const outrageMove = rattata.getMoveset().find(m => m.moveId === MoveId.OUTRAGE); + expect(outrageMove?.ppUsed).toBe(1); + + game.move.select(MoveId.AFTER_YOU, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(accelgor.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + expect(outrageMove?.ppUsed).toBe(1); + expect(rattata.getLastXMoves()[0]).toMatchObject({ + move: MoveId.OUTRAGE, + result: MoveResult.SUCCESS, + useMode: MoveUseMode.IGNORE_PP, + }); + }); }); diff --git a/test/moves/copycat.test.ts b/test/moves/copycat.test.ts index 1691cc1478c..e11a2262a43 100644 --- a/test/moves/copycat.test.ts +++ b/test/moves/copycat.test.ts @@ -1,9 +1,9 @@ import { BattlerIndex } from "#enums/battler-index"; -import type { RandomMoveAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/data-lists"; +import { RandomMoveAttr } from "#app/data/moves/move"; import { Stat } from "#app/enums/stat"; import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; +import { MoveUseMode } from "#enums/move-use-mode"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; @@ -14,8 +14,6 @@ describe("Moves - Copycat", () => { let phaserGame: Phaser.Game; let game: GameManager; - let randomMoveAttr: RandomMoveAttr; - beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -27,14 +25,12 @@ describe("Moves - Copycat", () => { }); beforeEach(() => { - randomMoveAttr = allMoves[MoveId.METRONOME].getAttrs("RandomMoveAttr")[0]; game = new GameManager(phaserGame); game.override .moveset([MoveId.COPYCAT, MoveId.SPIKY_SHIELD, MoveId.SWORDS_DANCE, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") .disableCrits() - .starterSpecies(SpeciesId.FEEBAS) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); @@ -42,7 +38,7 @@ describe("Moves - Copycat", () => { it("should copy the last move successfully executed", async () => { game.override.enemyMoveset(MoveId.SUCKER_PUNCH); - await game.classicMode.startBattle(); + await game.classicMode.startBattle([SpeciesId.FEEBAS]); game.move.select(MoveId.SWORDS_DANCE); await game.toNextTurn(); @@ -55,7 +51,7 @@ describe("Moves - Copycat", () => { it("should fail when the last move used is not a valid Copycat move", async () => { game.override.enemyMoveset(MoveId.PROTECT); // Protect is not a valid move for Copycat to copy - await game.classicMode.startBattle(); + await game.classicMode.startBattle([SpeciesId.FEEBAS]); game.move.select(MoveId.SPIKY_SHIELD); // Spiky Shield is not a valid move for Copycat to copy await game.toNextTurn(); @@ -68,19 +64,25 @@ describe("Moves - Copycat", () => { it("should copy the called move when the last move successfully calls another", async () => { game.override.moveset([MoveId.SPLASH, MoveId.METRONOME]).enemyMoveset(MoveId.COPYCAT); - await game.classicMode.startBattle(); - vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(MoveId.SWORDS_DANCE); + await game.classicMode.startBattle([SpeciesId.DRAMPA]); + vi.spyOn(RandomMoveAttr.prototype, "getMoveOverride").mockReturnValue(MoveId.SWORDS_DANCE); game.move.select(MoveId.METRONOME); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); // Player moves first, so enemy can copy Swords Dance + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); // Player moves first so enemy can copy Swords Dance await game.toNextTurn(); - expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.ATK)).toBe(2); + const enemy = game.scene.getEnemyPokemon()!; + expect(enemy.getLastXMoves()[0]).toMatchObject({ + move: MoveId.SWORDS_DANCE, + result: MoveResult.SUCCESS, + useMode: MoveUseMode.FOLLOW_UP, + }); + expect(enemy.getStatStage(Stat.ATK)).toBe(2); }); - it("should apply secondary effects of a move", async () => { + it("should apply move secondary effects", async () => { game.override.enemyMoveset(MoveId.ACID_SPRAY); // Secondary effect lowers SpDef by 2 stages - await game.classicMode.startBattle(); + await game.classicMode.startBattle([SpeciesId.FEEBAS]); game.move.select(MoveId.COPYCAT); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); diff --git a/test/moves/dig.test.ts b/test/moves/dig.test.ts index 73540c6ed48..a80ac0405fe 100644 --- a/test/moves/dig.test.ts +++ b/test/moves/dig.test.ts @@ -42,8 +42,8 @@ describe("Moves - Dig", () => { const enemyPokemon = game.scene.getEnemyPokemon()!; game.move.select(MoveId.DIG); - await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.getTag(BattlerTagType.UNDERGROUND)).toBeDefined(); expect(enemyPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.MISS); expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp()); @@ -53,9 +53,25 @@ describe("Moves - Dig", () => { await game.phaseInterceptor.to("TurnEndPhase"); expect(playerPokemon.getTag(BattlerTagType.UNDERGROUND)).toBeUndefined(); expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); + expect(playerPokemon.getMoveQueue()).toHaveLength(0); expect(playerPokemon.getMoveHistory()).toHaveLength(2); + }); - const playerDig = playerPokemon.getMoveset().find(mv => mv && mv.moveId === MoveId.DIG); + // TODO: Verify this on cartridge double battles + it.todo("should deduct PP only on the 2nd turn of the move", async () => { + game.override.moveset([]); + await game.classicMode.startBattle([SpeciesId.MAGIKARP]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.changeMoveset(playerPokemon, MoveId.DIG); + + game.move.select(MoveId.DIG); + await game.phaseInterceptor.to("TurnEndPhase"); + + const playerDig = playerPokemon.getMoveset().find(mv => mv?.moveId === MoveId.DIG); + expect(playerDig?.ppUsed).toBe(0); + + await game.phaseInterceptor.to("TurnEndPhase"); expect(playerDig?.ppUsed).toBe(1); }); diff --git a/test/moves/disable.test.ts b/test/moves/disable.test.ts index 127a5eaaa0c..b113acb9525 100644 --- a/test/moves/disable.test.ts +++ b/test/moves/disable.test.ts @@ -1,10 +1,13 @@ import { BattlerIndex } from "#enums/battler-index"; import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; +import { MoveUseMode } from "#enums/move-use-mode"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; +import { Stat } from "#enums/stat"; import GameManager from "#test/testUtils/gameManager"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { RandomMoveAttr } from "#app/data/moves/move"; describe("Moves - Disable", () => { let phaserGame: Phaser.Game; @@ -28,34 +31,39 @@ describe("Moves - Disable", () => { .enemyAbility(AbilityId.BALL_FETCH) .moveset([MoveId.DISABLE, MoveId.SPLASH]) .enemyMoveset(MoveId.SPLASH) - .starterSpecies(SpeciesId.PIKACHU) .enemySpecies(SpeciesId.SHUCKLE); }); - it("restricts moves", async () => { - await game.classicMode.startBattle(); + it("should restrict the last move used", async () => { + await game.classicMode.startBattle([SpeciesId.PIKACHU]); - const enemyMon = game.scene.getEnemyPokemon()!; + const enemyMon = game.field.getEnemyPokemon(); + + game.move.select(MoveId.SPLASH); + await game.move.forceEnemyMove(MoveId.GROWL); + await game.toNextTurn(); game.move.select(MoveId.DISABLE); + await game.move.forceEnemyMove(MoveId.SPLASH); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextTurn(); - expect(enemyMon.getMoveHistory()).toHaveLength(1); + expect(enemyMon.getLastXMoves(-1)).toHaveLength(2); expect(enemyMon.isMoveRestricted(MoveId.SPLASH)).toBe(true); + expect(enemyMon.isMoveRestricted(MoveId.GROWL)).toBe(false); }); - it("fails if enemy has no move history", async () => { - await game.classicMode.startBattle(); + it("should fail if enemy has no move history", async () => { + await game.classicMode.startBattle([SpeciesId.PIKACHU]); - const playerMon = game.scene.getPlayerPokemon()!; - const enemyMon = game.scene.getEnemyPokemon()!; + const playerMon = game.field.getPlayerPokemon(); + const enemyMon = game.field.getEnemyPokemon(); game.move.select(MoveId.DISABLE); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.toNextTurn(); - expect(playerMon.getMoveHistory()[0]).toMatchObject({ + expect(playerMon.getLastXMoves()[0]).toMatchObject({ move: MoveId.DISABLE, result: MoveResult.FAIL, }); @@ -63,9 +71,9 @@ describe("Moves - Disable", () => { }); it("causes STRUGGLE if all usable moves are disabled", async () => { - await game.classicMode.startBattle(); + await game.classicMode.startBattle([SpeciesId.PIKACHU]); - const enemyMon = game.scene.getEnemyPokemon()!; + const enemyMon = game.field.getEnemyPokemon(); game.move.select(MoveId.DISABLE); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); @@ -74,20 +82,19 @@ describe("Moves - Disable", () => { game.move.select(MoveId.SPLASH); await game.toNextTurn(); - const enemyHistory = enemyMon.getMoveHistory(); + const enemyHistory = enemyMon.getLastXMoves(-1); expect(enemyHistory).toHaveLength(2); - expect(enemyHistory[0].move).toBe(MoveId.SPLASH); - expect(enemyHistory[1].move).toBe(MoveId.STRUGGLE); + expect(enemyHistory.map(m => m.move)).toEqual([MoveId.STRUGGLE, MoveId.SPLASH]); }); - it("cannot disable STRUGGLE", async () => { - game.override.enemyMoveset([MoveId.STRUGGLE]); - await game.classicMode.startBattle(); + it("should fail if it would otherwise disable struggle", async () => { + await game.classicMode.startBattle([SpeciesId.PIKACHU]); - const playerMon = game.scene.getPlayerPokemon()!; - const enemyMon = game.scene.getEnemyPokemon()!; + const playerMon = game.field.getPlayerPokemon(); + const enemyMon = game.field.getEnemyPokemon(); game.move.select(MoveId.DISABLE); + await game.move.forceEnemyMove(MoveId.STRUGGLE); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextTurn(); @@ -96,59 +103,77 @@ describe("Moves - Disable", () => { expect(enemyMon.isMoveRestricted(MoveId.STRUGGLE)).toBe(false); }); - it("interrupts target's move when target moves after", async () => { - await game.classicMode.startBattle(); + it("should interrupt target's move if used first", async () => { + await game.classicMode.startBattle([SpeciesId.PIKACHU]); - const enemyMon = game.scene.getEnemyPokemon()!; + const enemyMon = game.field.getEnemyPokemon(); + // add splash to enemy move history + enemyMon.pushMoveHistory({ + move: MoveId.SPLASH, + targets: [BattlerIndex.ENEMY], + useMode: MoveUseMode.NORMAL, + }); - game.move.select(MoveId.SPLASH); - await game.toNextTurn(); - - // Both mons just used Splash last turn; now have player use Disable. game.move.select(MoveId.DISABLE); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.toNextTurn(); - const enemyHistory = enemyMon.getMoveHistory(); + const enemyHistory = enemyMon.getLastXMoves(-1); expect(enemyHistory).toHaveLength(2); - expect(enemyHistory[0]).toMatchObject({ - move: MoveId.SPLASH, - result: MoveResult.SUCCESS, - }); - expect(enemyHistory[1].result).toBe(MoveResult.FAIL); + expect(enemyHistory[0].result).toBe(MoveResult.FAIL); }); - it("disables NATURE POWER, not the move invoked by it", async () => { - game.override.enemyMoveset([MoveId.NATURE_POWER]); - await game.classicMode.startBattle(); + it.each([ + { name: "Nature Power", moveId: MoveId.NATURE_POWER }, + { name: "Mirror Move", moveId: MoveId.MIRROR_MOVE }, + { name: "Copycat", moveId: MoveId.COPYCAT }, + { name: "Metronome", moveId: MoveId.METRONOME }, + ])("should ignore virtual moves called by $name", async ({ moveId }) => { + vi.spyOn(RandomMoveAttr.prototype, "getMoveOverride").mockReturnValue(MoveId.ABSORB); + await game.classicMode.startBattle([SpeciesId.PIKACHU]); - const enemyMon = game.scene.getEnemyPokemon()!; + const playerMon = game.scene.getPlayerPokemon()!; + playerMon.pushMoveHistory({ move: MoveId.SPLASH, targets: [BattlerIndex.ENEMY], useMode: MoveUseMode.NORMAL }); + game.scene.currentBattle.lastMove = MoveId.SPLASH; game.move.select(MoveId.DISABLE); + await game.move.forceEnemyMove(moveId); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextTurn(); - expect(enemyMon.isMoveRestricted(MoveId.NATURE_POWER)).toBe(true); - expect(enemyMon.isMoveRestricted(enemyMon.getLastXMoves(2)[0].move)).toBe(false); + const enemyMon = game.scene.getEnemyPokemon()!; + expect(enemyMon.isMoveRestricted(moveId), `calling move ${MoveId[moveId]} was not disabled`).toBe(true); + expect(enemyMon.getLastXMoves(-1)).toHaveLength(2); + const calledMove = enemyMon.getLastXMoves()[0].move; + expect( + enemyMon.isMoveRestricted(calledMove), + `called move ${MoveId[calledMove]} (from ${MoveId[moveId]}) was incorrectly disabled`, + ).toBe(false); }); - it("disables most recent move", async () => { - game.override.enemyMoveset([MoveId.SPLASH, MoveId.TACKLE]); - await game.classicMode.startBattle(); + it("should ignore dancer copied moves, even if also in moveset", async () => { + game.override + .enemyAbility(AbilityId.DANCER) + .moveset([MoveId.DISABLE, MoveId.SWORDS_DANCE]) + .enemyMoveset([MoveId.SPLASH, MoveId.SWORDS_DANCE]); + await game.classicMode.startBattle([SpeciesId.PIKACHU]); - const enemyMon = game.scene.getEnemyPokemon()!; - - game.move.select(MoveId.SPLASH); - await game.move.selectEnemyMove(MoveId.SPLASH, BattlerIndex.PLAYER); + game.move.select(MoveId.SWORDS_DANCE); + await game.move.selectEnemyMove(MoveId.SPLASH); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextTurn(); game.move.select(MoveId.DISABLE); - await game.move.selectEnemyMove(MoveId.TACKLE, BattlerIndex.PLAYER); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.move.selectEnemyMove(MoveId.SWORDS_DANCE); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.toNextTurn(); - expect(enemyMon.isMoveRestricted(MoveId.TACKLE)).toBe(true); - expect(enemyMon.isMoveRestricted(MoveId.SPLASH)).toBe(false); + // Dancer-induced Swords Dance was ignored in favor of splash, + // leaving the subsequent _normal_ swords dance free to work as normal + const shuckle = game.field.getEnemyPokemon(); + expect.soft(shuckle.isMoveRestricted(MoveId.SPLASH)).toBe(true); + expect.soft(shuckle.isMoveRestricted(MoveId.SWORDS_DANCE)).toBe(false); + expect(shuckle.getLastXMoves()[0]).toMatchObject({ move: MoveId.SWORDS_DANCE, result: MoveResult.SUCCESS }); + expect(shuckle.getStatStage(Stat.ATK)).toBe(4); }); }); diff --git a/test/moves/electro_shot.test.ts b/test/moves/electro_shot.test.ts index 3f751687c59..1d69e9c2fa7 100644 --- a/test/moves/electro_shot.test.ts +++ b/test/moves/electro_shot.test.ts @@ -80,7 +80,7 @@ describe("Moves - Electro Shot", () => { expect(playerPokemon.getTag(BattlerTagType.CHARGING)).toBeUndefined(); expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); expect(playerPokemon.getMoveHistory()).toHaveLength(2); - expect(playerPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); const playerElectroShot = playerPokemon.getMoveset().find(mv => mv && mv.moveId === MoveId.ELECTRO_SHOT); expect(playerElectroShot?.ppUsed).toBe(1); diff --git a/test/moves/instruct.test.ts b/test/moves/instruct.test.ts index 74de4c5da17..6167b6fc4dd 100644 --- a/test/moves/instruct.test.ts +++ b/test/moves/instruct.test.ts @@ -1,9 +1,11 @@ import { BattlerIndex } from "#enums/battler-index"; +import { RandomMoveAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import type Pokemon from "#app/field/pokemon"; import { MoveResult } from "#enums/move-result"; import type { MovePhase } from "#app/phases/move-phase"; import { AbilityId } from "#enums/ability-id"; +import { MoveUseMode } from "#enums/move-use-mode"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; @@ -141,6 +143,21 @@ describe("Moves - Instruct", () => { expect(game.scene.getPlayerPokemon()!.turnData.attacksReceived.length).toBe(3); }); + it("should fail on metronomed moves, even if also in moveset", async () => { + vi.spyOn(RandomMoveAttr.prototype, "getMoveOverride").mockReturnValue(MoveId.ABSORB); + await game.classicMode.startBattle([SpeciesId.AMOONGUSS]); + + const enemy = game.field.getEnemyPokemon(); + game.move.changeMoveset(enemy, [MoveId.METRONOME, MoveId.ABSORB]); + + game.move.use(MoveId.INSTRUCT); + await game.move.selectEnemyMove(MoveId.METRONOME); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.toEndOfTurn(); + + expect(game.field.getPlayerPokemon().getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); + it("should respect enemy's status condition", async () => { game.override.moveset([MoveId.INSTRUCT, MoveId.THUNDER_WAVE]).enemyMoveset(MoveId.SONIC_BOOM); await game.classicMode.startBattle([SpeciesId.AMOONGUSS]); @@ -152,7 +169,7 @@ describe("Moves - Instruct", () => { game.move.select(MoveId.INSTRUCT); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.phaseInterceptor.to("MovePhase"); - // force enemy's instructed move to bork and then immediately thaw out + // force enemy's instructed move (and only the instructed move) to fail await game.move.forceStatusActivation(true); await game.move.forceStatusActivation(false); await game.phaseInterceptor.to("TurnEndPhase", false); @@ -176,7 +193,7 @@ describe("Moves - Instruct", () => { await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("TurnEndPhase", false); - const playerMoves = game.scene.getPlayerPokemon()!.getLastXMoves(-1)!; + const playerMoves = game.field.getPlayerPokemon().getLastXMoves(-1); expect(playerMoves[0].result).toBe(MoveResult.FAIL); expect(enemyPokemon.getMoveHistory().length).toBe(1); }); @@ -203,6 +220,7 @@ describe("Moves - Instruct", () => { expect(karp1.isFainted()).toBe(true); expect(karp2.isFainted()).toBe(true); }); + it("should allow for dancer copying of instructed dance move", async () => { game.override.battleStyle("double").enemyMoveset([MoveId.INSTRUCT, MoveId.SPLASH]).enemyLevel(1000); await game.classicMode.startBattle([SpeciesId.ORICORIO, SpeciesId.VOLCARONA]); @@ -231,19 +249,18 @@ describe("Moves - Instruct", () => { const amoonguss = game.scene.getPlayerPokemon()!; game.move.changeMoveset(amoonguss, MoveId.SEED_BOMB); - amoonguss.summonData.moveHistory = [ - { - move: MoveId.SEED_BOMB, - targets: [BattlerIndex.ENEMY], - result: MoveResult.SUCCESS, - }, - ]; + amoonguss.pushMoveHistory({ + move: MoveId.SEED_BOMB, + targets: [BattlerIndex.ENEMY], + result: MoveResult.SUCCESS, + useMode: MoveUseMode.NORMAL, + }); game.doSwitchPokemon(1); await game.phaseInterceptor.to("TurnEndPhase", false); - const enemyMoves = game.scene.getEnemyPokemon()!.getLastXMoves(-1)!; - expect(enemyMoves[0].result).toBe(MoveResult.FAIL); + const enemyMoves = game.field.getEnemyPokemon().getLastXMoves(-1); + expect(enemyMoves[0]?.result).toBe(MoveResult.FAIL); }); it("should fail if no move has yet been used by target", async () => { @@ -272,15 +289,9 @@ describe("Moves - Instruct", () => { await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2]); await game.phaseInterceptor.to("TurnEndPhase", false); - expect(game.scene.getPlayerField()[0].getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); - const enemyMove = game.scene.getEnemyField()[0]!.getLastXMoves()[0]; - expect(enemyMove.result).toBe(MoveResult.FAIL); - expect( - game.scene - .getEnemyField()[0] - .getMoveset() - .find(m => m?.moveId === MoveId.SONIC_BOOM)?.ppUsed, - ).toBe(1); + expect(game.field.getPlayerPokemon().getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + expect(enemy1.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(enemy1.getMoveset().find(m => m.moveId === MoveId.SONIC_BOOM)?.ppUsed).toBe(1); }); it("should not repeat enemy's move through protect", async () => { @@ -304,14 +315,12 @@ describe("Moves - Instruct", () => { const player = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; - enemy.summonData.moveHistory = [ - { - move: MoveId.SONIC_BOOM, - targets: [BattlerIndex.PLAYER], - result: MoveResult.SUCCESS, - virtual: false, - }, - ]; + enemy.pushMoveHistory({ + move: MoveId.SONIC_BOOM, + targets: [BattlerIndex.PLAYER], + result: MoveResult.SUCCESS, + useMode: MoveUseMode.NORMAL, + }); game.move.select(MoveId.INSTRUCT); await game.move.selectEnemyMove(MoveId.HYPER_BEAM); @@ -337,7 +346,7 @@ describe("Moves - Instruct", () => { move: MoveId.ELECTRO_DRIFT, targets: [BattlerIndex.PLAYER], result: MoveResult.SUCCESS, - virtual: false, + useMode: MoveUseMode.NORMAL, }); game.move.select(MoveId.SPLASH); @@ -351,14 +360,12 @@ describe("Moves - Instruct", () => { await game.classicMode.startBattle([SpeciesId.LUCARIO, SpeciesId.BANETTE]); const enemyPokemon = game.scene.getEnemyPokemon()!; - enemyPokemon.summonData.moveHistory = [ - { - move: MoveId.WHIRLWIND, - targets: [BattlerIndex.PLAYER], - result: MoveResult.SUCCESS, - virtual: false, - }, - ]; + enemyPokemon.pushMoveHistory({ + move: MoveId.WHIRLWIND, + targets: [BattlerIndex.PLAYER], + result: MoveResult.SUCCESS, + useMode: MoveUseMode.NORMAL, + }); game.move.select(MoveId.INSTRUCT); await game.move.selectEnemyMove(MoveId.SPLASH); @@ -378,11 +385,20 @@ describe("Moves - Instruct", () => { .enemyMoveset([MoveId.SPLASH, MoveId.PSYCHIC_TERRAIN]); await game.classicMode.startBattle([SpeciesId.BANETTE, SpeciesId.KLEFKI]); - game.move.select(MoveId.QUICK_ATTACK, BattlerIndex.PLAYER, BattlerIndex.ENEMY); // succeeds due to terrain no + const banette = game.field.getPlayerPokemon(); + + game.move.select(MoveId.QUICK_ATTACK, BattlerIndex.PLAYER, BattlerIndex.ENEMY); game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2); await game.move.selectEnemyMove(MoveId.SPLASH); await game.move.selectEnemyMove(MoveId.PSYCHIC_TERRAIN); await game.toNextTurn(); + expect(banette.getLastXMoves(-1)[0]).toEqual( + expect.objectContaining({ + move: MoveId.QUICK_ATTACK, + targets: [BattlerIndex.ENEMY], + result: MoveResult.SUCCESS, + }), + ); game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER); game.move.select(MoveId.INSTRUCT, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER); @@ -390,32 +406,74 @@ describe("Moves - Instruct", () => { await game.phaseInterceptor.to("TurnEndPhase", false); // quick attack failed when instructed - const banette = game.scene.getPlayerPokemon()!; expect(banette.getLastXMoves(-1)[1].move).toBe(MoveId.QUICK_ATTACK); expect(banette.getLastXMoves(-1)[1].result).toBe(MoveResult.FAIL); }); - it("should still work w/ prankster in psychic terrain", async () => { - game.override.battleStyle("double").enemyMoveset([MoveId.SPLASH, MoveId.PSYCHIC_TERRAIN]); + // TODO: Enable once Sky Drop is fully implemented + it.todo("should not work against Sky Dropped targets, even if user/target have No Guard", async () => { + game.override.battleStyle("double").ability(AbilityId.NO_GUARD); await game.classicMode.startBattle([SpeciesId.BANETTE, SpeciesId.KLEFKI]); - const [banette, klefki] = game.scene.getPlayerField()!; - game.move.changeMoveset(banette, [MoveId.VINE_WHIP, MoveId.SPLASH]); - game.move.changeMoveset(klefki, [MoveId.INSTRUCT, MoveId.SPLASH]); + const [banette, klefki] = game.scene.getPlayerField(); + banette.pushMoveHistory({ + move: MoveId.VINE_WHIP, + targets: [BattlerIndex.ENEMY], + result: MoveResult.SUCCESS, + useMode: MoveUseMode.NORMAL, + }); - game.move.select(MoveId.VINE_WHIP, BattlerIndex.PLAYER, BattlerIndex.ENEMY); - game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2); - await game.move.selectEnemyMove(MoveId.SPLASH); - await game.move.selectEnemyMove(MoveId.PSYCHIC_TERRAIN); - await game.toNextTurn(); + // Attempt to instruct banette after having been sent airborne + game.move.use(MoveId.VINE_WHIP, BattlerIndex.PLAYER, BattlerIndex.ENEMY); + game.move.use(MoveId.INSTRUCT, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER); + await game.move.forceEnemyMove(MoveId.SKY_DROP, BattlerIndex.PLAYER); + await game.move.forceEnemyMove(MoveId.ASTONISH, BattlerIndex.PLAYER); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2]); + await game.phaseInterceptor.to("TurnEndPhase", false); - game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER); + // Klefki instruct fails due to banette being airborne, even though it got hit prior + expect(banette.visible).toBe(false); + expect(banette.isFullHp()).toBe(false); + expect(klefki.getLastXMoves()[0]).toMatchObject({ + move: MoveId.INSTRUCT, + targets: [BattlerIndex.PLAYER], + result: MoveResult.FAIL, + }); + }); + + it("should still work with prankster in psychic terrain", async () => { + game.override + .battleStyle("double") + .ability(AbilityId.PRANKSTER) + .enemyMoveset(MoveId.SPLASH) + .enemyAbility(AbilityId.PSYCHIC_SURGE); + await game.classicMode.startBattle([SpeciesId.BANETTE, SpeciesId.KLEFKI]); + + const [banette, klefki] = game.scene.getPlayerField(); + game.move.changeMoveset(banette, [MoveId.VINE_WHIP]); + game.move.changeMoveset(klefki, MoveId.INSTRUCT); + banette.pushMoveHistory({ + move: MoveId.VINE_WHIP, + targets: [BattlerIndex.ENEMY], + result: MoveResult.SUCCESS, + useMode: MoveUseMode.NORMAL, + }); + + game.move.select(MoveId.VINE_WHIP, BattlerIndex.PLAYER); game.move.select(MoveId.INSTRUCT, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER); // copies vine whip await game.setTurnOrder([BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); await game.phaseInterceptor.to("TurnEndPhase", false); + + // Klefki instructing a non-priority move succeeds, ignoring the priority of Instruct itself expect(banette.getLastXMoves(-1)[1].move).toBe(MoveId.VINE_WHIP); expect(banette.getLastXMoves(-1)[2].move).toBe(MoveId.VINE_WHIP); - expect(banette.getMoveset().find(m => m?.moveId === MoveId.VINE_WHIP)?.ppUsed).toBe(2); + expect(klefki.getLastXMoves(-1)[0]).toEqual( + expect.objectContaining({ + move: MoveId.INSTRUCT, + targets: [BattlerIndex.PLAYER], + result: MoveResult.SUCCESS, + }), + ); }); it("should cause spread moves to correctly hit targets in doubles after singles", async () => { @@ -424,14 +482,15 @@ describe("Moves - Instruct", () => { .moveset([MoveId.BREAKING_SWIPE, MoveId.INSTRUCT, MoveId.SPLASH]) .enemyMoveset(MoveId.SONIC_BOOM) .enemySpecies(SpeciesId.AXEW) - .startingLevel(500); + .startingLevel(500) + .enemyLevel(1); await game.classicMode.startBattle([SpeciesId.KORAIDON, SpeciesId.KLEFKI]); const koraidon = game.scene.getPlayerField()[0]!; game.move.select(MoveId.BREAKING_SWIPE); await game.phaseInterceptor.to("TurnEndPhase", false); - expect(koraidon.getInverseHp()).toBe(0); + expect(koraidon.hp).toBe(koraidon.getMaxHp()); expect(koraidon.getLastXMoves(-1)[0].targets).toEqual([BattlerIndex.ENEMY]); await game.toNextWave(); @@ -439,9 +498,10 @@ describe("Moves - Instruct", () => { game.move.select(MoveId.INSTRUCT, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER); await game.setTurnOrder([BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); await game.phaseInterceptor.to("TurnEndPhase", false); + // did not take damage since enemies died beforehand; // last move used hit both enemies - expect(koraidon.getInverseHp()).toBe(0); + expect(koraidon.hp).toBe(koraidon.getMaxHp()); expect(koraidon.getLastXMoves(-1)[1].targets?.sort()).toEqual([BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); }); @@ -451,7 +511,8 @@ describe("Moves - Instruct", () => { .moveset([MoveId.BRUTAL_SWING, MoveId.INSTRUCT, MoveId.SPLASH]) .enemySpecies(SpeciesId.AXEW) .enemyMoveset(MoveId.SONIC_BOOM) - .startingLevel(500); + .startingLevel(500) + .enemyLevel(1); await game.classicMode.startBattle([SpeciesId.KORAIDON, SpeciesId.KLEFKI]); const koraidon = game.scene.getPlayerField()[0]!; @@ -459,22 +520,24 @@ describe("Moves - Instruct", () => { game.move.select(MoveId.BRUTAL_SWING); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.phaseInterceptor.to("TurnEndPhase", false); - expect(koraidon.getInverseHp()).toBe(0); + + expect(koraidon.hp).toBe(koraidon.getMaxHp()); expect(koraidon.getLastXMoves(-1)[0].targets).toEqual([BattlerIndex.ENEMY]); + await game.toNextWave(); game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER); game.move.select(MoveId.INSTRUCT, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER); await game.setTurnOrder([BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); await game.phaseInterceptor.to("TurnEndPhase", false); + // did not take damage since enemies died beforehand; // last move used hit everything around it - expect(koraidon.getInverseHp()).toBe(0); - expect(koraidon.getLastXMoves(-1)[1].targets?.sort()).toEqual([ - BattlerIndex.PLAYER_2, - BattlerIndex.ENEMY, - BattlerIndex.ENEMY_2, - ]); + expect(koraidon.hp).toBe(koraidon.getMaxHp()); + expect(koraidon.getLastXMoves(-1)[1].targets).toHaveLength(3); + expect(koraidon.getLastXMoves(-1)[1].targets).toEqual( + expect.arrayContaining([BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]), + ); }); it("should cause multi-hit moves to hit the appropriate number of times in singles", async () => { @@ -547,7 +610,12 @@ describe("Moves - Instruct", () => { // Fake enemy 1 having attacked prior const [, player2, enemy1, enemy2] = game.scene.getField(); - enemy1.pushMoveHistory({ move: MoveId.ABSORB, targets: [BattlerIndex.PLAYER] }); + enemy1.pushMoveHistory({ + move: MoveId.ABSORB, + targets: [BattlerIndex.PLAYER], + result: MoveResult.SUCCESS, + useMode: MoveUseMode.NORMAL, + }); game.field.mockAbility(enemy1, AbilityId.STEADFAST); game.move.use(MoveId.AIR_SLASH, BattlerIndex.PLAYER, BattlerIndex.ENEMY); diff --git a/test/moves/last-resort.test.ts b/test/moves/last-resort.test.ts index e4b8346053b..3b57bc5cf61 100644 --- a/test/moves/last-resort.test.ts +++ b/test/moves/last-resort.test.ts @@ -1,6 +1,7 @@ import { BattlerIndex } from "#enums/battler-index"; import { MoveResult } from "#enums/move-result"; import { AbilityId } from "#enums/ability-id"; +import { MoveUseMode } from "#enums/move-use-mode"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; @@ -53,19 +54,19 @@ describe("Moves - Last Resort", () => { expectLastResortFail(); // Splash (1/3) - blissey.pushMoveHistory({ move: MoveId.SPLASH, targets: [BattlerIndex.PLAYER] }); + blissey.pushMoveHistory({ move: MoveId.SPLASH, targets: [BattlerIndex.PLAYER], useMode: MoveUseMode.NORMAL }); game.move.select(MoveId.LAST_RESORT); await game.phaseInterceptor.to("TurnEndPhase"); expectLastResortFail(); // Growl (2/3) - blissey.pushMoveHistory({ move: MoveId.GROWL, targets: [BattlerIndex.ENEMY] }); + blissey.pushMoveHistory({ move: MoveId.GROWL, targets: [BattlerIndex.ENEMY], useMode: MoveUseMode.NORMAL }); game.move.select(MoveId.LAST_RESORT); await game.phaseInterceptor.to("TurnEndPhase"); expectLastResortFail(); // Were last resort itself counted, it would error here // Growth (3/3) - blissey.pushMoveHistory({ move: MoveId.GROWTH, targets: [BattlerIndex.PLAYER] }); + blissey.pushMoveHistory({ move: MoveId.GROWTH, targets: [BattlerIndex.PLAYER], useMode: MoveUseMode.NORMAL }); game.move.select(MoveId.LAST_RESORT); await game.phaseInterceptor.to("TurnEndPhase"); expect(game.scene.getPlayerPokemon()?.getLastXMoves()[0]).toEqual( @@ -117,11 +118,12 @@ describe("Moves - Last Resort", () => { expect.objectContaining({ move: MoveId.LAST_RESORT, result: MoveResult.SUCCESS, - virtual: true, + useMode: MoveUseMode.FOLLOW_UP, }), expect.objectContaining({ move: MoveId.SLEEP_TALK, result: MoveResult.SUCCESS, + useMode: MoveUseMode.NORMAL, }), ]); }); diff --git a/test/moves/metronome.test.ts b/test/moves/metronome.test.ts index ec610eeed45..670f7bb9b88 100644 --- a/test/moves/metronome.test.ts +++ b/test/moves/metronome.test.ts @@ -1,9 +1,13 @@ +import { BattlerIndex } from "#enums/battler-index"; import { RechargingTag, SemiInvulnerableTag } from "#app/data/battler-tags"; import type { RandomMoveAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { Stat } from "#app/enums/stat"; +import { MoveResult } from "#enums/move-result"; import { CommandPhase } from "#app/phases/command-phase"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { MoveUseMode } from "#enums/move-use-mode"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; @@ -33,7 +37,6 @@ describe("Moves - Metronome", () => { .moveset([MoveId.METRONOME, MoveId.SPLASH]) .battleStyle("single") .startingLevel(100) - .starterSpecies(SpeciesId.REGIELEKI) .enemyLevel(100) .enemySpecies(SpeciesId.SHUCKLE) .enemyMoveset(MoveId.SPLASH) @@ -41,7 +44,7 @@ describe("Moves - Metronome", () => { }); it("should have one semi-invulnerable turn and deal damage on the second turn when a semi-invulnerable move is called", async () => { - await game.classicMode.startBattle(); + await game.classicMode.startBattle([SpeciesId.REGIELEKI]); const player = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(MoveId.DIVE); @@ -57,7 +60,7 @@ describe("Moves - Metronome", () => { }); it("should apply secondary effects of a move", async () => { - await game.classicMode.startBattle(); + await game.classicMode.startBattle([SpeciesId.REGIELEKI]); const player = game.scene.getPlayerPokemon()!; vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(MoveId.WOOD_HAMMER); @@ -68,7 +71,7 @@ describe("Moves - Metronome", () => { }); it("should recharge after using recharge move", async () => { - await game.classicMode.startBattle(); + await game.classicMode.startBattle([SpeciesId.REGIELEKI]); const player = game.scene.getPlayerPokemon()!; vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(MoveId.HYPER_BEAM); vi.spyOn(allMoves[MoveId.HYPER_BEAM], "accuracy", "get").mockReturnValue(100); @@ -79,6 +82,41 @@ describe("Moves - Metronome", () => { expect(player.getTag(RechargingTag)).toBeTruthy(); }); + it("should charge for charging moves while still maintaining follow-up status", async () => { + game.override.moveset([]).enemyMoveset(MoveId.SPITE); + vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(MoveId.SOLAR_BEAM); + await game.classicMode.startBattle([SpeciesId.REGIELEKI]); + + const player = game.field.getPlayerPokemon(); + game.move.changeMoveset(player, [MoveId.METRONOME, MoveId.SOLAR_BEAM]); + + const [metronomeMove, solarBeamMove] = player.getMoveset(); + expect(metronomeMove).toBeDefined(); + expect(solarBeamMove).toBeDefined(); + + game.move.select(MoveId.METRONOME); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(player.getTag(BattlerTagType.CHARGING)).toBeTruthy(); + const turn1PpUsed = metronomeMove.ppUsed; + expect.soft(turn1PpUsed).toBeGreaterThan(1); + expect(solarBeamMove.ppUsed).toBe(0); + + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.toNextTurn(); + + expect(player.getTag(BattlerTagType.CHARGING)).toBeFalsy(); + const turn2PpUsed = metronomeMove.ppUsed - turn1PpUsed; + expect(turn2PpUsed).toBeGreaterThan(1); + expect(solarBeamMove.ppUsed).toBe(0); + expect(player.getLastXMoves()[0]).toMatchObject({ + move: MoveId.SOLAR_BEAM, + result: MoveResult.SUCCESS, + useMode: MoveUseMode.FOLLOW_UP, + }); + }); + it("should only target ally for Aromatic Mist", async () => { game.override.battleStyle("double"); await game.classicMode.startBattle([SpeciesId.REGIELEKI, SpeciesId.RATTATA]); @@ -98,7 +136,7 @@ describe("Moves - Metronome", () => { }); it("should cause opponent to flee, and not crash for Roar", async () => { - await game.classicMode.startBattle(); + await game.classicMode.startBattle([SpeciesId.REGIELEKI]); vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(MoveId.ROAR); const enemyPokemon = game.scene.getEnemyPokemon()!; diff --git a/test/moves/powder.test.ts b/test/moves/powder.test.ts index 889e660f167..8f47f2ff428 100644 --- a/test/moves/powder.test.ts +++ b/test/moves/powder.test.ts @@ -1,12 +1,10 @@ import { BattlerIndex } from "#enums/battler-index"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; import { MoveResult } from "#enums/move-result"; import { BerryPhase } from "#app/phases/berry-phase"; -import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; -import { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; +import { PokemonType } from "#enums/pokemon-type"; import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; @@ -44,14 +42,14 @@ describe("Moves - Powder", () => { await game.classicMode.startBattle([SpeciesId.CHARIZARD]); const enemyPokemon = game.scene.getEnemyPokemon()!; - enemyPokemon.moveset = [new PokemonMove(MoveId.EMBER)]; + game.move.changeMoveset(enemyPokemon, MoveId.EMBER); game.move.select(MoveId.POWDER); await game.phaseInterceptor.to(BerryPhase, false); expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); expect(enemyPokemon.hp).toBe(Math.ceil((3 * enemyPokemon.getMaxHp()) / 4)); - expect(enemyPokemon.moveset[0]!.ppUsed).toBe(1); + expect(enemyPokemon.moveset[0].ppUsed).toBe(1); await game.toNextTurn(); @@ -60,7 +58,7 @@ describe("Moves - Powder", () => { await game.phaseInterceptor.to(BerryPhase, false); expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); expect(enemyPokemon.hp).toBe(Math.ceil((3 * enemyPokemon.getMaxHp()) / 4)); - expect(enemyPokemon.moveset[0]!.ppUsed).toBe(2); + expect(enemyPokemon.moveset[0].ppUsed).toBe(2); }); it("should have no effect against Grass-type Pokemon", async () => { @@ -167,10 +165,10 @@ describe("Moves - Powder", () => { game.move.select(MoveId.FIERY_DANCE, 0, BattlerIndex.ENEMY); game.move.select(MoveId.POWDER, 1, BattlerIndex.ENEMY); - await game.phaseInterceptor.to(MoveEffectPhase); + await game.phaseInterceptor.to("MoveEffectPhase"); const enemyStartingHp = enemyPokemon.hp; - await game.phaseInterceptor.to(BerryPhase, false); + await game.toEndOfTurn(); // player should not take damage expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); @@ -181,7 +179,7 @@ describe("Moves - Powder", () => { ); }); - it("should cancel Fiery Dance, then prevent it from triggering Dancer", async () => { + it("should cancel Fiery Dance and prevent it from triggering Dancer", async () => { game.override.ability(AbilityId.DANCER).enemyMoveset(MoveId.FIERY_DANCE); await game.classicMode.startBattle([SpeciesId.CHARIZARD]); diff --git a/test/moves/quash.test.ts b/test/moves/quash.test.ts index 7c8306acd22..158c409cf05 100644 --- a/test/moves/quash.test.ts +++ b/test/moves/quash.test.ts @@ -7,6 +7,7 @@ import { MoveResult } from "#enums/move-result"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { describe, beforeAll, afterEach, beforeEach, it, expect } from "vitest"; +import { MoveUseMode } from "#enums/move-use-mode"; describe("Moves - Quash", () => { let phaserGame: Phaser.Game; @@ -49,8 +50,8 @@ describe("Moves - Quash", () => { it("fails if the target has already moved", async () => { await game.classicMode.startBattle([SpeciesId.ACCELGOR, SpeciesId.RATTATA]); - game.move.select(MoveId.SPLASH, 0); - game.move.select(MoveId.QUASH, 1, BattlerIndex.PLAYER); + game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER); + game.move.select(MoveId.QUASH, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER); await game.phaseInterceptor.to("MoveEndPhase"); await game.phaseInterceptor.to("MoveEndPhase"); @@ -58,6 +59,39 @@ describe("Moves - Quash", () => { expect(game.scene.getPlayerField()[1].getLastXMoves(1)[0].result).toBe(MoveResult.FAIL); }); + // TODO: Enable once rampaging moves and move queue are fixed. + // Currently does literally nothing because `MoveUseMode` is overridden from move queue + // within `MovePhase`, but should be enabled once that jank is removed + it.todo("should maintain PP ignore status of rampaging moves", async () => { + game.override.moveset([]); + await game.classicMode.startBattle([SpeciesId.ACCELGOR, SpeciesId.RATTATA]); + + const [accelgor, rattata] = game.scene.getPlayerField(); + expect(accelgor).toBeDefined(); + expect(rattata).toBeDefined(); + + game.move.changeMoveset(accelgor, [MoveId.SPLASH, MoveId.QUASH]); + game.move.changeMoveset(rattata, MoveId.OUTRAGE); + + game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER); + game.move.select(MoveId.OUTRAGE, BattlerIndex.PLAYER_2); + await game.phaseInterceptor.to("TurnEndPhase"); + + const outrageMove = rattata.getMoveset().find(m => m.moveId === MoveId.OUTRAGE); + expect(outrageMove?.ppUsed).toBe(1); + + game.move.select(MoveId.QUASH, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(accelgor.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + expect(outrageMove?.ppUsed).toBe(1); + expect(rattata.getLastXMoves()[0]).toMatchObject({ + move: MoveId.OUTRAGE, + result: MoveResult.SUCCESS, + useMode: MoveUseMode.IGNORE_PP, + }); + }); + it("makes multiple quashed targets move in speed order at the end of the turn", async () => { game.override.enemySpecies(SpeciesId.NINJASK).enemyLevel(100); diff --git a/test/moves/spit_up.test.ts b/test/moves/spit_up.test.ts index fee58ef99f7..00cbe2fd0ad 100644 --- a/test/moves/spit_up.test.ts +++ b/test/moves/spit_up.test.ts @@ -2,7 +2,6 @@ import { Stat } from "#enums/stat"; import { StockpilingTag } from "#app/data/battler-tags"; import { allMoves } from "#app/data/data-lists"; import { BattlerTagType } from "#app/enums/battler-tag-type"; -import type { TurnMove } from "#app/field/pokemon"; import { MoveResult } from "#enums/move-result"; import GameManager from "#test/testUtils/gameManager"; import { AbilityId } from "#enums/ability-id"; @@ -126,7 +125,7 @@ describe("Moves - Spit Up", () => { game.move.select(MoveId.SPIT_UP); await game.phaseInterceptor.to(TurnInitPhase); - expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ + expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: MoveId.SPIT_UP, result: MoveResult.FAIL, targets: [game.scene.getEnemyPokemon()!.getBattlerIndex()], @@ -153,7 +152,7 @@ describe("Moves - Spit Up", () => { await game.phaseInterceptor.to(TurnInitPhase); - expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ + expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: MoveId.SPIT_UP, result: MoveResult.SUCCESS, targets: [game.scene.getEnemyPokemon()!.getBattlerIndex()], @@ -185,7 +184,7 @@ describe("Moves - Spit Up", () => { game.move.select(MoveId.SPIT_UP); await game.phaseInterceptor.to(TurnInitPhase); - expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ + expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: MoveId.SPIT_UP, result: MoveResult.SUCCESS, targets: [game.scene.getEnemyPokemon()!.getBattlerIndex()], diff --git a/test/moves/stockpile.test.ts b/test/moves/stockpile.test.ts index 342ca43c27f..3ad47d8d85e 100644 --- a/test/moves/stockpile.test.ts +++ b/test/moves/stockpile.test.ts @@ -1,6 +1,5 @@ import { Stat } from "#enums/stat"; import { StockpilingTag } from "#app/data/battler-tags"; -import type { TurnMove } from "#app/field/pokemon"; import { MoveResult } from "#enums/move-result"; import { CommandPhase } from "#app/phases/command-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; @@ -72,7 +71,7 @@ describe("Moves - Stockpile", () => { expect(user.getStatStage(Stat.SPDEF)).toBe(3); expect(stockpilingTag).toBeDefined(); expect(stockpilingTag.stockpiledCount).toBe(3); - expect(user.getMoveHistory().at(-1)).toMatchObject({ + expect(user.getMoveHistory().at(-1)).toMatchObject({ result: MoveResult.FAIL, move: MoveId.STOCKPILE, targets: [user.getBattlerIndex()], diff --git a/test/moves/swallow.test.ts b/test/moves/swallow.test.ts index bb95c2c593d..c511682011f 100644 --- a/test/moves/swallow.test.ts +++ b/test/moves/swallow.test.ts @@ -1,7 +1,6 @@ import { Stat } from "#enums/stat"; import { StockpilingTag } from "#app/data/battler-tags"; import { BattlerTagType } from "#app/enums/battler-tag-type"; -import type { TurnMove } from "#app/field/pokemon"; import { MoveResult } from "#enums/move-result"; import { MovePhase } from "#app/phases/move-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; @@ -134,7 +133,7 @@ describe("Moves - Swallow", () => { game.move.select(MoveId.SWALLOW); await game.phaseInterceptor.to(TurnInitPhase); - expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ + expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: MoveId.SWALLOW, result: MoveResult.FAIL, targets: [pokemon.getBattlerIndex()], @@ -159,7 +158,7 @@ describe("Moves - Swallow", () => { await game.phaseInterceptor.to(TurnInitPhase); - expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ + expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: MoveId.SWALLOW, result: MoveResult.SUCCESS, targets: [pokemon.getBattlerIndex()], @@ -190,7 +189,7 @@ describe("Moves - Swallow", () => { await game.phaseInterceptor.to(TurnInitPhase); - expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ + expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: MoveId.SWALLOW, result: MoveResult.SUCCESS, targets: [pokemon.getBattlerIndex()], diff --git a/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts b/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts index d5fe1ffc68a..ae7e269ea00 100644 --- a/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts +++ b/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts @@ -24,6 +24,7 @@ import { FunAndGamesEncounter } from "#app/data/mystery-encounters/encounters/fu import { MoveId } from "#enums/move-id"; import { Command } from "#enums/command"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { MoveUseMode } from "#enums/move-use-mode"; const namespace = "mysteryEncounters/funAndGames"; const defaultParty = [SpeciesId.LAPRAS, SpeciesId.GENGAR, SpeciesId.ABRA]; @@ -152,15 +153,15 @@ describe("Fun And Games! - Mystery Encounter", () => { }); // Turn 1 - (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); + (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, MoveUseMode.NORMAL); await game.phaseInterceptor.to(CommandPhase); // Turn 2 - (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); + (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, MoveUseMode.NORMAL); await game.phaseInterceptor.to(CommandPhase); // Turn 3 - (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); + (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, MoveUseMode.NORMAL); await game.phaseInterceptor.to(SelectModifierPhase, false); // Rewards @@ -179,7 +180,7 @@ describe("Fun And Games! - Mystery Encounter", () => { // Skip minigame scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0; - (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); + (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, MoveUseMode.NORMAL); await game.phaseInterceptor.to(SelectModifierPhase, false); // Rewards @@ -208,7 +209,7 @@ describe("Fun And Games! - Mystery Encounter", () => { const wobbuffet = scene.getEnemyPokemon()!; wobbuffet.hp = Math.floor(0.2 * wobbuffet.getMaxHp()); scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0; - (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); + (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, MoveUseMode.NORMAL); await game.phaseInterceptor.to(SelectModifierPhase, false); // Rewards @@ -238,7 +239,7 @@ describe("Fun And Games! - Mystery Encounter", () => { const wobbuffet = scene.getEnemyPokemon()!; wobbuffet.hp = Math.floor(0.1 * wobbuffet.getMaxHp()); scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0; - (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); + (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, MoveUseMode.NORMAL); await game.phaseInterceptor.to(SelectModifierPhase, false); // Rewards @@ -268,7 +269,7 @@ describe("Fun And Games! - Mystery Encounter", () => { const wobbuffet = scene.getEnemyPokemon()!; wobbuffet.hp = 1; scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0; - (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false); + (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, MoveUseMode.NORMAL); await game.phaseInterceptor.to(SelectModifierPhase, false); // Rewards diff --git a/test/testUtils/gameManager.ts b/test/testUtils/gameManager.ts index 5d3ed3b6c8c..b9f499d4e0c 100644 --- a/test/testUtils/gameManager.ts +++ b/test/testUtils/gameManager.ts @@ -353,15 +353,20 @@ export default class GameManager { }; } - /** Transition to the first {@linkcode CommandPhase} of the next turn. */ + /** + * Transition to the first {@linkcode CommandPhase} of the next turn. + * @returns A promise that resolves once the next {@linkcode CommandPhase} has been reached. + */ async toNextTurn() { await this.phaseInterceptor.to("TurnInitPhase"); await this.phaseInterceptor.to("CommandPhase"); + console.log("==================[New Turn]=================="); } /** Transition to the {@linkcode TurnEndPhase | end of the current turn}. */ async toEndOfTurn() { await this.phaseInterceptor.to("TurnEndPhase"); + console.log("==================[End of Turn]=================="); } /** @@ -371,6 +376,7 @@ export default class GameManager { async toNextWave() { this.doSelectModifier(); + // forcibly end the message box for switching pokemon this.onNextPrompt( "CheckSwitchPhase", UiMode.CONFIRM, @@ -381,7 +387,9 @@ export default class GameManager { () => this.isCurrentPhase(TurnInitPhase), ); - await this.toNextTurn(); + await this.phaseInterceptor.to("TurnInitPhase"); + await this.phaseInterceptor.to("CommandPhase"); + console.log("==================[New Wave]=================="); } /** diff --git a/test/testUtils/helpers/moveHelper.ts b/test/testUtils/helpers/moveHelper.ts index d8c79c3bca8..ed1441a6a2f 100644 --- a/test/testUtils/helpers/moveHelper.ts +++ b/test/testUtils/helpers/moveHelper.ts @@ -13,6 +13,7 @@ import { getMovePosition } from "#test/testUtils/gameManagerUtils"; import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper"; import { vi } from "vitest"; import { coerceArray } from "#app/utils/common"; +import { MoveUseMode } from "#enums/move-use-mode"; /** * Helper to handle a Pokemon's move @@ -66,7 +67,7 @@ export class MoveHelper extends GameManagerHelper { (this.game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand( Command.FIGHT, movePosition, - false, + MoveUseMode.NORMAL, ); }); @@ -94,7 +95,11 @@ export class MoveHelper extends GameManagerHelper { ); }); this.game.onNextPrompt("CommandPhase", UiMode.FIGHT, () => { - (this.game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.TERA, movePosition, false); + (this.game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand( + Command.TERA, + movePosition, + MoveUseMode.NORMAL, + ); }); if (targetIndex !== null) { @@ -194,6 +199,7 @@ export class MoveHelper extends GameManagerHelper { target !== undefined && !legalTargets.multiple && legalTargets.targets.includes(target) ? [target] : enemy.getNextTargets(moveId), + useMode: MoveUseMode.NORMAL, }); /** @@ -242,6 +248,7 @@ export class MoveHelper extends GameManagerHelper { target !== undefined && !legalTargets.multiple && legalTargets.targets.includes(target) ? [target] : enemy.getNextTargets(moveId), + useMode: MoveUseMode.NORMAL, }); /** From ebc6ff2ed3f2dce4c59753f7d0290250de145982 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:18:44 -0400 Subject: [PATCH 48/51] [Refactor] Clean up shiny/HA reroll methods (#5803) --- src/field/pokemon.ts | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 730d2ab2c2a..24399504eba 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2785,17 +2785,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ public trySetShinySeed(thresholdOverride?: number, applyModifiersToOverride?: boolean): boolean { if (!this.shiny) { - const shinyThreshold = new NumberHolder(BASE_SHINY_CHANCE); - if (thresholdOverride === undefined || applyModifiersToOverride) { - if (thresholdOverride !== undefined && applyModifiersToOverride) { - shinyThreshold.value = thresholdOverride; - } + const shinyThreshold = new NumberHolder(thresholdOverride ?? BASE_SHINY_CHANCE); + if (applyModifiersToOverride) { if (timedEventManager.isEventActive()) { shinyThreshold.value *= timedEventManager.getShinyMultiplier(); } globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); - } else { - shinyThreshold.value = thresholdOverride; } this.shiny = randSeedInt(65536) < shinyThreshold.value; @@ -2864,16 +2859,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!this.species.abilityHidden) { return false; } - const haThreshold = new NumberHolder(BASE_HIDDEN_ABILITY_CHANCE); - if (thresholdOverride === undefined || applyModifiersToOverride) { - if (thresholdOverride !== undefined && applyModifiersToOverride) { - haThreshold.value = thresholdOverride; - } + const haThreshold = new NumberHolder(thresholdOverride ?? BASE_HIDDEN_ABILITY_CHANCE); + if (applyModifiersToOverride) { if (!this.hasTrainer()) { globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, haThreshold); } - } else { - haThreshold.value = thresholdOverride; } if (randSeedInt(65536) < haThreshold.value) { From ccceaac877495d063a62238aa2c1d43cfe100213 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:36:09 -0400 Subject: [PATCH 49/51] [Test] Reworked crit override to allow for forced crits (#5738) * Crit override stuff * Update ability.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update pokemon.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update game-mode.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- .env.development | 1 + scripts/create-test/create-test.js | 2 +- scripts/create-test/test-boilerplate.ts | 2 +- src/battle-scene.ts | 21 +++-- src/data/abilities/ability.ts | 10 +-- src/field/pokemon.ts | 57 ++++++------ src/game-mode.ts | 5 +- src/overrides.ts | 6 +- src/phases/move-effect-phase.ts | 2 +- .../ability_activation_order.test.ts | 2 +- test/abilities/analytic.test.ts | 2 +- test/abilities/commander.test.ts | 2 +- test/abilities/corrosion.test.ts | 2 +- test/abilities/cud_chew.test.ts | 2 +- test/abilities/dry_skin.test.ts | 2 +- test/abilities/early_bird.test.ts | 2 +- test/abilities/flash_fire.test.ts | 2 +- test/abilities/flower_veil.test.ts | 2 +- test/abilities/good_as_gold.test.ts | 2 +- test/abilities/gulp_missile.test.ts | 2 +- test/abilities/harvest.test.ts | 2 +- test/abilities/healer.test.ts | 2 +- test/abilities/heatproof.test.ts | 2 +- test/abilities/honey_gather.test.ts | 2 +- test/abilities/hustle.test.ts | 2 +- test/abilities/immunity.test.ts | 2 +- test/abilities/infiltrator.test.ts | 2 +- test/abilities/insomnia.test.ts | 2 +- test/abilities/lightningrod.test.ts | 2 +- test/abilities/limber.test.ts | 2 +- test/abilities/magic_bounce.test.ts | 2 +- test/abilities/magic_guard.test.ts | 4 +- test/abilities/magma_armor.test.ts | 2 +- test/abilities/mimicry.test.ts | 2 +- test/abilities/mold_breaker.test.ts | 2 +- test/abilities/mummy.test.ts | 2 +- test/abilities/mycelium_might.test.ts | 2 +- test/abilities/neutralizing_gas.test.ts | 2 +- test/abilities/normalize.test.ts | 2 +- test/abilities/oblivious.test.ts | 2 +- test/abilities/own_tempo.test.ts | 2 +- test/abilities/parental_bond.test.ts | 2 +- test/abilities/perish_body.test.ts | 6 +- test/abilities/protosynthesis.test.ts | 2 +- test/abilities/sand_spit.test.ts | 2 +- test/abilities/sap_sipper.test.ts | 2 +- test/abilities/seed_sower.test.ts | 4 +- test/abilities/serene_grace.test.ts | 2 +- test/abilities/sheer_force.test.ts | 2 +- test/abilities/shell-armor.test.ts | 69 +++++++++++++++ test/abilities/stakeout.test.ts | 2 +- test/abilities/stall.test.ts | 2 +- test/abilities/storm_drain.test.ts | 2 +- test/abilities/thermal_exchange.test.ts | 2 +- test/abilities/trace.test.ts | 2 +- test/abilities/victory_star.test.ts | 2 +- test/abilities/vital_spirit.test.ts | 2 +- test/abilities/volt_absorb.test.ts | 2 +- test/abilities/wandering_spirit.test.ts | 2 +- test/abilities/water_bubble.test.ts | 2 +- test/abilities/water_veil.test.ts | 2 +- test/abilities/wimp_out.test.ts | 2 +- test/abilities/zen_mode.test.ts | 2 +- test/arena/grassy_terrain.test.ts | 2 +- test/battle/ability_swap.test.ts | 2 +- test/battle/damage_calculation.test.ts | 2 +- test/boss-pokemon.test.ts | 2 +- test/data/status_effect.test.ts | 4 +- test/endless_boss.test.ts | 2 +- test/final_boss.test.ts | 2 +- test/items/multi_lens.test.ts | 2 +- test/items/reviver_seed.test.ts | 2 +- test/moves/alluring_voice.test.ts | 2 +- test/moves/assist.test.ts | 2 +- test/moves/aurora_veil.test.ts | 2 +- test/moves/baton_pass.test.ts | 2 +- test/moves/burning_jealousy.test.ts | 2 +- test/moves/camouflage.test.ts | 2 +- test/moves/chloroblast.test.ts | 2 +- test/moves/copycat.test.ts | 2 +- test/moves/defog.test.ts | 2 +- test/moves/doodle.test.ts | 2 +- test/moves/double_team.test.ts | 2 +- test/moves/dragon_rage.test.ts | 10 +-- test/moves/dynamax_cannon.test.ts | 8 +- test/moves/encore.test.ts | 2 +- test/moves/endure.test.ts | 2 +- test/moves/entrainment.test.ts | 2 +- test/moves/fairy_lock.test.ts | 2 +- test/moves/fake_out.test.ts | 2 +- test/moves/false_swipe.test.ts | 2 +- test/moves/fell_stinger.test.ts | 2 +- test/moves/fissure.test.ts | 4 +- test/moves/flame_burst.test.ts | 4 +- test/moves/foresight.test.ts | 2 +- test/moves/forests_curse.test.ts | 2 +- test/moves/fusion_bolt.test.ts | 2 +- test/moves/fusion_flare.test.ts | 2 +- test/moves/fusion_flare_bolt.test.ts | 2 +- test/moves/gigaton_hammer.test.ts | 2 +- test/moves/glaive_rush.test.ts | 2 +- test/moves/grudge.test.ts | 2 +- test/moves/heal_block.test.ts | 2 +- test/moves/instruct.test.ts | 2 +- test/moves/jaw_lock.test.ts | 2 +- test/moves/lash_out.test.ts | 2 +- test/moves/last-resort.test.ts | 2 +- test/moves/last_respects.test.ts | 2 +- test/moves/light_screen.test.ts | 2 +- test/moves/lucky_chant.test.ts | 86 +++++++++++-------- test/moves/magic_coat.test.ts | 2 +- test/moves/magnet_rise.test.ts | 2 +- test/moves/metal_burst.test.ts | 2 +- test/moves/miracle_eye.test.ts | 2 +- test/moves/mirror_move.test.ts | 2 +- test/moves/mist.test.ts | 2 +- test/moves/moongeist_beam.test.ts | 2 +- test/moves/multi_target.test.ts | 2 +- test/moves/order_up.test.ts | 2 +- test/moves/pollen_puff.test.ts | 2 +- test/moves/psycho_shift.test.ts | 2 +- test/moves/reflect.test.ts | 2 +- test/moves/reflect_type.test.ts | 6 +- test/moves/retaliate.test.ts | 2 +- test/moves/revival_blessing.test.ts | 2 +- test/moves/role_play.test.ts | 2 +- test/moves/rollout.test.ts | 2 +- test/moves/round.test.ts | 2 +- test/moves/scale_shot.test.ts | 2 +- test/moves/secret_power.test.ts | 2 +- test/moves/simple_beam.test.ts | 2 +- test/moves/sketch.test.ts | 2 +- test/moves/skill_swap.test.ts | 2 +- test/moves/sleep_talk.test.ts | 2 +- test/moves/spectral_thief.test.ts | 3 +- test/moves/struggle.test.ts | 2 +- test/moves/synchronoise.test.ts | 2 +- test/moves/tackle.test.ts | 2 +- test/moves/tar_shot.test.ts | 2 +- test/moves/tera_blast.test.ts | 2 +- test/moves/trick_or_treat.test.ts | 2 +- test/moves/u_turn.test.ts | 2 +- test/moves/upper_hand.test.ts | 2 +- test/moves/will_o_wisp.test.ts | 2 +- test/phases/form-change-phase.test.ts | 2 +- test/phases/frenzy-move-reset.test.ts | 2 +- test/phases/game-over-phase.test.ts | 2 +- test/testUtils/gameManagerUtils.ts | 2 +- test/testUtils/helpers/dailyModeHelper.ts | 5 +- test/testUtils/helpers/overridesHelper.ts | 11 ++- test/testUtils/phaseInterceptor.ts | 4 +- 151 files changed, 345 insertions(+), 243 deletions(-) create mode 100644 test/abilities/shell-armor.test.ts diff --git a/.env.development b/.env.development index e4e5053016f..91c228d6761 100644 --- a/.env.development +++ b/.env.development @@ -1,6 +1,7 @@ VITE_BYPASS_LOGIN=1 VITE_BYPASS_TUTORIAL=0 VITE_SERVER_URL=http://localhost:8001 +# IDs for discord/google auth go unused due to VITE_BYPASS_LOGIN VITE_DISCORD_CLIENT_ID=1234567890 VITE_GOOGLE_CLIENT_ID=1234567890 VITE_I18N_DEBUG=0 diff --git a/scripts/create-test/create-test.js b/scripts/create-test/create-test.js index e71994472f3..d5cac5cd408 100644 --- a/scripts/create-test/create-test.js +++ b/scripts/create-test/create-test.js @@ -48,7 +48,7 @@ async function promptTestType() { { type: "list", name: "selectedOption", - message: "What type of test would you like to create:", + message: "What type of test would you like to create?", choices: [...choices.map(choice => ({ name: choice.label, value: choice })), "EXIT"], }, ]); diff --git a/scripts/create-test/test-boilerplate.ts b/scripts/create-test/test-boilerplate.ts index 40912be6c5f..1fbd3c8040a 100644 --- a/scripts/create-test/test-boilerplate.ts +++ b/scripts/create-test/test-boilerplate.ts @@ -24,7 +24,7 @@ describe("{{description}}", () => { game.override .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 81c65d85e06..14b0c6a3d19 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2094,12 +2094,15 @@ export default class BattleScene extends SceneBase { } getMaxExpLevel(ignoreLevelCap = false): number { - if (Overrides.LEVEL_CAP_OVERRIDE > 0) { - return Overrides.LEVEL_CAP_OVERRIDE; + const capOverride = Overrides.LEVEL_CAP_OVERRIDE ?? 0; + if (capOverride > 0) { + return capOverride; } - if (ignoreLevelCap || Overrides.LEVEL_CAP_OVERRIDE < 0) { + + if (ignoreLevelCap || capOverride < 0) { return Number.MAX_SAFE_INTEGER; } + const waveIndex = Math.ceil((this.currentBattle?.waveIndex || 1) / 10) * 10; const difficultyWaveIndex = this.gameMode.getWaveForDifficulty(waveIndex); const baseLevel = (1 + difficultyWaveIndex / 2 + Math.pow(difficultyWaveIndex / 25, 2)) * 1.2; @@ -3492,17 +3495,13 @@ export default class BattleScene extends SceneBase { sessionEncounterRate + Math.min(currentRunDiffFromAvg * ANTI_VARIANCE_WEIGHT_MODIFIER, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT / 2); - const successRate = isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE) - ? favoredEncounterRate - : Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE!; + const successRate = Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE ?? favoredEncounterRate; - // If the most recent ME was 3 or fewer waves ago, can never spawn a ME + // MEs can only spawn 3 or more waves after the previous ME, barring overrides const canSpawn = - encounteredEvents.length === 0 || - waveIndex - encounteredEvents[encounteredEvents.length - 1].waveIndex > 3 || - !isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE); + encounteredEvents.length === 0 || waveIndex - encounteredEvents[encounteredEvents.length - 1].waveIndex > 3; - if (canSpawn) { + if (canSpawn || Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE !== null) { let roll = MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT; // Always rolls the check on the same offset to ensure no RNG changes from reloading session this.executeWithSeedOffset( diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index c9684986c46..3124f782ff5 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -4617,7 +4617,7 @@ export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbA * @param stat The stat being affected * @param cancelled Holds whether the stat change was already prevented. * @param args Args[0] is the target pokemon of the stat change. - * @returns + * @returns `true` if the ability can be applied */ override canApplyPreStatStageChange( _pokemon: Pokemon, @@ -4778,17 +4778,17 @@ export class BlockCritAbAttr extends AbAttr { } /** - * Apply the block crit ability by setting the value in the provided boolean holder to false - * @param args - [0] is a boolean holder representing whether the attack can crit + * Apply the block crit ability by setting the value in the provided boolean holder to `true`. + * @param args - `[0]`: A {@linkcode BooleanHolder} containing whether the attack is prevented from critting. */ override apply( _pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder, - args: [BooleanHolder, ...any], + args: [BooleanHolder], ): void { - args[0].value = false; + args[0].value = true; } } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 24399504eba..fe89142b5c6 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1356,8 +1356,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * Calculate the critical-hit stage of a move used against this pokemon by - * the given source + * Calculate the critical-hit stage of a move used **against** this pokemon by + * the given source. + * * @param source - The {@linkcode Pokemon} using the move * @param move - The {@linkcode Move} being used * @returns The final critical-hit stage value @@ -1370,11 +1371,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyAbAttrs("BonusCritAbAttr", source, null, false, critStage); const critBoostTag = source.getTag(CritBoostTag); if (critBoostTag) { - if (critBoostTag instanceof DragonCheerTag) { - critStage.value += critBoostTag.typesOnAdd.includes(PokemonType.DRAGON) ? 2 : 1; - } else { - critStage.value += 2; - } + // Dragon cheer only gives +1 crit stage to non-dragon types + critStage.value += + critBoostTag instanceof DragonCheerTag && !critBoostTag.typesOnAdd.includes(PokemonType.DRAGON) ? 1 : 2; } console.log(`crit stage: +${critStage.value}`); @@ -3878,33 +3877,39 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }; } - /** Calculate whether the given move critically hits this pokemon + /** + * Determine whether the given move will score a critical hit **against** this Pokemon. * @param source - The {@linkcode Pokemon} using the move * @param move - The {@linkcode Move} being used - * @param simulated - If `true`, suppresses changes to game state during calculation (defaults to `true`) - * @returns whether the move critically hits the pokemon + * @returns Whether the move will critically hit the defender. */ - getCriticalHitResult(source: Pokemon, move: Move, simulated = true): boolean { - const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide); - if (noCritTag || Overrides.NEVER_CRIT_OVERRIDE || move.hasAttr("FixedDamageAttr")) { + getCriticalHitResult(source: Pokemon, move: Move): boolean { + if (move.hasAttr("FixedDamageAttr")) { + // fixed damage moves (Dragon Rage, etc.) will nevet crit return false; } - const isCritical = new BooleanHolder(false); - if (source.getTag(BattlerTagType.ALWAYS_CRIT)) { - isCritical.value = true; - } - applyMoveAttrs("CritOnlyAttr", source, this, move, isCritical); - applyAbAttrs("ConditionalCritAbAttr", source, null, simulated, isCritical, this, move); - if (!isCritical.value) { - const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))]; - isCritical.value = critChance === 1 || !globalScene.randBattleSeedInt(critChance); - } + const alwaysCrit = new BooleanHolder(false); + applyMoveAttrs("CritOnlyAttr", source, this, move, alwaysCrit); + applyAbAttrs("ConditionalCritAbAttr", source, null, false, alwaysCrit, this, move); + const alwaysCritTag = !!source.getTag(BattlerTagType.ALWAYS_CRIT); + const critChance = [24, 8, 2, 1][Phaser.Math.Clamp(this.getCritStage(source, move), 0, 3)]; - applyAbAttrs("BlockCritAbAttr", this, null, simulated, isCritical); + let isCritical = alwaysCrit.value || alwaysCritTag || critChance === 1; - return isCritical.value; + // If we aren't already guaranteed to crit, do a random roll & check overrides + isCritical ||= Overrides.CRITICAL_HIT_OVERRIDE ?? globalScene.randBattleSeedInt(critChance) === 0; + + // apply crit block effects from lucky chant & co., overriding previous effects + const blockCrit = new BooleanHolder(false); + applyAbAttrs("BlockCritAbAttr", this, null, false, blockCrit); + const blockCritTag = globalScene.arena.getTagOnSide( + NoCritTag, + this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, + ); + isCritical &&= !blockCritTag && !blockCrit.value; // need to roll a crit and not be blocked by either crit prevention effect + + return isCritical; } /** diff --git a/src/game-mode.ts b/src/game-mode.ts index a6fc8195175..9722d564e09 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -90,13 +90,14 @@ export class GameMode implements GameModeConfig { } /** + * Helper function to get starting level for game mode. * @returns either: - * - override from overrides.ts + * - starting level override from overrides.ts * - 20 for Daily Runs * - 5 for all other modes */ getStartingLevel(): number { - if (Overrides.STARTING_LEVEL_OVERRIDE) { + if (Overrides.STARTING_LEVEL_OVERRIDE > 0) { return Overrides.STARTING_LEVEL_OVERRIDE; } switch (this.modeId) { diff --git a/src/overrides.ts b/src/overrides.ts index 5858be77dac..b390b9fa70f 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -80,7 +80,11 @@ class DefaultOverrides { /** Sets the level cap to this number during experience gain calculations. Set to `0` to disable override & use normal wave-based level caps, or any negative number to set it to 9 quadrillion (effectively disabling it). */ readonly LEVEL_CAP_OVERRIDE: number = 0; - readonly NEVER_CRIT_OVERRIDE: boolean = false; + /** + * If defined, overrides random critical hit rolls to always or never succeed. + * Ignored if the move is guaranteed to always/never crit. + */ + readonly CRITICAL_HIT_OVERRIDE: boolean | null = null; /** default 1000 */ readonly STARTING_MONEY_OVERRIDE: number = 0; /** Sets all shop item prices to 0 */ diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 35c803ee1d7..d7da1ab996c 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -821,7 +821,7 @@ export class MoveEffectPhase extends PokemonPhase { * @param effectiveness - The effectiveness of the move against the target */ protected applyMoveDamage(user: Pokemon, target: Pokemon, effectiveness: TypeDamageMultiplier): HitResult { - const isCritical = target.getCriticalHitResult(user, this.move, false); + const isCritical = target.getCriticalHitResult(user, this.move); /* * Apply stat changes from {@linkcode move} and gives it to {@linkcode source} diff --git a/test/abilities/ability_activation_order.test.ts b/test/abilities/ability_activation_order.test.ts index 04adf40b623..0b8e354cc7d 100644 --- a/test/abilities/ability_activation_order.test.ts +++ b/test/abilities/ability_activation_order.test.ts @@ -27,7 +27,7 @@ describe("Ability Activation Order", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/analytic.test.ts b/test/abilities/analytic.test.ts index 2cfea64d20e..5dbf4365186 100644 --- a/test/abilities/analytic.test.ts +++ b/test/abilities/analytic.test.ts @@ -27,7 +27,7 @@ describe("Abilities - Analytic", () => { .moveset([MoveId.SPLASH, MoveId.TACKLE]) .ability(AbilityId.ANALYTIC) .battleStyle("single") - .disableCrits() + .criticalHits(false) .startingLevel(200) .enemyLevel(200) .enemySpecies(SpeciesId.SNORLAX) diff --git a/test/abilities/commander.test.ts b/test/abilities/commander.test.ts index 39d9d0f4d7f..99512d7a733 100644 --- a/test/abilities/commander.test.ts +++ b/test/abilities/commander.test.ts @@ -35,7 +35,7 @@ describe("Abilities - Commander", () => { .moveset([MoveId.LIQUIDATION, MoveId.MEMENTO, MoveId.SPLASH, MoveId.FLIP_TURN]) .ability(AbilityId.COMMANDER) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.SNORLAX) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.TACKLE); diff --git a/test/abilities/corrosion.test.ts b/test/abilities/corrosion.test.ts index 81b04ef340c..2a0464d1146 100644 --- a/test/abilities/corrosion.test.ts +++ b/test/abilities/corrosion.test.ts @@ -24,7 +24,7 @@ describe("Abilities - Corrosion", () => { game.override .moveset([MoveId.SPLASH]) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.GRIMER) .enemyAbility(AbilityId.CORROSION) .enemyMoveset(MoveId.TOXIC); diff --git a/test/abilities/cud_chew.test.ts b/test/abilities/cud_chew.test.ts index 8a80c6625cc..70c282bf8a8 100644 --- a/test/abilities/cud_chew.test.ts +++ b/test/abilities/cud_chew.test.ts @@ -33,7 +33,7 @@ describe("Abilities - Cud Chew", () => { .startingHeldItems([{ name: "BERRY", type: BerryType.SITRUS, count: 1 }]) .ability(AbilityId.CUD_CHEW) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/dry_skin.test.ts b/test/abilities/dry_skin.test.ts index 549bb45ba9a..31620beb9bd 100644 --- a/test/abilities/dry_skin.test.ts +++ b/test/abilities/dry_skin.test.ts @@ -23,7 +23,7 @@ describe("Abilities - Dry Skin", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemyAbility(AbilityId.DRY_SKIN) .enemyMoveset(MoveId.SPLASH) .enemySpecies(SpeciesId.CHARMANDER) diff --git a/test/abilities/early_bird.test.ts b/test/abilities/early_bird.test.ts index e19f2653b57..dcdfd8e4956 100644 --- a/test/abilities/early_bird.test.ts +++ b/test/abilities/early_bird.test.ts @@ -28,7 +28,7 @@ describe("Abilities - Early Bird", () => { .moveset([MoveId.REST, MoveId.BELLY_DRUM, MoveId.SPLASH]) .ability(AbilityId.EARLY_BIRD) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/flash_fire.test.ts b/test/abilities/flash_fire.test.ts index fe17013f49f..3be156a1578 100644 --- a/test/abilities/flash_fire.test.ts +++ b/test/abilities/flash_fire.test.ts @@ -32,7 +32,7 @@ describe("Abilities - Flash Fire", () => { .enemyAbility(AbilityId.BALL_FETCH) .startingLevel(20) .enemyLevel(20) - .disableCrits(); + .criticalHits(false); }); it("immune to Fire-type moves", async () => { diff --git a/test/abilities/flower_veil.test.ts b/test/abilities/flower_veil.test.ts index d3e99185c2f..7cabfa23e96 100644 --- a/test/abilities/flower_veil.test.ts +++ b/test/abilities/flower_veil.test.ts @@ -32,7 +32,7 @@ describe("Abilities - Flower Veil", () => { .enemySpecies(SpeciesId.BULBASAUR) .ability(AbilityId.FLOWER_VEIL) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/good_as_gold.test.ts b/test/abilities/good_as_gold.test.ts index 3b6600eabd4..89e354b1f34 100644 --- a/test/abilities/good_as_gold.test.ts +++ b/test/abilities/good_as_gold.test.ts @@ -33,7 +33,7 @@ describe("Abilities - Good As Gold", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.GOOD_AS_GOLD) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/gulp_missile.test.ts b/test/abilities/gulp_missile.test.ts index b41eea9ce18..e47b22f0c06 100644 --- a/test/abilities/gulp_missile.test.ts +++ b/test/abilities/gulp_missile.test.ts @@ -41,7 +41,7 @@ describe("Abilities - Gulp Missile", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .disableCrits() + .criticalHits(false) .battleStyle("single") .moveset([MoveId.SURF, MoveId.DIVE, MoveId.SPLASH, MoveId.SUBSTITUTE]) .enemySpecies(SpeciesId.SNORLAX) diff --git a/test/abilities/harvest.test.ts b/test/abilities/harvest.test.ts index 40e6a6abd62..662eeed6dd0 100644 --- a/test/abilities/harvest.test.ts +++ b/test/abilities/harvest.test.ts @@ -47,7 +47,7 @@ describe("Abilities - Harvest", () => { .ability(AbilityId.HARVEST) .startingLevel(100) .battleStyle("single") - .disableCrits() + .criticalHits(false) .statusActivation(false) // Since we're using nuzzle to proc both enigma and sitrus berries .weather(WeatherType.SUNNY) // guaranteed recovery .enemyLevel(1) diff --git a/test/abilities/healer.test.ts b/test/abilities/healer.test.ts index 94272848e16..b37c9effeb0 100644 --- a/test/abilities/healer.test.ts +++ b/test/abilities/healer.test.ts @@ -30,7 +30,7 @@ describe("Abilities - Healer", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/heatproof.test.ts b/test/abilities/heatproof.test.ts index f705e8bf785..39c9dff8289 100644 --- a/test/abilities/heatproof.test.ts +++ b/test/abilities/heatproof.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Heatproof", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.CHARMANDER) .enemyAbility(AbilityId.HEATPROOF) .enemyMoveset(MoveId.SPLASH) diff --git a/test/abilities/honey_gather.test.ts b/test/abilities/honey_gather.test.ts index 06d351b2a61..f8700f3e6f7 100644 --- a/test/abilities/honey_gather.test.ts +++ b/test/abilities/honey_gather.test.ts @@ -29,7 +29,7 @@ describe("Abilities - Honey Gather", () => { .ability(AbilityId.HONEY_GATHER) .passiveAbility(AbilityId.RUN_AWAY) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/hustle.test.ts b/test/abilities/hustle.test.ts index 7ac636b15b2..57a1a2e1d86 100644 --- a/test/abilities/hustle.test.ts +++ b/test/abilities/hustle.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Hustle", () => { game.override .ability(AbilityId.HUSTLE) .moveset([MoveId.TACKLE, MoveId.GIGA_DRAIN, MoveId.FISSURE]) - .disableCrits() + .criticalHits(false) .battleStyle("single") .enemyMoveset(MoveId.SPLASH) .enemySpecies(SpeciesId.SHUCKLE) diff --git a/test/abilities/immunity.test.ts b/test/abilities/immunity.test.ts index b6ca34bfaa3..063ab57f11a 100644 --- a/test/abilities/immunity.test.ts +++ b/test/abilities/immunity.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Immunity", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/infiltrator.test.ts b/test/abilities/infiltrator.test.ts index 03b55e925cd..a5de035a136 100644 --- a/test/abilities/infiltrator.test.ts +++ b/test/abilities/infiltrator.test.ts @@ -31,7 +31,7 @@ describe("Abilities - Infiltrator", () => { .moveset([MoveId.TACKLE, MoveId.WATER_GUN, MoveId.SPORE, MoveId.BABY_DOLL_EYES]) .ability(AbilityId.INFILTRATOR) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.SNORLAX) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH) diff --git a/test/abilities/insomnia.test.ts b/test/abilities/insomnia.test.ts index 418e0ed1345..800663823c3 100644 --- a/test/abilities/insomnia.test.ts +++ b/test/abilities/insomnia.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Insomnia", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/lightningrod.test.ts b/test/abilities/lightningrod.test.ts index a69f5e94067..2dc29500454 100644 --- a/test/abilities/lightningrod.test.ts +++ b/test/abilities/lightningrod.test.ts @@ -27,7 +27,7 @@ describe("Abilities - Lightningrod", () => { .moveset([MoveId.SPLASH, MoveId.SHOCK_WAVE]) .ability(AbilityId.BALL_FETCH) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/limber.test.ts b/test/abilities/limber.test.ts index 2ca469dcaa1..77acd8b3dab 100644 --- a/test/abilities/limber.test.ts +++ b/test/abilities/limber.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Limber", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/magic_bounce.test.ts b/test/abilities/magic_bounce.test.ts index fb4f273950a..04bfcbbfe8f 100644 --- a/test/abilities/magic_bounce.test.ts +++ b/test/abilities/magic_bounce.test.ts @@ -32,7 +32,7 @@ describe("Abilities - Magic Bounce", () => { .ability(AbilityId.BALL_FETCH) .battleStyle("single") .moveset([MoveId.GROWL, MoveId.SPLASH]) - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.MAGIC_BOUNCE) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/magic_guard.test.ts b/test/abilities/magic_guard.test.ts index 4274e692a2e..f135a761bba 100644 --- a/test/abilities/magic_guard.test.ts +++ b/test/abilities/magic_guard.test.ts @@ -89,9 +89,7 @@ describe("Abilities - Magic Guard", () => { }); it("ability effect should not persist when the ability is replaced", async () => { - game.override - .enemyMoveset([MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED]) - .statusEffect(StatusEffect.POISON); + game.override.enemyMoveset(MoveId.WORRY_SEED).statusEffect(StatusEffect.POISON); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); diff --git a/test/abilities/magma_armor.test.ts b/test/abilities/magma_armor.test.ts index 74493fac365..0a8f3024291 100644 --- a/test/abilities/magma_armor.test.ts +++ b/test/abilities/magma_armor.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Magma Armor", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/mimicry.test.ts b/test/abilities/mimicry.test.ts index df6facc3755..7ac7aeb599f 100644 --- a/test/abilities/mimicry.test.ts +++ b/test/abilities/mimicry.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Mimicry", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.MIMICRY) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyMoveset(MoveId.SPLASH); }); diff --git a/test/abilities/mold_breaker.test.ts b/test/abilities/mold_breaker.test.ts index e2e8a73da9d..28a077e8908 100644 --- a/test/abilities/mold_breaker.test.ts +++ b/test/abilities/mold_breaker.test.ts @@ -27,7 +27,7 @@ describe("Abilities - Mold Breaker", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.MOLD_BREAKER) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/mummy.test.ts b/test/abilities/mummy.test.ts index 2b35e801677..d6c40541d25 100644 --- a/test/abilities/mummy.test.ts +++ b/test/abilities/mummy.test.ts @@ -25,7 +25,7 @@ describe("Abilities - Mummy", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.MUMMY) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.TACKLE); diff --git a/test/abilities/mycelium_might.test.ts b/test/abilities/mycelium_might.test.ts index 7bfb3fb7b81..2cdda4353b5 100644 --- a/test/abilities/mycelium_might.test.ts +++ b/test/abilities/mycelium_might.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Mycelium Might", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.SHUCKLE) .enemyAbility(AbilityId.CLEAR_BODY) diff --git a/test/abilities/neutralizing_gas.test.ts b/test/abilities/neutralizing_gas.test.ts index 51d2bed3ff0..2408a78f11d 100644 --- a/test/abilities/neutralizing_gas.test.ts +++ b/test/abilities/neutralizing_gas.test.ts @@ -31,7 +31,7 @@ describe("Abilities - Neutralizing Gas", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.NEUTRALIZING_GAS) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/normalize.test.ts b/test/abilities/normalize.test.ts index 2e000e76a27..1a4b93f3f32 100644 --- a/test/abilities/normalize.test.ts +++ b/test/abilities/normalize.test.ts @@ -29,7 +29,7 @@ describe("Abilities - Normalize", () => { .moveset([MoveId.TACKLE]) .ability(AbilityId.NORMALIZE) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/oblivious.test.ts b/test/abilities/oblivious.test.ts index e340d3c867d..dab8394edd0 100644 --- a/test/abilities/oblivious.test.ts +++ b/test/abilities/oblivious.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Oblivious", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/own_tempo.test.ts b/test/abilities/own_tempo.test.ts index 1d6d8aa76e5..869953b4e93 100644 --- a/test/abilities/own_tempo.test.ts +++ b/test/abilities/own_tempo.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Own Tempo", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/parental_bond.test.ts b/test/abilities/parental_bond.test.ts index f9bd703d751..fd914b86100 100644 --- a/test/abilities/parental_bond.test.ts +++ b/test/abilities/parental_bond.test.ts @@ -28,7 +28,7 @@ describe("Abilities - Parental Bond", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .ability(AbilityId.PARENTAL_BOND) .enemySpecies(SpeciesId.SNORLAX) .enemyAbility(AbilityId.FUR_COAT) diff --git a/test/abilities/perish_body.test.ts b/test/abilities/perish_body.test.ts index 5b98aab5f6c..4852533816b 100644 --- a/test/abilities/perish_body.test.ts +++ b/test/abilities/perish_body.test.ts @@ -23,13 +23,13 @@ describe("Abilities - Perish Song", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .starterSpecies(SpeciesId.CURSOLA) .ability(AbilityId.PERISH_BODY) - .moveset([MoveId.SPLASH]) - .enemyMoveset([MoveId.AQUA_JET]); + .moveset(MoveId.SPLASH) + .enemyMoveset(MoveId.AQUA_JET); }); it("should trigger when hit with damaging move", async () => { diff --git a/test/abilities/protosynthesis.test.ts b/test/abilities/protosynthesis.test.ts index ba7bb5d084b..ef860fc0cd5 100644 --- a/test/abilities/protosynthesis.test.ts +++ b/test/abilities/protosynthesis.test.ts @@ -28,7 +28,7 @@ describe("Abilities - Protosynthesis", () => { .moveset([MoveId.SPLASH, MoveId.TACKLE]) .ability(AbilityId.PROTOSYNTHESIS) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/sand_spit.test.ts b/test/abilities/sand_spit.test.ts index 62d5d782fc5..2b519745649 100644 --- a/test/abilities/sand_spit.test.ts +++ b/test/abilities/sand_spit.test.ts @@ -24,7 +24,7 @@ describe("Abilities - Sand Spit", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .starterSpecies(SpeciesId.SILICOBRA) diff --git a/test/abilities/sap_sipper.test.ts b/test/abilities/sap_sipper.test.ts index 16559fb563f..c70730f0711 100644 --- a/test/abilities/sap_sipper.test.ts +++ b/test/abilities/sap_sipper.test.ts @@ -31,7 +31,7 @@ describe("Abilities - Sap Sipper", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .ability(AbilityId.SAP_SIPPER) .enemySpecies(SpeciesId.RATTATA) .enemyAbility(AbilityId.SAP_SIPPER) diff --git a/test/abilities/seed_sower.test.ts b/test/abilities/seed_sower.test.ts index 4b649275d83..7a898f5bcf9 100644 --- a/test/abilities/seed_sower.test.ts +++ b/test/abilities/seed_sower.test.ts @@ -24,12 +24,12 @@ describe("Abilities - Seed Sower", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .starterSpecies(SpeciesId.ARBOLIVA) .ability(AbilityId.SEED_SOWER) - .moveset([MoveId.SPLASH]); + .moveset(MoveId.SPLASH); }); it("should trigger when hit with damaging move", async () => { diff --git a/test/abilities/serene_grace.test.ts b/test/abilities/serene_grace.test.ts index c5c12fe217e..2acf2981537 100644 --- a/test/abilities/serene_grace.test.ts +++ b/test/abilities/serene_grace.test.ts @@ -24,7 +24,7 @@ describe("Abilities - Serene Grace", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .disableCrits() + .criticalHits(false) .battleStyle("single") .ability(AbilityId.SERENE_GRACE) .moveset([MoveId.AIR_SLASH]) diff --git a/test/abilities/sheer_force.test.ts b/test/abilities/sheer_force.test.ts index b597afd8293..e90b8d08611 100644 --- a/test/abilities/sheer_force.test.ts +++ b/test/abilities/sheer_force.test.ts @@ -31,7 +31,7 @@ describe("Abilities - Sheer Force", () => { .enemySpecies(SpeciesId.ONIX) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset([MoveId.SPLASH]) - .disableCrits(); + .criticalHits(false); }); const SHEER_FORCE_MULT = 1.3; diff --git a/test/abilities/shell-armor.test.ts b/test/abilities/shell-armor.test.ts new file mode 100644 index 00000000000..ea0ed4a95dd --- /dev/null +++ b/test/abilities/shell-armor.test.ts @@ -0,0 +1,69 @@ +import type Move from "#app/data/moves/move"; +import Pokemon from "#app/field/pokemon"; +import { AbilityId } from "#enums/ability-id"; +import { MoveId } from "#enums/move-id"; +import { SpeciesId } from "#enums/species-id"; +import { StatusEffect } from "#enums/status-effect"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi, type MockInstance } from "vitest"; + +describe("Abilities - Shell Armor", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + let critSpy: MockInstance<(source: Pokemon, move: Move, simulated?: boolean) => boolean>; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .ability(AbilityId.SHELL_ARMOR) + .battleStyle("single") + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.BALL_FETCH) + .statusEffect(StatusEffect.POISON); + + critSpy = vi.spyOn(Pokemon.prototype, "getCriticalHitResult"); + }); + + it("should prevent natural crit rolls from suceeding", async () => { + game.override.criticalHits(true); // force random crit rolls to always succeed + await game.classicMode.startBattle([SpeciesId.ABOMASNOW]); + + game.move.use(MoveId.SPLASH); + await game.move.forceEnemyMove(MoveId.TACKLE); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(critSpy).toHaveReturnedWith(false); + }); + + it("should prevent guaranteed-crit moves from critting", async () => { + await game.classicMode.startBattle([SpeciesId.ABOMASNOW]); + + game.move.use(MoveId.SPLASH); + await game.move.forceEnemyMove(MoveId.FLOWER_TRICK); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(critSpy).toHaveReturnedWith(false); + }); + + it("should block Merciless guaranteed crits", async () => { + game.override.enemyAbility(AbilityId.MERCILESS); + await game.classicMode.startBattle([SpeciesId.ABOMASNOW]); + + game.move.use(MoveId.SPLASH); + await game.move.forceEnemyMove(MoveId.TACKLE); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(critSpy).toHaveReturnedWith(false); + }); +}); diff --git a/test/abilities/stakeout.test.ts b/test/abilities/stakeout.test.ts index ba4325e0295..b3844bf17ca 100644 --- a/test/abilities/stakeout.test.ts +++ b/test/abilities/stakeout.test.ts @@ -27,7 +27,7 @@ describe("Abilities - Stakeout", () => { .moveset([MoveId.SPLASH, MoveId.SURF]) .ability(AbilityId.STAKEOUT) .battleStyle("single") - .disableCrits() + .criticalHits(false) .startingLevel(100) .enemyLevel(100) .enemySpecies(SpeciesId.SNORLAX) diff --git a/test/abilities/stall.test.ts b/test/abilities/stall.test.ts index 74fd2f67f83..e8ee23fb972 100644 --- a/test/abilities/stall.test.ts +++ b/test/abilities/stall.test.ts @@ -24,7 +24,7 @@ describe("Abilities - Stall", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.REGIELEKI) .enemyAbility(AbilityId.STALL) .enemyMoveset(MoveId.QUICK_ATTACK) diff --git a/test/abilities/storm_drain.test.ts b/test/abilities/storm_drain.test.ts index 3a6c0aba100..8eedf0c7ce8 100644 --- a/test/abilities/storm_drain.test.ts +++ b/test/abilities/storm_drain.test.ts @@ -27,7 +27,7 @@ describe("Abilities - Storm Drain", () => { .moveset([MoveId.SPLASH, MoveId.WATER_GUN]) .ability(AbilityId.BALL_FETCH) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/thermal_exchange.test.ts b/test/abilities/thermal_exchange.test.ts index f27e6da1d3b..70af864d413 100644 --- a/test/abilities/thermal_exchange.test.ts +++ b/test/abilities/thermal_exchange.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Thermal Exchange", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/trace.test.ts b/test/abilities/trace.test.ts index 9bfd2f021c8..f17bec159ab 100644 --- a/test/abilities/trace.test.ts +++ b/test/abilities/trace.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Trace", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.TRACE) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/victory_star.test.ts b/test/abilities/victory_star.test.ts index a15beac8b2c..4742dd96aa6 100644 --- a/test/abilities/victory_star.test.ts +++ b/test/abilities/victory_star.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Victory Star", () => { game.override .moveset([MoveId.TACKLE, MoveId.SPLASH]) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/vital_spirit.test.ts b/test/abilities/vital_spirit.test.ts index c32454e9d31..18764f94a6b 100644 --- a/test/abilities/vital_spirit.test.ts +++ b/test/abilities/vital_spirit.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Vital Spirit", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/volt_absorb.test.ts b/test/abilities/volt_absorb.test.ts index 707a889f951..6bea70ee2a4 100644 --- a/test/abilities/volt_absorb.test.ts +++ b/test/abilities/volt_absorb.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Volt Absorb", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single").disableCrits(); + game.override.battleStyle("single").criticalHits(false); }); it("does not activate when CHARGE is used", async () => { diff --git a/test/abilities/wandering_spirit.test.ts b/test/abilities/wandering_spirit.test.ts index 360eedda4c9..950cec6d27c 100644 --- a/test/abilities/wandering_spirit.test.ts +++ b/test/abilities/wandering_spirit.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Wandering Spirit", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.WANDERING_SPIRIT) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.TACKLE); diff --git a/test/abilities/water_bubble.test.ts b/test/abilities/water_bubble.test.ts index 412c4a25035..455a2e368c4 100644 --- a/test/abilities/water_bubble.test.ts +++ b/test/abilities/water_bubble.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Water Bubble", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/water_veil.test.ts b/test/abilities/water_veil.test.ts index e67287d250f..2df06ec1a21 100644 --- a/test/abilities/water_veil.test.ts +++ b/test/abilities/water_veil.test.ts @@ -26,7 +26,7 @@ describe("Abilities - Water Veil", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/abilities/wimp_out.test.ts b/test/abilities/wimp_out.test.ts index 05c848a75c0..1db0b80fcd0 100644 --- a/test/abilities/wimp_out.test.ts +++ b/test/abilities/wimp_out.test.ts @@ -39,7 +39,7 @@ describe("Abilities - Wimp Out", () => { .enemyLevel(70) .moveset([MoveId.SPLASH, MoveId.FALSE_SWIPE, MoveId.ENDURE]) .enemyMoveset(MoveId.FALSE_SWIPE) - .disableCrits(); + .criticalHits(false); }); function confirmSwitch(): void { diff --git a/test/abilities/zen_mode.test.ts b/test/abilities/zen_mode.test.ts index 70d2f564218..5df2b3a6bc7 100644 --- a/test/abilities/zen_mode.test.ts +++ b/test/abilities/zen_mode.test.ts @@ -27,7 +27,7 @@ describe("Abilities - ZEN MODE", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyLevel(5) diff --git a/test/arena/grassy_terrain.test.ts b/test/arena/grassy_terrain.test.ts index 832f4400cf4..5f78d8f801d 100644 --- a/test/arena/grassy_terrain.test.ts +++ b/test/arena/grassy_terrain.test.ts @@ -23,7 +23,7 @@ describe("Arena - Grassy Terrain", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemyLevel(1) .enemySpecies(SpeciesId.SHUCKLE) .enemyAbility(AbilityId.STURDY) diff --git a/test/battle/ability_swap.test.ts b/test/battle/ability_swap.test.ts index 64446d703ac..3dd92576e3b 100644 --- a/test/battle/ability_swap.test.ts +++ b/test/battle/ability_swap.test.ts @@ -27,7 +27,7 @@ describe("Test Ability Swapping", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/battle/damage_calculation.test.ts b/test/battle/damage_calculation.test.ts index 555b3a9c554..19cdf6b9237 100644 --- a/test/battle/damage_calculation.test.ts +++ b/test/battle/damage_calculation.test.ts @@ -32,7 +32,7 @@ describe("Battle Mechanics - Damage Calculation", () => { .enemyMoveset(MoveId.SPLASH) .startingLevel(100) .enemyLevel(100) - .disableCrits() + .criticalHits(false) .moveset([MoveId.TACKLE, MoveId.DRAGON_RAGE, MoveId.FISSURE, MoveId.JUMP_KICK]); }); diff --git a/test/boss-pokemon.test.ts b/test/boss-pokemon.test.ts index fdfca9249f7..afcf5e8fa77 100644 --- a/test/boss-pokemon.test.ts +++ b/test/boss-pokemon.test.ts @@ -28,7 +28,7 @@ describe("Boss Pokemon / Shields", () => { game.override .battleStyle("single") .disableTrainerWaves() - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.RATTATA) .enemyMoveset(MoveId.SPLASH) .enemyHeldItems([]) diff --git a/test/data/status_effect.test.ts b/test/data/status_effect.test.ts index a5bfb33c023..e697ee3f1c7 100644 --- a/test/data/status_effect.test.ts +++ b/test/data/status_effect.test.ts @@ -356,7 +356,7 @@ describe("Status Effects", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); @@ -412,7 +412,7 @@ describe("Status Effects", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.NUZZLE) diff --git a/test/endless_boss.test.ts b/test/endless_boss.test.ts index 6814cf5e12a..d219cacf317 100644 --- a/test/endless_boss.test.ts +++ b/test/endless_boss.test.ts @@ -21,7 +21,7 @@ describe("Endless Boss", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.startingBiome(BiomeId.END).disableCrits(); + game.override.startingBiome(BiomeId.END).criticalHits(false); }); afterEach(() => { diff --git a/test/final_boss.test.ts b/test/final_boss.test.ts index d3fcd3214bb..071f83285e7 100644 --- a/test/final_boss.test.ts +++ b/test/final_boss.test.ts @@ -27,7 +27,7 @@ describe("Final Boss", () => { game.override .startingWave(FinalWave.Classic) .startingBiome(BiomeId.END) - .disableCrits() + .criticalHits(false) .enemyMoveset(MoveId.SPLASH) .moveset([MoveId.SPLASH, MoveId.WILL_O_WISP, MoveId.DRAGON_PULSE]) .startingLevel(10000); diff --git a/test/items/multi_lens.test.ts b/test/items/multi_lens.test.ts index 11e4c04ec21..8a3161970c0 100644 --- a/test/items/multi_lens.test.ts +++ b/test/items/multi_lens.test.ts @@ -28,7 +28,7 @@ describe("Items - Multi Lens", () => { .ability(AbilityId.BALL_FETCH) .startingHeldItems([{ name: "MULTI_LENS" }]) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.SNORLAX) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH) diff --git a/test/items/reviver_seed.test.ts b/test/items/reviver_seed.test.ts index 54927130869..f444a6eac66 100644 --- a/test/items/reviver_seed.test.ts +++ b/test/items/reviver_seed.test.ts @@ -29,7 +29,7 @@ describe("Items - Reviver Seed", () => { .moveset([MoveId.SPLASH, MoveId.TACKLE, MoveId.ENDURE]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .startingHeldItems([{ name: "REVIVER_SEED" }]) diff --git a/test/moves/alluring_voice.test.ts b/test/moves/alluring_voice.test.ts index 132f83cd4c1..ba096391f1b 100644 --- a/test/moves/alluring_voice.test.ts +++ b/test/moves/alluring_voice.test.ts @@ -26,7 +26,7 @@ describe("Moves - Alluring Voice", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.ICE_SCALES) .enemyMoveset(MoveId.HOWL) diff --git a/test/moves/assist.test.ts b/test/moves/assist.test.ts index bc51f8bd06a..27aa5528f17 100644 --- a/test/moves/assist.test.ts +++ b/test/moves/assist.test.ts @@ -30,7 +30,7 @@ describe("Moves - Assist", () => { game.override .ability(AbilityId.BALL_FETCH) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyLevel(100) .enemyAbility(AbilityId.BALL_FETCH) diff --git a/test/moves/aurora_veil.test.ts b/test/moves/aurora_veil.test.ts index 98b490b0e32..b9ae79e4155 100644 --- a/test/moves/aurora_veil.test.ts +++ b/test/moves/aurora_veil.test.ts @@ -42,7 +42,7 @@ describe("Moves - Aurora Veil", () => { .enemyLevel(100) .enemySpecies(SpeciesId.MAGIKARP) .enemyMoveset(MoveId.AURORA_VEIL) - .disableCrits() + .criticalHits(false) .weather(WeatherType.HAIL); }); diff --git a/test/moves/baton_pass.test.ts b/test/moves/baton_pass.test.ts index a010636034a..1b1b0620133 100644 --- a/test/moves/baton_pass.test.ts +++ b/test/moves/baton_pass.test.ts @@ -31,7 +31,7 @@ describe("Moves - Baton Pass", () => { .moveset([MoveId.BATON_PASS, MoveId.NASTY_PLOT, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH) - .disableCrits(); + .criticalHits(false); }); it("transfers all stat stages when player uses it", async () => { diff --git a/test/moves/burning_jealousy.test.ts b/test/moves/burning_jealousy.test.ts index 9e1898d9cd1..ce0a1ce57c9 100644 --- a/test/moves/burning_jealousy.test.ts +++ b/test/moves/burning_jealousy.test.ts @@ -26,7 +26,7 @@ describe("Moves - Burning Jealousy", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.ICE_SCALES) .enemyMoveset([MoveId.HOWL]) diff --git a/test/moves/camouflage.test.ts b/test/moves/camouflage.test.ts index 03364265179..6f28493b1e5 100644 --- a/test/moves/camouflage.test.ts +++ b/test/moves/camouflage.test.ts @@ -28,7 +28,7 @@ describe("Moves - Camouflage", () => { .moveset([MoveId.CAMOUFLAGE]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.REGIELEKI) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.PSYCHIC_TERRAIN); diff --git a/test/moves/chloroblast.test.ts b/test/moves/chloroblast.test.ts index cf5e791d0b9..6c790bac150 100644 --- a/test/moves/chloroblast.test.ts +++ b/test/moves/chloroblast.test.ts @@ -25,7 +25,7 @@ describe("Moves - Chloroblast", () => { game.override .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH); }); diff --git a/test/moves/copycat.test.ts b/test/moves/copycat.test.ts index e11a2262a43..8d135d5e2c7 100644 --- a/test/moves/copycat.test.ts +++ b/test/moves/copycat.test.ts @@ -30,7 +30,7 @@ describe("Moves - Copycat", () => { .moveset([MoveId.COPYCAT, MoveId.SPIKY_SHIELD, MoveId.SWORDS_DANCE, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/defog.test.ts b/test/moves/defog.test.ts index d42682c7f3f..17dc0613e9b 100644 --- a/test/moves/defog.test.ts +++ b/test/moves/defog.test.ts @@ -26,7 +26,7 @@ describe("Moves - Defog", () => { .moveset([MoveId.MIST, MoveId.SAFEGUARD, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.SHUCKLE) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset([MoveId.DEFOG, MoveId.GROWL]); diff --git a/test/moves/doodle.test.ts b/test/moves/doodle.test.ts index d4d09b3a195..2dbd5c08c8f 100644 --- a/test/moves/doodle.test.ts +++ b/test/moves/doodle.test.ts @@ -27,7 +27,7 @@ describe("Moves - Doodle", () => { .moveset([MoveId.SPLASH, MoveId.DOODLE]) .ability(AbilityId.ADAPTABILITY) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/double_team.test.ts b/test/moves/double_team.test.ts index 9a17a542f47..d1f6c5be3e4 100644 --- a/test/moves/double_team.test.ts +++ b/test/moves/double_team.test.ts @@ -26,7 +26,7 @@ describe("Moves - Double Team", () => { game.override .battleStyle("single") .moveset([MoveId.DOUBLE_TEAM]) - .disableCrits() + .criticalHits(false) .ability(AbilityId.BALL_FETCH) .enemySpecies(SpeciesId.SHUCKLE) .enemyAbility(AbilityId.BALL_FETCH) diff --git a/test/moves/dragon_rage.test.ts b/test/moves/dragon_rage.test.ts index f0be6748c7f..c5bed3377fa 100644 --- a/test/moves/dragon_rage.test.ts +++ b/test/moves/dragon_rage.test.ts @@ -51,7 +51,7 @@ describe("Moves - Dragon Rage", () => { }); it("ignores weaknesses", async () => { - game.override.disableCrits(); + game.override.criticalHits(false); vi.spyOn(enemyPokemon, "getTypes").mockReturnValue([PokemonType.DRAGON]); game.move.select(MoveId.DRAGON_RAGE); @@ -61,7 +61,7 @@ describe("Moves - Dragon Rage", () => { }); it("ignores resistances", async () => { - game.override.disableCrits(); + game.override.criticalHits(false); vi.spyOn(enemyPokemon, "getTypes").mockReturnValue([PokemonType.STEEL]); game.move.select(MoveId.DRAGON_RAGE); @@ -71,7 +71,7 @@ describe("Moves - Dragon Rage", () => { }); it("ignores SPATK stat stages", async () => { - game.override.disableCrits(); + game.override.criticalHits(false); partyPokemon.setStatStage(Stat.SPATK, 2); game.move.select(MoveId.DRAGON_RAGE); @@ -81,7 +81,7 @@ describe("Moves - Dragon Rage", () => { }); it("ignores stab", async () => { - game.override.disableCrits(); + game.override.criticalHits(false); vi.spyOn(partyPokemon, "getTypes").mockReturnValue([PokemonType.DRAGON]); game.move.select(MoveId.DRAGON_RAGE); @@ -100,7 +100,7 @@ describe("Moves - Dragon Rage", () => { }); it("ignores damage modification from abilities, for example ICE_SCALES", async () => { - game.override.disableCrits().enemyAbility(AbilityId.ICE_SCALES); + game.override.criticalHits(false).enemyAbility(AbilityId.ICE_SCALES); game.move.select(MoveId.DRAGON_RAGE); await game.phaseInterceptor.to(TurnEndPhase); diff --git a/test/moves/dynamax_cannon.test.ts b/test/moves/dynamax_cannon.test.ts index bf8a63f044c..3febf918de8 100644 --- a/test/moves/dynamax_cannon.test.ts +++ b/test/moves/dynamax_cannon.test.ts @@ -32,13 +32,13 @@ describe("Moves - Dynamax Cannon", () => { game.override .moveset(MoveId.DYNAMAX_CANNON) .startingLevel(200) - .levelCap(10) + .levelCap(100) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyMoveset(MoveId.SPLASH); - vi.spyOn(allMoves[MoveId.DYNAMAX_CANNON], "calculateBattlePower"); + vi.spyOn(dynamaxCannon, "calculateBattlePower"); }); it("should return 100 power against an enemy below level cap", async () => { @@ -54,7 +54,7 @@ describe("Moves - Dynamax Cannon", () => { }); it("should return 100 power against an enemy at level cap", async () => { - game.override.enemyLevel(10); + game.override.enemyLevel(100); await game.classicMode.startBattle([SpeciesId.ETERNATUS]); game.move.select(dynamaxCannon.id); diff --git a/test/moves/encore.test.ts b/test/moves/encore.test.ts index 120d065d528..772b7c96060 100644 --- a/test/moves/encore.test.ts +++ b/test/moves/encore.test.ts @@ -28,7 +28,7 @@ describe("Moves - Encore", () => { .moveset([MoveId.SPLASH, MoveId.ENCORE]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset([MoveId.SPLASH, MoveId.TACKLE]) diff --git a/test/moves/endure.test.ts b/test/moves/endure.test.ts index 799be22ba45..7738f7426fe 100644 --- a/test/moves/endure.test.ts +++ b/test/moves/endure.test.ts @@ -26,7 +26,7 @@ describe("Moves - Endure", () => { .ability(AbilityId.SKILL_LINK) .startingLevel(100) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.NO_GUARD) .enemyMoveset(MoveId.ENDURE); diff --git a/test/moves/entrainment.test.ts b/test/moves/entrainment.test.ts index 0a0cbd3b5f9..65586149492 100644 --- a/test/moves/entrainment.test.ts +++ b/test/moves/entrainment.test.ts @@ -26,7 +26,7 @@ describe("Moves - Entrainment", () => { .moveset([MoveId.SPLASH, MoveId.ENTRAINMENT]) .ability(AbilityId.ADAPTABILITY) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/fairy_lock.test.ts b/test/moves/fairy_lock.test.ts index 74524d67b38..b9f854cdc93 100644 --- a/test/moves/fairy_lock.test.ts +++ b/test/moves/fairy_lock.test.ts @@ -27,7 +27,7 @@ describe("Moves - Fairy Lock", () => { .moveset([MoveId.FAIRY_LOCK, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset([MoveId.SPLASH, MoveId.U_TURN]); diff --git a/test/moves/fake_out.test.ts b/test/moves/fake_out.test.ts index 95f69fe792e..4fc83e5aa07 100644 --- a/test/moves/fake_out.test.ts +++ b/test/moves/fake_out.test.ts @@ -27,7 +27,7 @@ describe("Moves - Fake Out", () => { .enemyMoveset(MoveId.SPLASH) .enemyLevel(10) .startingLevel(1) // prevent LevelUpPhase from happening - .disableCrits(); + .criticalHits(false); }); it("should only work the first turn a pokemon is sent out in a battle", async () => { diff --git a/test/moves/false_swipe.test.ts b/test/moves/false_swipe.test.ts index c98b76f1ef1..bf9c8307c22 100644 --- a/test/moves/false_swipe.test.ts +++ b/test/moves/false_swipe.test.ts @@ -27,7 +27,7 @@ describe("Moves - False Swipe", () => { .ability(AbilityId.BALL_FETCH) .startingLevel(1000) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/fell_stinger.test.ts b/test/moves/fell_stinger.test.ts index d5081a3ba30..0737db9105f 100644 --- a/test/moves/fell_stinger.test.ts +++ b/test/moves/fell_stinger.test.ts @@ -30,7 +30,7 @@ describe("Moves - Fell Stinger", () => { .battleStyle("single") .moveset([MoveId.FELL_STINGER, MoveId.SALT_CURE, MoveId.BIND, MoveId.LEECH_SEED]) .startingLevel(50) - .disableCrits() + .criticalHits(false) .enemyAbility(AbilityId.STURDY) .enemySpecies(SpeciesId.HYPNO) .enemyMoveset(MoveId.SPLASH) diff --git a/test/moves/fissure.test.ts b/test/moves/fissure.test.ts index 231eab427e1..27031a7736d 100644 --- a/test/moves/fissure.test.ts +++ b/test/moves/fissure.test.ts @@ -30,9 +30,9 @@ describe("Moves - Fissure", () => { game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .starterSpecies(SpeciesId.SNORLAX) - .moveset([MoveId.FISSURE]) + .moveset(MoveId.FISSURE) .passiveAbility(AbilityId.BALL_FETCH) .startingLevel(100) .enemySpecies(SpeciesId.SNORLAX) diff --git a/test/moves/flame_burst.test.ts b/test/moves/flame_burst.test.ts index e994dc3e568..0a378df1077 100644 --- a/test/moves/flame_burst.test.ts +++ b/test/moves/flame_burst.test.ts @@ -38,12 +38,12 @@ describe("Moves - Flame Burst", () => { game.override .battleStyle("double") .moveset([MoveId.FLAME_BURST, MoveId.SPLASH]) - .disableCrits() + .criticalHits(false) .ability(AbilityId.UNNERVE) .startingWave(4) .enemySpecies(SpeciesId.SHUCKLE) .enemyAbility(AbilityId.BALL_FETCH) - .enemyMoveset([MoveId.SPLASH]); + .enemyMoveset(MoveId.SPLASH); }); it("inflicts damage to the target's ally equal to 1/16 of its max HP", async () => { diff --git a/test/moves/foresight.test.ts b/test/moves/foresight.test.ts index a30b4d6a444..96a341582f9 100644 --- a/test/moves/foresight.test.ts +++ b/test/moves/foresight.test.ts @@ -22,7 +22,7 @@ describe("Moves - Foresight", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.GASTLY) .enemyMoveset(MoveId.SPLASH) .enemyLevel(5) diff --git a/test/moves/forests_curse.test.ts b/test/moves/forests_curse.test.ts index 77fec5d277d..81ced674a33 100644 --- a/test/moves/forests_curse.test.ts +++ b/test/moves/forests_curse.test.ts @@ -26,7 +26,7 @@ describe("Moves - Forest's Curse", () => { .moveset([MoveId.FORESTS_CURSE, MoveId.TRICK_OR_TREAT]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/fusion_bolt.test.ts b/test/moves/fusion_bolt.test.ts index cb3d75f56eb..c92a483a497 100644 --- a/test/moves/fusion_bolt.test.ts +++ b/test/moves/fusion_bolt.test.ts @@ -31,7 +31,7 @@ describe("Moves - Fusion Bolt", () => { .enemyMoveset(MoveId.SPLASH) .battleStyle("single") .startingWave(97) - .disableCrits(); + .criticalHits(false); }); it("should not make contact", async () => { diff --git a/test/moves/fusion_flare.test.ts b/test/moves/fusion_flare.test.ts index df6d390686f..c0df347fcce 100644 --- a/test/moves/fusion_flare.test.ts +++ b/test/moves/fusion_flare.test.ts @@ -31,7 +31,7 @@ describe("Moves - Fusion Flare", () => { .enemyMoveset(MoveId.REST) .battleStyle("single") .startingWave(97) - .disableCrits(); + .criticalHits(false); }); it("should thaw freeze status condition", async () => { diff --git a/test/moves/fusion_flare_bolt.test.ts b/test/moves/fusion_flare_bolt.test.ts index f9e55b72f6c..1967e9f12d1 100644 --- a/test/moves/fusion_flare_bolt.test.ts +++ b/test/moves/fusion_flare_bolt.test.ts @@ -40,7 +40,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { .enemyMoveset(MoveId.REST) .battleStyle("double") .startingWave(97) - .disableCrits(); + .criticalHits(false); vi.spyOn(fusionFlare, "calculateBattlePower"); vi.spyOn(fusionBolt, "calculateBattlePower"); diff --git a/test/moves/gigaton_hammer.test.ts b/test/moves/gigaton_hammer.test.ts index e8c9d316372..d80743f4ed0 100644 --- a/test/moves/gigaton_hammer.test.ts +++ b/test/moves/gigaton_hammer.test.ts @@ -29,7 +29,7 @@ describe("Moves - Gigaton Hammer", () => { .startingLevel(10) .enemyLevel(100) .enemyMoveset(MoveId.SPLASH) - .disableCrits(); + .criticalHits(false); }); it("can't be used two turns in a row", async () => { diff --git a/test/moves/glaive_rush.test.ts b/test/moves/glaive_rush.test.ts index 2aead405fb6..0b6f30da71a 100644 --- a/test/moves/glaive_rush.test.ts +++ b/test/moves/glaive_rush.test.ts @@ -24,7 +24,7 @@ describe("Moves - Glaive Rush", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset([MoveId.GLAIVE_RUSH]) diff --git a/test/moves/grudge.test.ts b/test/moves/grudge.test.ts index 63018a419e3..36030c88bad 100644 --- a/test/moves/grudge.test.ts +++ b/test/moves/grudge.test.ts @@ -26,7 +26,7 @@ describe("Moves - Grudge", () => { .moveset([MoveId.EMBER, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.SHEDINJA) .enemyAbility(AbilityId.WONDER_GUARD) .enemyMoveset([MoveId.GRUDGE, MoveId.SPLASH]); diff --git a/test/moves/heal_block.test.ts b/test/moves/heal_block.test.ts index 39e8efea827..77a10927930 100644 --- a/test/moves/heal_block.test.ts +++ b/test/moves/heal_block.test.ts @@ -33,7 +33,7 @@ describe("Moves - Heal Block", () => { .ability(AbilityId.NO_GUARD) .enemyAbility(AbilityId.BALL_FETCH) .enemySpecies(SpeciesId.BLISSEY) - .disableCrits(); + .criticalHits(false); }); it("shouldn't stop damage from HP-drain attacks, just HP restoration", async () => { diff --git a/test/moves/instruct.test.ts b/test/moves/instruct.test.ts index 6167b6fc4dd..d12859301b6 100644 --- a/test/moves/instruct.test.ts +++ b/test/moves/instruct.test.ts @@ -42,7 +42,7 @@ describe("Moves - Instruct", () => { .passiveAbility(AbilityId.NO_GUARD) .enemyLevel(100) .startingLevel(100) - .disableCrits(); + .criticalHits(false); }); it("should repeat target's last used move", async () => { diff --git a/test/moves/jaw_lock.test.ts b/test/moves/jaw_lock.test.ts index e0815a4c3c9..4e103c14f98 100644 --- a/test/moves/jaw_lock.test.ts +++ b/test/moves/jaw_lock.test.ts @@ -36,7 +36,7 @@ describe("Moves - Jaw Lock", () => { .moveset([MoveId.JAW_LOCK, MoveId.SPLASH]) .startingLevel(100) .enemyLevel(100) - .disableCrits(); + .criticalHits(false); }); it("should trap the move's user and target", async () => { diff --git a/test/moves/lash_out.test.ts b/test/moves/lash_out.test.ts index c0c0881b340..33a58914978 100644 --- a/test/moves/lash_out.test.ts +++ b/test/moves/lash_out.test.ts @@ -25,7 +25,7 @@ describe("Moves - Lash Out", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.FUR_COAT) .enemyMoveset([MoveId.GROWL]) diff --git a/test/moves/last-resort.test.ts b/test/moves/last-resort.test.ts index 3b57bc5cf61..3cb98a0cd92 100644 --- a/test/moves/last-resort.test.ts +++ b/test/moves/last-resort.test.ts @@ -35,7 +35,7 @@ describe("Moves - Last Resort", () => { game.override .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/last_respects.test.ts b/test/moves/last_respects.test.ts index 08256d3ebfb..14aa5ad3309 100644 --- a/test/moves/last_respects.test.ts +++ b/test/moves/last_respects.test.ts @@ -32,7 +32,7 @@ describe("Moves - Last Respects", () => { basePower = move.power; game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .moveset([MoveId.LAST_RESPECTS, MoveId.EXPLOSION, MoveId.LUNAR_DANCE]) .ability(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH) diff --git a/test/moves/light_screen.test.ts b/test/moves/light_screen.test.ts index 404d30920c7..8c436aacf6a 100644 --- a/test/moves/light_screen.test.ts +++ b/test/moves/light_screen.test.ts @@ -41,7 +41,7 @@ describe("Moves - Light Screen", () => { .enemyLevel(100) .enemySpecies(SpeciesId.MAGIKARP) .enemyMoveset(MoveId.LIGHT_SCREEN) - .disableCrits(); + .criticalHits(false); }); it("reduces damage of special attacks by half in a single battle", async () => { diff --git a/test/moves/lucky_chant.test.ts b/test/moves/lucky_chant.test.ts index 58944027398..a57d231134b 100644 --- a/test/moves/lucky_chant.test.ts +++ b/test/moves/lucky_chant.test.ts @@ -1,11 +1,10 @@ import { AbilityId } from "#enums/ability-id"; -import { BattlerTagType } from "#app/enums/battler-tag-type"; +import { BattlerTagType } from "#enums/battler-tag-type"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; -import { BerryPhase } from "#app/phases/berry-phase"; -import { TurnEndPhase } from "#app/phases/turn-end-phase"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import GameManager from "#test/testUtils/gameManager"; +import { BattlerIndex } from "#enums/battler-index"; describe("Moves - Lucky Chant", () => { let phaserGame: Phaser.Game; @@ -29,74 +28,91 @@ describe("Moves - Lucky Chant", () => { .moveset([MoveId.LUCKY_CHANT, MoveId.SPLASH, MoveId.FOLLOW_ME]) .enemySpecies(SpeciesId.SNORLAX) .enemyAbility(AbilityId.INSOMNIA) - .enemyMoveset([MoveId.FLOWER_TRICK]) + .enemyMoveset(MoveId.TACKLE) .startingLevel(100) .enemyLevel(100); }); - it("should prevent critical hits from moves", async () => { + it("should prevent random critical hits from moves", async () => { + game.override.criticalHits(true); await game.classicMode.startBattle([SpeciesId.CHARIZARD]); - const playerPokemon = game.scene.getPlayerPokemon()!; + const charizard = game.scene.getPlayerPokemon()!; + expect(charizard).toBeDefined(); + const critSpy = vi.spyOn(charizard, "getCriticalHitResult"); // called on the defender (ie player) game.move.select(MoveId.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); - await game.phaseInterceptor.to(TurnEndPhase); - - const firstTurnDamage = playerPokemon.getMaxHp() - playerPokemon.hp; + expect(critSpy).toHaveLastReturnedWith(true); + const firstTurnDamage = charizard.getInverseHp(); game.move.select(MoveId.LUCKY_CHANT); + await game.phaseInterceptor.to("TurnEndPhase"); - await game.phaseInterceptor.to(BerryPhase, false); + expect(critSpy).toHaveLastReturnedWith(false); + const secondTurnDamage = charizard.getInverseHp() - firstTurnDamage; + expect(secondTurnDamage).toBeLessThan(firstTurnDamage); + }); - const secondTurnDamage = playerPokemon.getMaxHp() - playerPokemon.hp - firstTurnDamage; + it("should prevent guaranteed critical hits from moves", async () => { + game.override.enemyMoveset(MoveId.FLOWER_TRICK); + await game.classicMode.startBattle([SpeciesId.CHARIZARD]); + + const charizard = game.scene.getPlayerPokemon()!; + expect(charizard).toBeDefined(); + + game.move.select(MoveId.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); + + const firstTurnDamage = charizard.getInverseHp(); + + game.move.select(MoveId.LUCKY_CHANT); + await game.phaseInterceptor.to("TurnEndPhase"); + + const secondTurnDamage = charizard.getInverseHp() - firstTurnDamage; expect(secondTurnDamage).toBeLessThan(firstTurnDamage); }); it("should prevent critical hits against the user's ally", async () => { - game.override.battleStyle("double"); + game.override.battleStyle("double").criticalHits(true); await game.classicMode.startBattle([SpeciesId.CHARIZARD, SpeciesId.BLASTOISE]); - const playerPokemon = game.scene.getPlayerField(); + const charizard = game.scene.getPlayerPokemon()!; + expect(charizard).toBeDefined(); - game.move.select(MoveId.FOLLOW_ME); - game.move.select(MoveId.SPLASH, 1); + game.move.select(MoveId.FOLLOW_ME, BattlerIndex.PLAYER); + game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2); + await game.phaseInterceptor.to("TurnEndPhase"); - await game.phaseInterceptor.to(TurnEndPhase); + const firstTurnDamage = charizard.getInverseHp(); - const firstTurnDamage = playerPokemon[0].getMaxHp() - playerPokemon[0].hp; + game.move.select(MoveId.FOLLOW_ME, BattlerIndex.PLAYER); + game.move.select(MoveId.LUCKY_CHANT, BattlerIndex.PLAYER_2); - game.move.select(MoveId.FOLLOW_ME); - game.move.select(MoveId.LUCKY_CHANT, 1); + await game.phaseInterceptor.to("TurnEndPhase"); - await game.phaseInterceptor.to(BerryPhase, false); - - const secondTurnDamage = playerPokemon[0].getMaxHp() - playerPokemon[0].hp - firstTurnDamage; + const secondTurnDamage = charizard.getInverseHp() - firstTurnDamage; expect(secondTurnDamage).toBeLessThan(firstTurnDamage); }); it("should prevent critical hits from field effects", async () => { - game.override.enemyMoveset([MoveId.TACKLE]); - await game.classicMode.startBattle([SpeciesId.CHARIZARD]); - const playerPokemon = game.scene.getPlayerPokemon()!; - const enemyPokemon = game.scene.getEnemyPokemon()!; - - enemyPokemon.addTag(BattlerTagType.ALWAYS_CRIT, 2, MoveId.NONE, 0); + const charizard = game.field.getPlayerPokemon(); + const snorlax = game.field.getEnemyPokemon(); + snorlax.addTag(BattlerTagType.ALWAYS_CRIT, 2, MoveId.NONE, 0); game.move.select(MoveId.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); - await game.phaseInterceptor.to(TurnEndPhase); - - const firstTurnDamage = playerPokemon.getMaxHp() - playerPokemon.hp; + const firstTurnDamage = charizard.getInverseHp(); game.move.select(MoveId.LUCKY_CHANT); + await game.phaseInterceptor.to("TurnEndPhase"); - await game.phaseInterceptor.to(BerryPhase, false); - - const secondTurnDamage = playerPokemon.getMaxHp() - playerPokemon.hp - firstTurnDamage; + const secondTurnDamage = charizard.getInverseHp() - firstTurnDamage; expect(secondTurnDamage).toBeLessThan(firstTurnDamage); }); }); diff --git a/test/moves/magic_coat.test.ts b/test/moves/magic_coat.test.ts index 7bbd4779da6..566c206f1ba 100644 --- a/test/moves/magic_coat.test.ts +++ b/test/moves/magic_coat.test.ts @@ -31,7 +31,7 @@ describe("Moves - Magic Coat", () => { game.override .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.MAGIC_COAT); diff --git a/test/moves/magnet_rise.test.ts b/test/moves/magnet_rise.test.ts index 496c0609411..d8fc6a74225 100644 --- a/test/moves/magnet_rise.test.ts +++ b/test/moves/magnet_rise.test.ts @@ -28,7 +28,7 @@ describe("Moves - Magnet Rise", () => { .starterSpecies(SpeciesId.MAGNEZONE) .enemySpecies(SpeciesId.RATTATA) .enemyMoveset(MoveId.DRILL_RUN) - .disableCrits() + .criticalHits(false) .enemyLevel(1) .moveset([moveToUse, MoveId.SPLASH, MoveId.GRAVITY, MoveId.BATON_PASS]); }); diff --git a/test/moves/metal_burst.test.ts b/test/moves/metal_burst.test.ts index bcb9d938985..4d07d59c16d 100644 --- a/test/moves/metal_burst.test.ts +++ b/test/moves/metal_burst.test.ts @@ -28,7 +28,7 @@ describe("Moves - Metal Burst", () => { .ability(AbilityId.PURE_POWER) .startingLevel(10) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.PICHU) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.TACKLE); diff --git a/test/moves/miracle_eye.test.ts b/test/moves/miracle_eye.test.ts index 1238ae6a650..9281931621f 100644 --- a/test/moves/miracle_eye.test.ts +++ b/test/moves/miracle_eye.test.ts @@ -23,7 +23,7 @@ describe("Moves - Miracle Eye", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.UMBREON) .enemyMoveset(MoveId.SPLASH) .enemyLevel(5) diff --git a/test/moves/mirror_move.test.ts b/test/moves/mirror_move.test.ts index fe40402e5a2..03de93c56dc 100644 --- a/test/moves/mirror_move.test.ts +++ b/test/moves/mirror_move.test.ts @@ -28,7 +28,7 @@ describe("Moves - Mirror Move", () => { .moveset([MoveId.MIRROR_MOVE, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/mist.test.ts b/test/moves/mist.test.ts index ea4eb95a1eb..4191aae0a46 100644 --- a/test/moves/mist.test.ts +++ b/test/moves/mist.test.ts @@ -26,7 +26,7 @@ describe("Moves - Mist", () => { .moveset([MoveId.MIST, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.SNORLAX) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.GROWL); diff --git a/test/moves/moongeist_beam.test.ts b/test/moves/moongeist_beam.test.ts index 28bd3f70daa..367fe67cff5 100644 --- a/test/moves/moongeist_beam.test.ts +++ b/test/moves/moongeist_beam.test.ts @@ -27,7 +27,7 @@ describe("Moves - Moongeist Beam", () => { .ability(AbilityId.BALL_FETCH) .startingLevel(200) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.STURDY) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/multi_target.test.ts b/test/moves/multi_target.test.ts index 139b669da7b..bc6b24c474d 100644 --- a/test/moves/multi_target.test.ts +++ b/test/moves/multi_target.test.ts @@ -24,7 +24,7 @@ describe("Multi-target damage reduction", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .disableCrits() + .criticalHits(false) .battleStyle("double") .enemyLevel(100) .startingLevel(100) diff --git a/test/moves/order_up.test.ts b/test/moves/order_up.test.ts index adf37c0719e..fc505809fe6 100644 --- a/test/moves/order_up.test.ts +++ b/test/moves/order_up.test.ts @@ -30,7 +30,7 @@ describe("Moves - Order Up", () => { .moveset(MoveId.ORDER_UP) .ability(AbilityId.COMMANDER) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.SNORLAX) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH) diff --git a/test/moves/pollen_puff.test.ts b/test/moves/pollen_puff.test.ts index e6dcd2c41d0..cd0a138e96c 100644 --- a/test/moves/pollen_puff.test.ts +++ b/test/moves/pollen_puff.test.ts @@ -26,7 +26,7 @@ describe("Moves - Pollen Puff", () => { .moveset([MoveId.POLLEN_PUFF]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/psycho_shift.test.ts b/test/moves/psycho_shift.test.ts index f92eea5fd38..e4eb2bd8ef6 100644 --- a/test/moves/psycho_shift.test.ts +++ b/test/moves/psycho_shift.test.ts @@ -27,7 +27,7 @@ describe("Moves - Psycho Shift", () => { .ability(AbilityId.BALL_FETCH) .statusEffect(StatusEffect.POISON) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyLevel(20) .enemyAbility(AbilityId.SYNCHRONIZE) diff --git a/test/moves/reflect.test.ts b/test/moves/reflect.test.ts index 5e10de42a3c..ef8c80070bf 100644 --- a/test/moves/reflect.test.ts +++ b/test/moves/reflect.test.ts @@ -41,7 +41,7 @@ describe("Moves - Reflect", () => { .enemyLevel(100) .enemySpecies(SpeciesId.MAGIKARP) .enemyMoveset(MoveId.REFLECT) - .disableCrits(); + .criticalHits(false); }); it("reduces damage of physical attacks by half in a single battle", async () => { diff --git a/test/moves/reflect_type.test.ts b/test/moves/reflect_type.test.ts index 86c70ed62f1..0915069764c 100644 --- a/test/moves/reflect_type.test.ts +++ b/test/moves/reflect_type.test.ts @@ -22,7 +22,11 @@ describe("Moves - Reflect Type", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.ability(AbilityId.BALL_FETCH).battleStyle("single").disableCrits().enemyAbility(AbilityId.BALL_FETCH); + game.override + .ability(AbilityId.BALL_FETCH) + .battleStyle("single") + .criticalHits(false) + .enemyAbility(AbilityId.BALL_FETCH); }); it("will make the user Normal/Grass if targetting a typeless Pokemon affected by Forest's Curse", async () => { diff --git a/test/moves/retaliate.test.ts b/test/moves/retaliate.test.ts index 16261c71dc3..dd5029a7c83 100644 --- a/test/moves/retaliate.test.ts +++ b/test/moves/retaliate.test.ts @@ -32,7 +32,7 @@ describe("Moves - Retaliate", () => { .enemyLevel(100) .moveset([MoveId.RETALIATE, MoveId.SPLASH]) .startingLevel(80) - .disableCrits(); + .criticalHits(false); }); it("increases power if ally died previous turn", async () => { diff --git a/test/moves/revival_blessing.test.ts b/test/moves/revival_blessing.test.ts index f22c0467378..1f2ba869e4f 100644 --- a/test/moves/revival_blessing.test.ts +++ b/test/moves/revival_blessing.test.ts @@ -28,7 +28,7 @@ describe("Moves - Revival Blessing", () => { .moveset([MoveId.SPLASH, MoveId.REVIVAL_BLESSING, MoveId.MEMENTO]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/role_play.test.ts b/test/moves/role_play.test.ts index 3c3f0afb1a3..cfa38ed38b7 100644 --- a/test/moves/role_play.test.ts +++ b/test/moves/role_play.test.ts @@ -26,7 +26,7 @@ describe("Moves - Role Play", () => { .moveset([MoveId.SPLASH, MoveId.ROLE_PLAY]) .ability(AbilityId.ADAPTABILITY) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/rollout.test.ts b/test/moves/rollout.test.ts index b9faf438ae5..9639a2e5408 100644 --- a/test/moves/rollout.test.ts +++ b/test/moves/rollout.test.ts @@ -24,7 +24,7 @@ describe("Moves - Rollout", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .disableCrits() + .criticalHits(false) .battleStyle("single") .starterSpecies(SpeciesId.RATTATA) .ability(AbilityId.BALL_FETCH) diff --git a/test/moves/round.test.ts b/test/moves/round.test.ts index 630137b7dce..8e07a819131 100644 --- a/test/moves/round.test.ts +++ b/test/moves/round.test.ts @@ -28,7 +28,7 @@ describe("Moves - Round", () => { .moveset([MoveId.SPLASH, MoveId.ROUND]) .ability(AbilityId.BALL_FETCH) .battleStyle("double") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset([MoveId.SPLASH, MoveId.ROUND]) diff --git a/test/moves/scale_shot.test.ts b/test/moves/scale_shot.test.ts index a5872579003..e2c86091378 100644 --- a/test/moves/scale_shot.test.ts +++ b/test/moves/scale_shot.test.ts @@ -31,7 +31,7 @@ describe("Moves - Scale Shot", () => { game.override .moveset([MoveId.SCALE_SHOT]) .battleStyle("single") - .disableCrits() + .criticalHits(false) .ability(AbilityId.NO_GUARD) .passiveAbility(AbilityId.SKILL_LINK) .enemyMoveset(MoveId.SPLASH) diff --git a/test/moves/secret_power.test.ts b/test/moves/secret_power.test.ts index 39498782b58..adb91162ae8 100644 --- a/test/moves/secret_power.test.ts +++ b/test/moves/secret_power.test.ts @@ -33,7 +33,7 @@ describe("Moves - Secret Power", () => { .moveset([MoveId.SECRET_POWER]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyLevel(60) .enemyAbility(AbilityId.BALL_FETCH); diff --git a/test/moves/simple_beam.test.ts b/test/moves/simple_beam.test.ts index 94609c3c4ac..f8549944ed9 100644 --- a/test/moves/simple_beam.test.ts +++ b/test/moves/simple_beam.test.ts @@ -25,7 +25,7 @@ describe("Moves - Simple Beam", () => { .moveset([MoveId.SPLASH, MoveId.SIMPLE_BEAM]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/sketch.test.ts b/test/moves/sketch.test.ts index c6fb7b4a32a..5e9ce9a9e84 100644 --- a/test/moves/sketch.test.ts +++ b/test/moves/sketch.test.ts @@ -30,7 +30,7 @@ describe("Moves - Sketch", () => { game.override .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.SHUCKLE) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/skill_swap.test.ts b/test/moves/skill_swap.test.ts index c4fb6005e28..7c2e6f5fd0a 100644 --- a/test/moves/skill_swap.test.ts +++ b/test/moves/skill_swap.test.ts @@ -26,7 +26,7 @@ describe("Moves - Skill Swap", () => { .moveset([MoveId.SPLASH, MoveId.SKILL_SWAP]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/sleep_talk.test.ts b/test/moves/sleep_talk.test.ts index 1d9aec77ea2..84e9d509a03 100644 --- a/test/moves/sleep_talk.test.ts +++ b/test/moves/sleep_talk.test.ts @@ -29,7 +29,7 @@ describe("Moves - Sleep Talk", () => { .statusEffect(StatusEffect.SLEEP) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH) diff --git a/test/moves/spectral_thief.test.ts b/test/moves/spectral_thief.test.ts index 808e9174caf..e7b19eda506 100644 --- a/test/moves/spectral_thief.test.ts +++ b/test/moves/spectral_thief.test.ts @@ -29,7 +29,8 @@ describe("Moves - Spectral Thief", () => { .enemyMoveset(MoveId.SPLASH) .enemyAbility(AbilityId.BALL_FETCH) .moveset([MoveId.SPECTRAL_THIEF, MoveId.SPLASH]) - .ability(AbilityId.BALL_FETCH).disableCrits; + .ability(AbilityId.BALL_FETCH) + .criticalHits(false); }); it("should steal max possible positive stat changes and ignore negative ones.", async () => { diff --git a/test/moves/struggle.test.ts b/test/moves/struggle.test.ts index 2175dc557b3..e1373b29d33 100644 --- a/test/moves/struggle.test.ts +++ b/test/moves/struggle.test.ts @@ -25,7 +25,7 @@ describe("Moves - Struggle", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/synchronoise.test.ts b/test/moves/synchronoise.test.ts index 176137c8180..9068a40693b 100644 --- a/test/moves/synchronoise.test.ts +++ b/test/moves/synchronoise.test.ts @@ -26,7 +26,7 @@ describe("Moves - Synchronoise", () => { .moveset([MoveId.SYNCHRONOISE]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/tackle.test.ts b/test/moves/tackle.test.ts index 96faa27ad38..f3c0db85a39 100644 --- a/test/moves/tackle.test.ts +++ b/test/moves/tackle.test.ts @@ -31,7 +31,7 @@ describe("Moves - Tackle", () => { .startingWave(97) .moveset([moveToUse]) .enemyMoveset(MoveId.GROWTH) - .disableCrits(); + .criticalHits(false); }); it("TACKLE against ghost", async () => { diff --git a/test/moves/tar_shot.test.ts b/test/moves/tar_shot.test.ts index fefd1a30e1e..8d5ea4b3582 100644 --- a/test/moves/tar_shot.test.ts +++ b/test/moves/tar_shot.test.ts @@ -30,7 +30,7 @@ describe("Moves - Tar Shot", () => { .enemySpecies(SpeciesId.TANGELA) .enemyLevel(1000) .moveset([MoveId.TAR_SHOT, MoveId.FIRE_PUNCH]) - .disableCrits(); + .criticalHits(false); }); it("lowers the target's Speed stat by one stage and doubles the effectiveness of Fire-type moves used on the target", async () => { diff --git a/test/moves/tera_blast.test.ts b/test/moves/tera_blast.test.ts index 3372fd21539..fbebd428cfd 100644 --- a/test/moves/tera_blast.test.ts +++ b/test/moves/tera_blast.test.ts @@ -35,7 +35,7 @@ describe("Moves - Tera Blast", () => { game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .starterSpecies(SpeciesId.FEEBAS) .moveset([MoveId.TERA_BLAST]) .ability(AbilityId.BALL_FETCH) diff --git a/test/moves/trick_or_treat.test.ts b/test/moves/trick_or_treat.test.ts index 366c6ee60fe..861115e2503 100644 --- a/test/moves/trick_or_treat.test.ts +++ b/test/moves/trick_or_treat.test.ts @@ -26,7 +26,7 @@ describe("Moves - Trick Or Treat", () => { .moveset([MoveId.FORESTS_CURSE, MoveId.TRICK_OR_TREAT]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/moves/u_turn.test.ts b/test/moves/u_turn.test.ts index 5d4cad3a5ad..a2e29899ad8 100644 --- a/test/moves/u_turn.test.ts +++ b/test/moves/u_turn.test.ts @@ -29,7 +29,7 @@ describe("Moves - U-turn", () => { .startingWave(97) .moveset([MoveId.U_TURN]) .enemyMoveset(MoveId.SPLASH) - .disableCrits(); + .criticalHits(false); }); it("triggers regenerator a single time when a regenerator user switches out with u-turn", async () => { diff --git a/test/moves/upper_hand.test.ts b/test/moves/upper_hand.test.ts index e3d490ba6fa..fe38e75cf9a 100644 --- a/test/moves/upper_hand.test.ts +++ b/test/moves/upper_hand.test.ts @@ -27,7 +27,7 @@ describe("Moves - Upper Hand", () => { .moveset(MoveId.UPPER_HAND) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.QUICK_ATTACK) diff --git a/test/moves/will_o_wisp.test.ts b/test/moves/will_o_wisp.test.ts index 40495009d2a..336e84129c5 100644 --- a/test/moves/will_o_wisp.test.ts +++ b/test/moves/will_o_wisp.test.ts @@ -27,7 +27,7 @@ describe("Moves - Will-O-Wisp", () => { .moveset([MoveId.WILL_O_WISP, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/phases/form-change-phase.test.ts b/test/phases/form-change-phase.test.ts index 99c072bdafe..b025f72f5e4 100644 --- a/test/phases/form-change-phase.test.ts +++ b/test/phases/form-change-phase.test.ts @@ -28,7 +28,7 @@ describe("Form Change Phase", () => { .moveset([MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); diff --git a/test/phases/frenzy-move-reset.test.ts b/test/phases/frenzy-move-reset.test.ts index 878c58d8666..9c59e1da968 100644 --- a/test/phases/frenzy-move-reset.test.ts +++ b/test/phases/frenzy-move-reset.test.ts @@ -26,7 +26,7 @@ describe("Frenzy Move Reset", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .disableCrits() + .criticalHits(false) .starterSpecies(SpeciesId.MAGIKARP) .moveset(MoveId.THRASH) .statusEffect(StatusEffect.PARALYSIS) diff --git a/test/phases/game-over-phase.test.ts b/test/phases/game-over-phase.test.ts index d45330697fc..008f9fb68e8 100644 --- a/test/phases/game-over-phase.test.ts +++ b/test/phases/game-over-phase.test.ts @@ -28,7 +28,7 @@ describe("Game Over Phase", () => { .moveset([MoveId.MEMENTO, MoveId.ICE_BEAM, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .disableCrits() + .criticalHits(false) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH) .startingWave(200) diff --git a/test/testUtils/gameManagerUtils.ts b/test/testUtils/gameManagerUtils.ts index d582f8c04e3..eda3921e9e0 100644 --- a/test/testUtils/gameManagerUtils.ts +++ b/test/testUtils/gameManagerUtils.ts @@ -97,7 +97,7 @@ export function waitUntil(truth): Promise { }); } -/** Get the index of `move` from the moveset of the pokemon on the player's field at location `pokemonIndex` */ +/** Get the index of `move` from the moveset of the pokemon on the player's field at location `pokemonIndex`. */ export function getMovePosition(scene: BattleScene, pokemonIndex: 0 | 1, move: MoveId): number { const playerPokemon = scene.getPlayerField()[pokemonIndex]; const moveSet = playerPokemon.getMoveset(); diff --git a/test/testUtils/helpers/dailyModeHelper.ts b/test/testUtils/helpers/dailyModeHelper.ts index 8ee03ce5f89..4672d4dc787 100644 --- a/test/testUtils/helpers/dailyModeHelper.ts +++ b/test/testUtils/helpers/dailyModeHelper.ts @@ -16,8 +16,9 @@ export class DailyModeHelper extends GameManagerHelper { /** * Runs the daily game to the summon phase. * @returns A promise that resolves when the summon phase is reached. + * @remarks Please do not use for starting normal battles - use {@linkcode startBattle} instead */ - async runToSummon() { + async runToSummon(): Promise { await this.game.runToTitle(); if (this.game.override.disableShinies) { @@ -45,7 +46,7 @@ export class DailyModeHelper extends GameManagerHelper { * Transitions to the start of a battle. * @returns A promise that resolves when the battle is started. */ - async startBattle() { + async startBattle(): Promise { await this.runToSummon(); if (this.game.scene.battleStyle === BattleStyle.SWITCH) { diff --git a/test/testUtils/helpers/overridesHelper.ts b/test/testUtils/helpers/overridesHelper.ts index 9869c7450e2..3bf0fbbda47 100644 --- a/test/testUtils/helpers/overridesHelper.ts +++ b/test/testUtils/helpers/overridesHelper.ts @@ -243,12 +243,15 @@ export class OverridesHelper extends GameManagerHelper { } /** - * Override each wave to not have critical hits + * Force random critical hit rolls to always or never suceed. + * @param crits - `true` to guarantee crits on eligible moves, `false` to force rolls to fail, `null` to disable override + * @remarks This does not bypass effects that guarantee or block critical hits; it merely mocks the chance-based rolls. * @returns `this` */ - public disableCrits(): this { - vi.spyOn(Overrides, "NEVER_CRIT_OVERRIDE", "get").mockReturnValue(true); - this.log("Critical hits are disabled!"); + public criticalHits(crits: boolean | null): this { + vi.spyOn(Overrides, "CRITICAL_HIT_OVERRIDE", "get").mockReturnValue(crits); + const freq = crits === true ? "always" : crits === false ? "never" : "randomly"; + this.log(`Critical hit rolls set to ${freq} succeed!`); return this; } diff --git a/test/testUtils/phaseInterceptor.ts b/test/testUtils/phaseInterceptor.ts index 34fba2c145d..9d046fc85ba 100644 --- a/test/testUtils/phaseInterceptor.ts +++ b/test/testUtils/phaseInterceptor.ts @@ -210,7 +210,7 @@ export default class PhaseInterceptor { /** * Method to transition to a target phase. * @param phaseTo - The phase to transition to. - * @param runTarget - Whether or not to run the target phase. + * @param runTarget - Whether or not to run the target phase; default `true`. * @returns A promise that resolves when the transition is complete. */ async to(phaseTo: PhaseInterceptorPhase, runTarget = true): Promise { @@ -439,7 +439,7 @@ export default class PhaseInterceptor { * @param mode - The mode of the UI. * @param callback - The callback function to execute. * @param expireFn - The function to determine if the prompt has expired. - * @param awaitingActionInput + * @param awaitingActionInput - ???; default `false` */ addToNextPrompt( phaseTarget: string, From e3108603e36f6f8c10100c8adb6619c3f623f755 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:57:51 -0400 Subject: [PATCH 50/51] [Refactor] Rework evolution conditions and descriptions (#5679) * Refactor evo conditions and descriptions * Fix test * Fix Shedinja * Simplify Gimmighoul evolution * Primeape and Stantler evolve by using their move 10 times * Basculin white stripe evolves by taking 294 recoil damage * Primeape and Stantler use modifiers for tracking * Basculin uses modifier too * Remove evo count from pokemon data * No more evo counter data, Gallade/Froslass * Fix allmoves import * Clamperl * Struggle shouldn't count for Basc recoil * Change to nicer type * Apply Benjie's suggestions Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Address formatting * Undo new evolution changes * Remove unused imports * Fix speciesid * Fixed up descriptions a little * Change a key name * Fix Gimmighoul * Apply Biome * Apply Biome unsafe fixes * Review suggestions - Convert `EvoCondKey` enum to `const` object - Use early returns in `SpeciesEvolutionCondition#description` and `SpeciesFormEvolution#description` - Replace `!!x.find` with `x.some` and `y.indexOf() > -1` with `y.includes()` - Implement `coerceArray` - Fix Shelmet evolution condition checking for Shelmet and not Karrablast - Remove unnecessary type casting in `battle-scene.ts` * Remove leftover enforce func loop * Fix circular imports issue - `getPokemonSpecies` moved to `src/utils/pokemon-utils.ts` - `allSpecies` moved to `src/data/data-lists.ts` --------- Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/battle-scene.ts | 10 +- src/data/balance/pokemon-evolutions.ts | 665 +++++++++--------- src/data/challenge.ts | 3 +- src/data/daily-run.ts | 3 +- src/data/data-lists.ts | 2 + src/data/egg.ts | 2 +- .../encounters/absolute-avarice-encounter.ts | 2 +- .../an-offer-you-cant-refuse-encounter.ts | 2 +- .../encounters/clowning-around-encounter.ts | 2 +- .../encounters/dancing-lessons-encounter.ts | 2 +- .../encounters/dark-deal-encounter.ts | 2 +- .../encounters/delibirdy-encounter.ts | 2 +- .../encounters/fiery-fallout-encounter.ts | 2 +- .../encounters/fun-and-games-encounter.ts | 2 +- .../global-trade-system-encounter.ts | 3 +- .../encounters/lost-at-sea-encounter.ts | 2 +- .../encounters/mysterious-chest-encounter.ts | 2 +- .../encounters/safari-zone-encounter.ts | 2 +- .../slumbering-snorlax-encounter.ts | 2 +- .../the-expert-pokemon-breeder-encounter.ts | 2 +- .../the-pokemon-salesman-encounter.ts | 2 +- .../encounters/the-strong-stuff-encounter.ts | 2 +- .../the-winstrate-challenge-encounter.ts | 2 +- .../encounters/trash-to-treasure-encounter.ts | 2 +- .../encounters/weird-dream-encounter.ts | 3 +- .../mystery-encounter-requirements.ts | 66 -- .../utils/encounter-phase-utils.ts | 2 +- .../utils/encounter-pokemon-utils.ts | 2 +- src/data/pokemon-species.ts | 22 +- src/data/trainers/trainer-config.ts | 2 +- src/field/arena.ts | 2 +- src/field/pokemon.ts | 55 +- src/field/trainer.ts | 2 +- src/game-mode.ts | 2 +- src/modifier/modifier-type.ts | 35 +- src/modifier/modifier.ts | 57 +- src/phases/game-over-phase.ts | 2 +- src/phases/select-starter-phase.ts | 2 +- src/system/game-data.ts | 3 +- src/system/pokemon-data.ts | 5 +- .../version_migration/versions/v1_0_4.ts | 2 +- .../version_migration/versions/v1_7_0.ts | 3 +- .../version_migration/versions/v1_8_3.ts | 2 +- src/ui/egg-gacha-ui-handler.ts | 2 +- src/ui/pokedex-page-ui-handler.ts | 4 +- src/ui/pokedex-scan-ui-handler.ts | 2 +- src/ui/pokedex-ui-handler.ts | 3 +- src/ui/starter-select-ui-handler.ts | 3 +- src/ui/title-ui-handler.ts | 2 +- src/utils/pokemon-utils.ts | 21 + test/battle/battle.test.ts | 2 +- test/boss-pokemon.test.ts | 2 +- test/eggs/egg.test.ts | 2 +- test/moves/effectiveness.test.ts | 2 +- ...an-offer-you-cant-refuse-encounter.test.ts | 2 +- .../clowning-around-encounter.test.ts | 2 +- .../fiery-fallout-encounter.test.ts | 2 +- .../encounters/lost-at-sea-encounter.test.ts | 2 +- .../the-strong-stuff-encounter.test.ts | 2 +- .../the-winstrate-challenge-encounter.test.ts | 2 +- .../trash-to-treasure-encounter.test.ts | 2 +- .../uncommon-breed-encounter.test.ts | 2 +- .../mystery-encounter-utils.test.ts | 2 +- test/phases/select-modifier-phase.test.ts | 2 +- test/testUtils/gameManagerUtils.ts | 3 +- test/ui/pokedex.test.ts | 4 +- test/ui/starter-select.test.ts | 2 +- 67 files changed, 465 insertions(+), 600 deletions(-) create mode 100644 src/utils/pokemon-utils.ts diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 14b0c6a3d19..b802466ee19 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -4,7 +4,8 @@ import type Pokemon from "#app/field/pokemon"; import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import type { PokemonSpeciesFilter } from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; +import { allSpecies } from "#app/data/data-lists"; import { fixedInt, getIvsFromId, @@ -2966,6 +2967,13 @@ export default class BattleScene extends SceneBase { ) { modifiers.splice(m--, 1); } + if ( + modifier instanceof PokemonHeldItemModifier && + !isNullOrUndefined(modifier.getSpecies()) && + !this.getPokemonById(modifier.pokemonId)?.hasSpecies(modifier.getSpecies()!) + ) { + modifiers.splice(m--, 1); + } } for (const modifier of modifiers) { if (modifier instanceof PersistentModifier) { diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index d307f5cfbf6..e97a51fed29 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -1,9 +1,9 @@ import { globalScene } from "#app/global-scene"; -import { Gender } from "#app/data/gender"; +import { Gender, getGenderSymbol } from "#app/data/gender"; import { PokeballType } from "#enums/pokeball"; import type Pokemon from "#app/field/pokemon"; import { PokemonType } from "#enums/pokemon-type"; -import { randSeedInt } from "#app/utils/common"; +import { coerceArray, isNullOrUndefined, randSeedInt } from "#app/utils/common"; import { WeatherType } from "#enums/weather-type"; import { Nature } from "#enums/nature"; import { BiomeId } from "#enums/biome-id"; @@ -11,10 +11,11 @@ import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { SpeciesFormKey } from "#enums/species-form-key"; import { TimeOfDay } from "#enums/time-of-day"; -import type { SpeciesStatBoosterModifierType } from "#app/modifier/modifier-type"; +import type { SpeciesStatBoosterItem, SpeciesStatBoosterModifierType } from "#app/modifier/modifier-type"; import { speciesStarterCosts } from "./starters"; import i18next from "i18next"; -import { initI18n } from "#app/plugins/i18n"; +import { allMoves } from "#app/data/data-lists"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; export enum SpeciesWildEvolutionDelay { NONE, @@ -74,6 +75,8 @@ export enum EvolutionItem { LEADERS_CREST } +type TyrogueMove = MoveId.LOW_SWEEP | MoveId.MACH_PUNCH | MoveId.RAPID_SPIN; + /** * Pokemon Evolution tuple type consisting of: * @property 0 {@linkcode SpeciesId} The species of the Pokemon. @@ -81,8 +84,132 @@ export enum EvolutionItem { */ export type EvolutionLevel = [species: SpeciesId, level: number]; -export type EvolutionConditionPredicate = (p: Pokemon) => boolean; -export type EvolutionConditionEnforceFunc = (p: Pokemon) => void; +const EvoCondKey = { + FRIENDSHIP: 1, + TIME: 2, + MOVE: 3, + MOVE_TYPE: 4, + PARTY_TYPE: 5, + WEATHER: 6, + BIOME: 7, + TYROGUE: 8, + SHEDINJA: 9, + EVO_TREASURE_TRACKER: 10, + RANDOM_FORM: 11, + SPECIES_CAUGHT: 12, + GENDER: 13, + NATURE: 14, + HELD_ITEM: 15, // Currently checks only for species stat booster items +} as const; + +type EvolutionConditionData = + {key: typeof EvoCondKey.FRIENDSHIP | typeof EvoCondKey.RANDOM_FORM | typeof EvoCondKey.EVO_TREASURE_TRACKER, value: number} | + {key: typeof EvoCondKey.MOVE, move: MoveId} | + {key: typeof EvoCondKey.TIME, time: TimeOfDay[]} | + {key: typeof EvoCondKey.BIOME, biome: BiomeId[]} | + {key: typeof EvoCondKey.GENDER, gender: Gender} | + {key: typeof EvoCondKey.MOVE_TYPE | typeof EvoCondKey.PARTY_TYPE, pkmnType: PokemonType} | + {key: typeof EvoCondKey.SPECIES_CAUGHT, speciesCaught: SpeciesId} | + {key: typeof EvoCondKey.HELD_ITEM, itemKey: SpeciesStatBoosterItem} | + {key: typeof EvoCondKey.NATURE, nature: Nature[]} | + {key: typeof EvoCondKey.WEATHER, weather: WeatherType[]} | + {key: typeof EvoCondKey.TYROGUE, move: TyrogueMove} | + {key: typeof EvoCondKey.SHEDINJA}; + +export class SpeciesEvolutionCondition { + public data: EvolutionConditionData[]; + private desc: string[]; + + constructor(...data: EvolutionConditionData[]) { + this.data = data; + } + + public get description(): string[] { + if (!isNullOrUndefined(this.desc)) { + return this.desc; + } + this.desc = this.data.map(cond => { + switch(cond.key) { + case EvoCondKey.FRIENDSHIP: + return i18next.t("pokemonEvolutions:friendship"); + case EvoCondKey.TIME: + return i18next.t(`pokemonEvolutions:timeOfDay.${TimeOfDay[cond.time[cond.time.length - 1]]}`); // For Day and Night evos, the key we want goes last + case EvoCondKey.MOVE_TYPE: + return i18next.t("pokemonEvolutions:moveType", {type: i18next.t(`pokemonInfo:Type.${PokemonType[cond.pkmnType]}`)}); + case EvoCondKey.PARTY_TYPE: + return i18next.t("pokemonEvolutions:partyType", {type: i18next.t(`pokemonInfo:Type.${PokemonType[cond.pkmnType]}`)}); + case EvoCondKey.GENDER: + return i18next.t("pokemonEvolutions:gender", {gender: getGenderSymbol(cond.gender)}); + case EvoCondKey.MOVE: + case EvoCondKey.TYROGUE: + return i18next.t("pokemonEvolutions:move", {move: allMoves[cond.move].name}); + case EvoCondKey.BIOME: + return i18next.t("pokemonEvolutions:biome"); + case EvoCondKey.NATURE: + return i18next.t("pokemonEvolutions:nature"); + case EvoCondKey.WEATHER: + return i18next.t("pokemonEvolutions:weather"); + case EvoCondKey.SHEDINJA: + return i18next.t("pokemonEvolutions:shedinja"); + case EvoCondKey.EVO_TREASURE_TRACKER: + return i18next.t("pokemonEvolutions:treasure"); + case EvoCondKey.SPECIES_CAUGHT: + return i18next.t("pokemonEvolutions:caught", {species: getPokemonSpecies(cond.speciesCaught).name}); + case EvoCondKey.HELD_ITEM: + return i18next.t(`pokemonEvolutions:heldItem.${cond.itemKey}`); + } + }).filter(s => !isNullOrUndefined(s)); // Filter out stringless conditions + return this.desc; + } + + public conditionsFulfilled(pokemon: Pokemon): boolean { + console.log(this.data); + return this.data.every(cond => { + switch (cond.key) { + case EvoCondKey.FRIENDSHIP: + return pokemon.friendship >= cond.value; + case EvoCondKey.TIME: + return cond.time.includes(globalScene.arena.getTimeOfDay()); + case EvoCondKey.MOVE: + return pokemon.moveset.some(m => m.moveId === cond.move); + case EvoCondKey.MOVE_TYPE: + return pokemon.moveset.some(m => m.getMove().type === cond.pkmnType); + case EvoCondKey.PARTY_TYPE: + return globalScene.getPlayerParty().some(p => p.getTypes(false, false, true).includes(cond.pkmnType)) + case EvoCondKey.EVO_TREASURE_TRACKER: + return pokemon.getHeldItems().some(m => + m.is("EvoTrackerModifier") && + m.getStackCount() + pokemon.getPersistentTreasureCount() >= cond.value + ); + case EvoCondKey.GENDER: + return pokemon.gender === cond.gender; + case EvoCondKey.SHEDINJA: // Shedinja cannot be evolved into directly + return false; + case EvoCondKey.BIOME: + return cond.biome.includes(globalScene.arena.biomeType); + case EvoCondKey.WEATHER: + return cond.weather.includes(globalScene.arena.getWeatherType()); + case EvoCondKey.TYROGUE: + return pokemon.getMoveset(true).find(m => m.moveId as TyrogueMove)?.moveId === cond.move; + case EvoCondKey.NATURE: + return cond.nature.includes(pokemon.getNature()); + case EvoCondKey.RANDOM_FORM: { + let ret = false; + globalScene.executeWithSeedOffset(() => ret = !randSeedInt(cond.value), pokemon.id); + return ret; + } + case EvoCondKey.SPECIES_CAUGHT: + return !!globalScene.gameData.dexData[cond.speciesCaught].caughtAttr; + case EvoCondKey.HELD_ITEM: + return pokemon.getHeldItems().some(m => m.is("SpeciesStatBoosterModifier") && (m.type as SpeciesStatBoosterModifierType).key === cond.itemKey) + } + }); + } +} + +export function validateShedinjaEvo(): boolean { + return globalScene.getPlayerParty().length < 6 && globalScene.pokeballCounts[PokeballType.POKEBALL] > 0; +} export class SpeciesFormEvolution { public speciesId: SpeciesId; @@ -92,41 +219,96 @@ export class SpeciesFormEvolution { public item: EvolutionItem | null; public condition: SpeciesEvolutionCondition | null; public wildDelay: SpeciesWildEvolutionDelay; - public description = ""; + public desc = ""; - constructor(speciesId: SpeciesId, preFormKey: string | null, evoFormKey: string | null, level: number, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay) { - if (!i18next.isInitialized) { - initI18n(); - } + constructor(speciesId: SpeciesId, preFormKey: string | null, evoFormKey: string | null, level: number, item: EvolutionItem | null, condition: EvolutionConditionData | EvolutionConditionData[] | null, wildDelay?: SpeciesWildEvolutionDelay) { this.speciesId = speciesId; this.preFormKey = preFormKey; this.evoFormKey = evoFormKey; this.level = level; this.item = item || EvolutionItem.NONE; - this.condition = condition; + if (!isNullOrUndefined(condition)) { + this.condition = new SpeciesEvolutionCondition(...coerceArray(condition)); + } this.wildDelay = wildDelay ?? SpeciesWildEvolutionDelay.NONE; + } + + get description(): string { + if (this.desc.length > 0) { + return this.desc; + } const strings: string[] = []; + let len = 0; if (this.level > 1) { - strings.push(i18next.t("pokemonEvolutions:level") + ` ${this.level}`); + strings.push(i18next.t("pokemonEvolutions:atLevel", {lv: this.level})); } if (this.item) { const itemDescription = i18next.t(`modifierType:EvolutionItem.${EvolutionItem[this.item].toUpperCase()}`); const rarity = this.item > 50 ? i18next.t("pokemonEvolutions:ULTRA") : i18next.t("pokemonEvolutions:GREAT"); - strings.push(i18next.t("pokemonEvolutions:using") + itemDescription + ` (${rarity})`); + strings.push(i18next.t("pokemonEvolutions:using", {item: itemDescription, tier: rarity})); } if (this.condition) { - strings.push(this.condition.description); + if (strings.length === 0) { + strings.push(i18next.t("pokemonEvolutions:levelUp")); + } + strings.push(...this.condition.description); } - this.description = strings + this.desc = strings .filter(str => str !== "") - .map((str, index) => index > 0 ? str[0].toLowerCase() + str.slice(1) : str) - .join(i18next.t("pokemonEvolutions:connector")); + .map((str, index) => { + if (index === 0) { + len = str.length; + return str; + } + if (len + str.length > 60) { + len = str.length; + return "\n" + str[0].toLowerCase() + str.slice(1); + } + len += str.length; + return str[0].toLowerCase() + str.slice(1); + }) + .join(" ") + .replace(" \n", i18next.t("pokemonEvolutions:connector") + "\n"); + + return this.desc; + } + + /** + * Checks if a Pokemon fulfills the requirements of this evolution. + * @param pokemon {@linkcode Pokemon} who wants to evolve + * @param forFusion defaults to False. Whether this evolution is meant for the secondary fused mon. In that case, use their form key. + * @param item {@linkcode EvolutionItem} optional, check if the evolution uses a certain item + * @returns whether this evolution can apply to the Pokemon + */ + public validate(pokemon: Pokemon, forFusion = false, item?: EvolutionItem): boolean { + return ( + pokemon.level >= this.level && + // Check form key, using the fusion's form key if we're checking the fusion + (isNullOrUndefined(this.preFormKey) || (forFusion ? pokemon.getFusionFormKey() : pokemon.getFormKey()) === this.preFormKey) && + (isNullOrUndefined(this.condition) || this.condition.conditionsFulfilled(pokemon)) && + ((item ?? EvolutionItem.NONE) === (this.item ?? EvolutionItem.NONE)) + ); + } + + public isValidItemEvolution(pokemon: Pokemon, forFusion = false): boolean { + return ( + // If an item is given, check if it's the right one + !isNullOrUndefined(this.item) && + pokemon.level >= this.level && + // Check form key, using the fusion's form key if we're checking the fusion + (isNullOrUndefined(this.preFormKey) || (forFusion ? pokemon.getFormKey() : pokemon.getFusionFormKey()) === this.preFormKey) && + (isNullOrUndefined(this.condition) || this.condition.conditionsFulfilled(pokemon)) + ); + } + + public get evoItem(): EvolutionItem { + return this.item ?? EvolutionItem.NONE; } } export class SpeciesEvolution extends SpeciesFormEvolution { - constructor(speciesId: SpeciesId, level: number, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay) { + constructor(speciesId: SpeciesId, level: number, item: EvolutionItem | null, condition: EvolutionConditionData | EvolutionConditionData[] | null, wildDelay?: SpeciesWildEvolutionDelay) { super(speciesId, null, null, level, item, condition, wildDelay); } } @@ -135,226 +317,12 @@ export class FusionSpeciesFormEvolution extends SpeciesFormEvolution { public primarySpeciesId: SpeciesId; constructor(primarySpeciesId: SpeciesId, evolution: SpeciesFormEvolution) { - super(evolution.speciesId, evolution.preFormKey, evolution.evoFormKey, evolution.level, evolution.item, evolution.condition, evolution.wildDelay); + super(evolution.speciesId, evolution.preFormKey, evolution.evoFormKey, evolution.level, evolution.item, evolution.condition?.data ?? null, evolution.wildDelay); this.primarySpeciesId = primarySpeciesId; } } -export class SpeciesEvolutionCondition { - public predicate: EvolutionConditionPredicate; - public enforceFunc?: EvolutionConditionEnforceFunc; - public description: string; - - constructor(predicate: EvolutionConditionPredicate, enforceFunc?: EvolutionConditionEnforceFunc) { - this.predicate = predicate; - this.enforceFunc = enforceFunc; - this.description = ""; - } -} - -class GenderEvolutionCondition extends SpeciesEvolutionCondition { - public gender: Gender; - constructor(gender: Gender) { - super(p => p.gender === gender, p => p.gender = gender); - this.gender = gender; - this.description = i18next.t("pokemonEvolutions:gender", { gender: i18next.t(`pokemonEvolutions:${Gender[gender]}`) }); - } -} - -class TimeOfDayEvolutionCondition extends SpeciesEvolutionCondition { - public timesOfDay: TimeOfDay[]; - constructor(tod: "day" | "night") { - if (tod === "day") { - super(() => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY); - this.timesOfDay = [ TimeOfDay.DAWN, TimeOfDay.DAY ]; - } else if (tod === "night") { - super(() => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT); - this.timesOfDay = [ TimeOfDay.DUSK, TimeOfDay.NIGHT ]; - } else { - super(() => false); - this.timesOfDay = []; - } - this.description = i18next.t("pokemonEvolutions:timeOfDay", { tod: i18next.t(`pokemonEvolutions:${tod}`) }); - } -} - -class MoveEvolutionCondition extends SpeciesEvolutionCondition { - public move: MoveId; - constructor(move: MoveId) { - super(p => p.moveset.filter(m => m.moveId === move).length > 0); - this.move = move; - const moveKey = MoveId[this.move].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); - this.description = i18next.t("pokemonEvolutions:move", { move: i18next.t(`move:${moveKey}.name`) }); - } -} - -class FriendshipEvolutionCondition extends SpeciesEvolutionCondition { - public amount: number; - constructor(amount: number) { - super(p => p.friendship >= amount); - this.amount = amount; - this.description = i18next.t("pokemonEvolutions:friendship"); - } -} - -class FriendshipTimeOfDayEvolutionCondition extends SpeciesEvolutionCondition { - public amount: number; - public timesOfDay: TimeOfDay[]; - constructor(amount: number, tod: "day" | "night") { - if (tod === "day") { - super(p => p.friendship >= amount && (globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)); - this.timesOfDay = [ TimeOfDay.DAWN, TimeOfDay.DAY ]; - } else if (tod === "night") { - super(p => p.friendship >= amount && (globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)); - this.timesOfDay = [ TimeOfDay.DUSK, TimeOfDay.NIGHT ]; - } else { - super(_p => false); - this.timesOfDay = []; - } - this.amount = amount; - this.description = i18next.t("pokemonEvolutions:friendshipTimeOfDay", { tod: i18next.t(`pokemonEvolutions:${tod}`) }); - } -} - -class FriendshipMoveTypeEvolutionCondition extends SpeciesEvolutionCondition { - public amount: number; - public type: PokemonType; - constructor(amount: number, type: PokemonType) { - super(p => p.friendship >= amount && !!p.getMoveset().find(m => m?.getMove().type === type)); - this.amount = amount; - this.type = type; - this.description = i18next.t("pokemonEvolutions:friendshipMoveType", { type: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`) }); - } -} - -class ShedinjaEvolutionCondition extends SpeciesEvolutionCondition { - constructor() { - super(() => globalScene.getPlayerParty().length < 6 && globalScene.pokeballCounts[PokeballType.POKEBALL] > 0); - this.description = i18next.t("pokemonEvolutions:shedinja"); - } -} - -class PartyTypeEvolutionCondition extends SpeciesEvolutionCondition { - public type: PokemonType; - constructor(type: PokemonType) { - super(() => !!globalScene.getPlayerParty().find(p => p.getTypes(false, false, true).indexOf(type) > -1)); - this.type = type; - this.description = i18next.t("pokemonEvolutions:partyType", { type: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`) }); - } -} - -class CaughtEvolutionCondition extends SpeciesEvolutionCondition { - public species: SpeciesId; - constructor(species: SpeciesId) { - super(() => !!globalScene.gameData.dexData[species].caughtAttr); - this.species = species; - this.description = i18next.t("pokemonEvolutions:caught", { species: i18next.t(`pokemon:${SpeciesId[this.species].toLowerCase()}`) }); - } -} - -class WeatherEvolutionCondition extends SpeciesEvolutionCondition { - public weatherTypes: WeatherType[]; - constructor(weatherTypes: WeatherType[]) { - super(() => weatherTypes.indexOf(globalScene.arena.weather?.weatherType || WeatherType.NONE) > -1); - this.weatherTypes = weatherTypes; - this.description = i18next.t("pokemonEvolutions:weather"); - } -} - -class MoveTypeEvolutionCondition extends SpeciesEvolutionCondition { - public type: PokemonType; - constructor(type: PokemonType) { - super(p => p.moveset.filter(m => m?.getMove().type === type).length > 0); - this.type = type; - this.description = i18next.t("pokemonEvolutions:moveType", { type: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`) }); - } -} - -class TreasureEvolutionCondition extends SpeciesEvolutionCondition { - constructor() { - super(p => p.evoCounter - + p.getHeldItems().filter(m => m.is("DamageMoneyRewardModifier")).length - + globalScene.findModifiers(m => m.is("MoneyMultiplierModifier") - || m.is("ExtraModifierModifier") || m.is("TempExtraModifierModifier")).length > 9); - this.description = i18next.t("pokemonEvolutions:treasure"); - } -} - -class TyrogueEvolutionCondition extends SpeciesEvolutionCondition { - public move: MoveId; - constructor(move: MoveId) { - super(p => - p.getMoveset(true).find(m => m && [ MoveId.LOW_SWEEP, MoveId.MACH_PUNCH, MoveId.RAPID_SPIN ].includes(m.moveId))?.moveId === move); - this.move = move; - const moveKey = MoveId[this.move].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); - this.description = i18next.t("pokemonEvolutions:move", { move: i18next.t(`move:${moveKey}.name`) }); - } -} - -class NatureEvolutionCondition extends SpeciesEvolutionCondition { - public natures: Nature[]; - constructor(natures: Nature[]) { - super(p => natures.indexOf(p.getNature()) > -1); - this.natures = natures; - this.description = i18next.t("pokemonEvolutions:nature"); - } -} - -class MoveTimeOfDayEvolutionCondition extends SpeciesEvolutionCondition { - public move: MoveId; - public timesOfDay: TimeOfDay[]; - constructor(move: MoveId, tod: "day" | "night") { - if (tod === "day") { - super(p => p.moveset.filter(m => m.moveId === move).length > 0 && (globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)); - this.move = move; - this.timesOfDay = [ TimeOfDay.DAWN, TimeOfDay.DAY ]; - } else if (tod === "night") { - super(p => p.moveset.filter(m => m.moveId === move).length > 0 && (globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)); - this.move = move; - this.timesOfDay = [ TimeOfDay.DUSK, TimeOfDay.NIGHT ]; - } else { - super(() => false); - this.timesOfDay = []; - } - const moveKey = MoveId[this.move].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); - this.description = i18next.t("pokemonEvolutions:moveTimeOfDay", { move: i18next.t(`move:${moveKey}.name`), tod: i18next.t(`pokemonEvolutions:${tod}`) }); - } -} - -class BiomeEvolutionCondition extends SpeciesEvolutionCondition { - public biomes: BiomeId[]; - constructor(biomes: BiomeId[]) { - super(() => biomes.filter(b => b === globalScene.arena.biomeType).length > 0); - this.biomes = biomes; - this.description = i18next.t("pokemonEvolutions:biome"); - } -} - -class DunsparceEvolutionCondition extends SpeciesEvolutionCondition { - constructor() { - super(p => { - let ret = false; - if (p.moveset.filter(m => m.moveId === MoveId.HYPER_DRILL).length > 0) { - globalScene.executeWithSeedOffset(() => ret = !randSeedInt(4), p.id); - } - return ret; - }); - const moveKey = MoveId[MoveId.HYPER_DRILL].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); - this.description = i18next.t("pokemonEvolutions:move", { move: i18next.t(`move:${moveKey}.name`) }); - } -} - -class TandemausEvolutionCondition extends SpeciesEvolutionCondition { - constructor() { - super(p => { - let ret = false; - globalScene.executeWithSeedOffset(() => ret = !randSeedInt(4), p.id); - return ret; - }); - } -} - interface PokemonEvolutions { [key: string]: SpeciesFormEvolution[] } @@ -488,8 +456,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.ELECTRODE, 30, null, null) ], [SpeciesId.CUBONE]: [ - new SpeciesEvolution(SpeciesId.ALOLA_MAROWAK, 28, null, new TimeOfDayEvolutionCondition("night")), - new SpeciesEvolution(SpeciesId.MAROWAK, 28, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.ALOLA_MAROWAK, 28, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}), + new SpeciesEvolution(SpeciesId.MAROWAK, 28, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.TYROGUE]: [ /** @@ -498,13 +466,13 @@ export const pokemonEvolutions: PokemonEvolutions = { * If Tyrogue knows multiple of these moves, its evolution is based on * the first qualifying move in its moveset. */ - new SpeciesEvolution(SpeciesId.HITMONLEE, 20, null, new TyrogueEvolutionCondition(MoveId.LOW_SWEEP)), - new SpeciesEvolution(SpeciesId.HITMONCHAN, 20, null, new TyrogueEvolutionCondition(MoveId.MACH_PUNCH)), - new SpeciesEvolution(SpeciesId.HITMONTOP, 20, null, new TyrogueEvolutionCondition(MoveId.RAPID_SPIN)), + new SpeciesEvolution(SpeciesId.HITMONLEE, 20, null, {key: EvoCondKey.TYROGUE, move: MoveId.LOW_SWEEP}), + new SpeciesEvolution(SpeciesId.HITMONCHAN, 20, null, {key: EvoCondKey.TYROGUE, move: MoveId.MACH_PUNCH}), + new SpeciesEvolution(SpeciesId.HITMONTOP, 20, null, {key: EvoCondKey.TYROGUE, move: MoveId.RAPID_SPIN}), ], [SpeciesId.KOFFING]: [ - new SpeciesEvolution(SpeciesId.GALAR_WEEZING, 35, null, new TimeOfDayEvolutionCondition("night")), - new SpeciesEvolution(SpeciesId.WEEZING, 35, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.GALAR_WEEZING, 35, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}), + new SpeciesEvolution(SpeciesId.WEEZING, 35, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.RHYHORN]: [ new SpeciesEvolution(SpeciesId.RHYDON, 42, null, null) @@ -549,8 +517,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.QUILAVA, 14, null, null) ], [SpeciesId.QUILAVA]: [ - new SpeciesEvolution(SpeciesId.HISUI_TYPHLOSION, 36, null, new TimeOfDayEvolutionCondition("night")), - new SpeciesEvolution(SpeciesId.TYPHLOSION, 36, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.HISUI_TYPHLOSION, 36, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}), + new SpeciesEvolution(SpeciesId.TYPHLOSION, 36, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.TOTODILE]: [ new SpeciesEvolution(SpeciesId.CROCONAW, 18, null, null) @@ -652,8 +620,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.LINOONE, 20, null, null) ], [SpeciesId.WURMPLE]: [ - new SpeciesEvolution(SpeciesId.SILCOON, 7, null, new TimeOfDayEvolutionCondition("day")), - new SpeciesEvolution(SpeciesId.CASCOON, 7, null, new TimeOfDayEvolutionCondition("night")) + new SpeciesEvolution(SpeciesId.SILCOON, 7, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}), + new SpeciesEvolution(SpeciesId.CASCOON, 7, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}) ], [SpeciesId.SILCOON]: [ new SpeciesEvolution(SpeciesId.BEAUTIFLY, 10, null, null) @@ -677,8 +645,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.KIRLIA, 20, null, null) ], [SpeciesId.KIRLIA]: [ - new SpeciesEvolution(SpeciesId.GARDEVOIR, 30, null, new GenderEvolutionCondition(Gender.FEMALE)), - new SpeciesEvolution(SpeciesId.GALLADE, 30, null, new GenderEvolutionCondition(Gender.MALE)) + new SpeciesEvolution(SpeciesId.GARDEVOIR, 30, null, {key: EvoCondKey.GENDER, gender: Gender.FEMALE}), + new SpeciesEvolution(SpeciesId.GALLADE, 30, null, {key: EvoCondKey.GENDER, gender: Gender.MALE}) ], [SpeciesId.SURSKIT]: [ new SpeciesEvolution(SpeciesId.MASQUERAIN, 22, null, null) @@ -694,7 +662,7 @@ export const pokemonEvolutions: PokemonEvolutions = { ], [SpeciesId.NINCADA]: [ new SpeciesEvolution(SpeciesId.NINJASK, 20, null, null), - new SpeciesEvolution(SpeciesId.SHEDINJA, 20, null, new ShedinjaEvolutionCondition()) + new SpeciesEvolution(SpeciesId.SHEDINJA, 20, null, {key: EvoCondKey.SHEDINJA}) ], [SpeciesId.WHISMUR]: [ new SpeciesEvolution(SpeciesId.LOUDRED, 20, null, null) @@ -766,8 +734,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.DUSCLOPS, 37, null, null) ], [SpeciesId.SNORUNT]: [ - new SpeciesEvolution(SpeciesId.GLALIE, 42, null, new GenderEvolutionCondition(Gender.MALE)), - new SpeciesEvolution(SpeciesId.FROSLASS, 42, null, new GenderEvolutionCondition(Gender.FEMALE)) + new SpeciesEvolution(SpeciesId.GLALIE, 42, null, {key: EvoCondKey.GENDER, gender: Gender.MALE}), + new SpeciesEvolution(SpeciesId.FROSLASS, 42, null, {key: EvoCondKey.GENDER, gender: Gender.FEMALE}) ], [SpeciesId.SPHEAL]: [ new SpeciesEvolution(SpeciesId.SEALEO, 32, null, null) @@ -830,11 +798,11 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.BASTIODON, 30, null, null) ], [SpeciesId.BURMY]: [ - new SpeciesEvolution(SpeciesId.MOTHIM, 20, null, new GenderEvolutionCondition(Gender.MALE)), - new SpeciesEvolution(SpeciesId.WORMADAM, 20, null, new GenderEvolutionCondition(Gender.FEMALE)) + new SpeciesEvolution(SpeciesId.MOTHIM, 20, null, {key: EvoCondKey.GENDER, gender: Gender.MALE}), + new SpeciesEvolution(SpeciesId.WORMADAM, 20, null, {key: EvoCondKey.GENDER, gender: Gender.FEMALE}) ], [SpeciesId.COMBEE]: [ - new SpeciesEvolution(SpeciesId.VESPIQUEN, 21, null, new GenderEvolutionCondition(Gender.FEMALE)) + new SpeciesEvolution(SpeciesId.VESPIQUEN, 21, null, {key: EvoCondKey.GENDER, gender: Gender.FEMALE}) ], [SpeciesId.BUIZEL]: [ new SpeciesEvolution(SpeciesId.FLOATZEL, 26, null, null) @@ -876,7 +844,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.LUMINEON, 31, null, null) ], [SpeciesId.MANTYKE]: [ - new SpeciesEvolution(SpeciesId.MANTINE, 32, null, new CaughtEvolutionCondition(SpeciesId.REMORAID), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(SpeciesId.MANTINE, 32, null, {key: EvoCondKey.SPECIES_CAUGHT, speciesCaught: SpeciesId.REMORAID}, SpeciesWildEvolutionDelay.MEDIUM) ], [SpeciesId.SNOVER]: [ new SpeciesEvolution(SpeciesId.ABOMASNOW, 40, null, null) @@ -897,8 +865,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.DEWOTT, 17, null, null) ], [SpeciesId.DEWOTT]: [ - new SpeciesEvolution(SpeciesId.HISUI_SAMUROTT, 36, null, new TimeOfDayEvolutionCondition("night")), - new SpeciesEvolution(SpeciesId.SAMUROTT, 36, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.HISUI_SAMUROTT, 36, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}), + new SpeciesEvolution(SpeciesId.SAMUROTT, 36, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.PATRAT]: [ new SpeciesEvolution(SpeciesId.WATCHOG, 20, null, null) @@ -1048,8 +1016,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.KINGAMBIT, 1, EvolutionItem.LEADERS_CREST, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.RUFFLET]: [ - new SpeciesEvolution(SpeciesId.HISUI_BRAVIARY, 54, null, new TimeOfDayEvolutionCondition("night")), - new SpeciesEvolution(SpeciesId.BRAVIARY, 54, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.HISUI_BRAVIARY, 54, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}), + new SpeciesEvolution(SpeciesId.BRAVIARY, 54, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.VULLABY]: [ new SpeciesEvolution(SpeciesId.MANDIBUZZ, 54, null, null) @@ -1106,11 +1074,11 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.GOGOAT, 32, null, null) ], [SpeciesId.PANCHAM]: [ - new SpeciesEvolution(SpeciesId.PANGORO, 32, null, new PartyTypeEvolutionCondition(PokemonType.DARK), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(SpeciesId.PANGORO, 32, null, {key: EvoCondKey.PARTY_TYPE, pkmnType: PokemonType.DARK}, SpeciesWildEvolutionDelay.MEDIUM) ], [SpeciesId.ESPURR]: [ - new SpeciesFormEvolution(SpeciesId.MEOWSTIC, "", "female", 25, null, new GenderEvolutionCondition(Gender.FEMALE)), - new SpeciesFormEvolution(SpeciesId.MEOWSTIC, "", "", 25, null, new GenderEvolutionCondition(Gender.MALE)) + new SpeciesFormEvolution(SpeciesId.MEOWSTIC, "", "female", 25, null, {key: EvoCondKey.GENDER, gender: Gender.FEMALE}), + new SpeciesFormEvolution(SpeciesId.MEOWSTIC, "", "", 25, null, {key: EvoCondKey.GENDER, gender: Gender.MALE}) ], [SpeciesId.HONEDGE]: [ new SpeciesEvolution(SpeciesId.DOUBLADE, 35, null, null) @@ -1128,21 +1096,21 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.CLAWITZER, 37, null, null) ], [SpeciesId.TYRUNT]: [ - new SpeciesEvolution(SpeciesId.TYRANTRUM, 39, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.TYRANTRUM, 39, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.AMAURA]: [ - new SpeciesEvolution(SpeciesId.AURORUS, 39, null, new TimeOfDayEvolutionCondition("night")) + new SpeciesEvolution(SpeciesId.AURORUS, 39, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}) ], [SpeciesId.GOOMY]: [ - new SpeciesEvolution(SpeciesId.HISUI_SLIGGOO, 40, null, new TimeOfDayEvolutionCondition("night")), - new SpeciesEvolution(SpeciesId.SLIGGOO, 40, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.HISUI_SLIGGOO, 40, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}), + new SpeciesEvolution(SpeciesId.SLIGGOO, 40, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.SLIGGOO]: [ - new SpeciesEvolution(SpeciesId.GOODRA, 50, null, new WeatherEvolutionCondition([ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ]), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.GOODRA, 50, null, {key: EvoCondKey.WEATHER, weather: [ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ]}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.BERGMITE]: [ - new SpeciesEvolution(SpeciesId.HISUI_AVALUGG, 37, null, new TimeOfDayEvolutionCondition("night")), - new SpeciesEvolution(SpeciesId.AVALUGG, 37, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.HISUI_AVALUGG, 37, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}), + new SpeciesEvolution(SpeciesId.AVALUGG, 37, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.NOIBAT]: [ new SpeciesEvolution(SpeciesId.NOIVERN, 48, null, null) @@ -1151,8 +1119,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.DARTRIX, 17, null, null) ], [SpeciesId.DARTRIX]: [ - new SpeciesEvolution(SpeciesId.HISUI_DECIDUEYE, 36, null, new TimeOfDayEvolutionCondition("night")), - new SpeciesEvolution(SpeciesId.DECIDUEYE, 34, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.HISUI_DECIDUEYE, 36, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}), + new SpeciesEvolution(SpeciesId.DECIDUEYE, 34, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.LITTEN]: [ new SpeciesEvolution(SpeciesId.TORRACAT, 17, null, null) @@ -1173,7 +1141,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.TOUCANNON, 28, null, null) ], [SpeciesId.YUNGOOS]: [ - new SpeciesEvolution(SpeciesId.GUMSHOOS, 20, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.GUMSHOOS, 20, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.GRUBBIN]: [ new SpeciesEvolution(SpeciesId.CHARJABUG, 20, null, null) @@ -1191,13 +1159,13 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.ARAQUANID, 22, null, null) ], [SpeciesId.FOMANTIS]: [ - new SpeciesEvolution(SpeciesId.LURANTIS, 34, null, new TimeOfDayEvolutionCondition("day")) + new SpeciesEvolution(SpeciesId.LURANTIS, 34, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}) ], [SpeciesId.MORELULL]: [ new SpeciesEvolution(SpeciesId.SHIINOTIC, 24, null, null) ], [SpeciesId.SALANDIT]: [ - new SpeciesEvolution(SpeciesId.SALAZZLE, 33, null, new GenderEvolutionCondition(Gender.FEMALE)) + new SpeciesEvolution(SpeciesId.SALAZZLE, 33, null, {key: EvoCondKey.GENDER, gender: Gender.FEMALE}) ], [SpeciesId.STUFFUL]: [ new SpeciesEvolution(SpeciesId.BEWEAR, 27, null, null) @@ -1228,7 +1196,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.MELMETAL, 48, null, null) ], [SpeciesId.ALOLA_RATTATA]: [ - new SpeciesEvolution(SpeciesId.ALOLA_RATICATE, 20, null, new TimeOfDayEvolutionCondition("night")) + new SpeciesEvolution(SpeciesId.ALOLA_RATICATE, 20, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}) ], [SpeciesId.ALOLA_DIGLETT]: [ new SpeciesEvolution(SpeciesId.ALOLA_DUGTRIO, 26, null, null) @@ -1301,7 +1269,7 @@ export const pokemonEvolutions: PokemonEvolutions = { ], [SpeciesId.TOXEL]: [ new SpeciesFormEvolution(SpeciesId.TOXTRICITY, "", "lowkey", 30, null, - new NatureEvolutionCondition([ Nature.LONELY, Nature.BOLD, Nature.RELAXED, Nature.TIMID, Nature.SERIOUS, Nature.MODEST, Nature.MILD, Nature.QUIET, Nature.BASHFUL, Nature.CALM, Nature.GENTLE, Nature.CAREFUL ]) + {key: EvoCondKey.NATURE, nature: [ Nature.LONELY, Nature.BOLD, Nature.RELAXED, Nature.TIMID, Nature.SERIOUS, Nature.MODEST, Nature.MILD, Nature.QUIET, Nature.BASHFUL, Nature.CALM, Nature.GENTLE, Nature.CAREFUL ]} ), new SpeciesFormEvolution(SpeciesId.TOXTRICITY, "", "amped", 30, null, null) ], @@ -1352,7 +1320,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.GALAR_LINOONE, 20, null, null) ], [SpeciesId.GALAR_LINOONE]: [ - new SpeciesEvolution(SpeciesId.OBSTAGOON, 35, null, new TimeOfDayEvolutionCondition("night")) + new SpeciesEvolution(SpeciesId.OBSTAGOON, 35, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}) ], [SpeciesId.GALAR_YAMASK]: [ new SpeciesEvolution(SpeciesId.RUNERIGUS, 34, null, null) @@ -1361,7 +1329,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.HISUI_ZOROARK, 30, null, null) ], [SpeciesId.HISUI_SLIGGOO]: [ - new SpeciesEvolution(SpeciesId.HISUI_GOODRA, 50, null, new WeatherEvolutionCondition([ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ]), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.HISUI_GOODRA, 50, null, {key: EvoCondKey.WEATHER, weather: [ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ]}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.SPRIGATITO]: [ new SpeciesEvolution(SpeciesId.FLORAGATO, 16, null, null) @@ -1382,8 +1350,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.QUAQUAVAL, 36, null, null) ], [SpeciesId.LECHONK]: [ - new SpeciesFormEvolution(SpeciesId.OINKOLOGNE, "", "female", 18, null, new GenderEvolutionCondition(Gender.FEMALE)), - new SpeciesFormEvolution(SpeciesId.OINKOLOGNE, "", "", 18, null, new GenderEvolutionCondition(Gender.MALE)) + new SpeciesFormEvolution(SpeciesId.OINKOLOGNE, "", "female", 18, null, {key: EvoCondKey.GENDER, gender: Gender.FEMALE}), + new SpeciesFormEvolution(SpeciesId.OINKOLOGNE, "", "", 18, null, {key: EvoCondKey.GENDER, gender: Gender.MALE}) ], [SpeciesId.TAROUNTULA]: [ new SpeciesEvolution(SpeciesId.SPIDOPS, 15, null, null) @@ -1398,7 +1366,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.PAWMOT, 32, null, null) ], [SpeciesId.TANDEMAUS]: [ - new SpeciesFormEvolution(SpeciesId.MAUSHOLD, "", "three", 25, null, new TandemausEvolutionCondition()), + new SpeciesFormEvolution(SpeciesId.MAUSHOLD, "", "three", 25, null, {key: EvoCondKey.RANDOM_FORM, value: 4}), new SpeciesFormEvolution(SpeciesId.MAUSHOLD, "", "four", 25, null, null) ], [SpeciesId.FIDOUGH]: [ @@ -1456,7 +1424,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.GLIMMORA, 35, null, null) ], [SpeciesId.GREAVARD]: [ - new SpeciesEvolution(SpeciesId.HOUNDSTONE, 30, null, new TimeOfDayEvolutionCondition("night")) + new SpeciesEvolution(SpeciesId.HOUNDSTONE, 30, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}) ], [SpeciesId.FRIGIBAX]: [ new SpeciesEvolution(SpeciesId.ARCTIBAX, 35, null, null) @@ -1513,21 +1481,21 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.EXEGGUTOR, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.TANGELA]: [ - new SpeciesEvolution(SpeciesId.TANGROWTH, 34, null, new MoveEvolutionCondition(MoveId.ANCIENT_POWER), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.TANGROWTH, 34, null, {key: EvoCondKey.MOVE, move: MoveId.ANCIENT_POWER}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.LICKITUNG]: [ - new SpeciesEvolution(SpeciesId.LICKILICKY, 32, null, new MoveEvolutionCondition(MoveId.ROLLOUT), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.LICKILICKY, 32, null, {key: EvoCondKey.MOVE, move: MoveId.ROLLOUT}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.STARYU]: [ new SpeciesEvolution(SpeciesId.STARMIE, 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.EEVEE]: [ - new SpeciesFormEvolution(SpeciesId.SYLVEON, "", "", 1, null, new FriendshipMoveTypeEvolutionCondition(120, PokemonType.FAIRY), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(SpeciesId.SYLVEON, "partner", "", 1, null, new FriendshipMoveTypeEvolutionCondition(120, PokemonType.FAIRY), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(SpeciesId.ESPEON, "", "", 1, null, new FriendshipTimeOfDayEvolutionCondition(120, "day"), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(SpeciesId.ESPEON, "partner", "", 1, null, new FriendshipTimeOfDayEvolutionCondition(120, "day"), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(SpeciesId.UMBREON, "", "", 1, null, new FriendshipTimeOfDayEvolutionCondition(120, "night"), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(SpeciesId.UMBREON, "partner", "", 1, null, new FriendshipTimeOfDayEvolutionCondition(120, "night"), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(SpeciesId.SYLVEON, "", "", 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 120}, {key: EvoCondKey.MOVE_TYPE, pkmnType: PokemonType.FAIRY}], SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(SpeciesId.SYLVEON, "partner", "", 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 120}, {key: EvoCondKey.MOVE_TYPE, pkmnType: PokemonType.FAIRY}], SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(SpeciesId.ESPEON, "", "", 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 120}, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}], SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(SpeciesId.ESPEON, "partner", "", 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 120}, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}], SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(SpeciesId.UMBREON, "", "", 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 120}, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}], SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(SpeciesId.UMBREON, "partner", "", 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 120}, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}], SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.VAPOREON, "", "", 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.VAPOREON, "partner", "", 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.JOLTEON, "", "", 1, EvolutionItem.THUNDER_STONE, null, SpeciesWildEvolutionDelay.LONG), @@ -1543,13 +1511,13 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.TOGEKISS, 1, EvolutionItem.SHINY_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.AIPOM]: [ - new SpeciesEvolution(SpeciesId.AMBIPOM, 32, null, new MoveEvolutionCondition(MoveId.DOUBLE_HIT), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.AMBIPOM, 32, null, {key: EvoCondKey.MOVE, move: MoveId.DOUBLE_HIT}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.SUNKERN]: [ new SpeciesEvolution(SpeciesId.SUNFLORA, 1, EvolutionItem.SUN_STONE, null, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.YANMA]: [ - new SpeciesEvolution(SpeciesId.YANMEGA, 33, null, new MoveEvolutionCondition(MoveId.ANCIENT_POWER), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.YANMEGA, 33, null, {key: EvoCondKey.MOVE, move: MoveId.ANCIENT_POWER}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.MURKROW]: [ new SpeciesEvolution(SpeciesId.HONCHKROW, 1, EvolutionItem.DUSK_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG) @@ -1558,26 +1526,26 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.MISMAGIUS, 1, EvolutionItem.DUSK_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.GIRAFARIG]: [ - new SpeciesEvolution(SpeciesId.FARIGIRAF, 32, null, new MoveEvolutionCondition(MoveId.TWIN_BEAM), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.FARIGIRAF, 32, null, {key: EvoCondKey.MOVE, move: MoveId.TWIN_BEAM}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.DUNSPARCE]: [ - new SpeciesFormEvolution(SpeciesId.DUDUNSPARCE, "", "three-segment", 32, null, new DunsparceEvolutionCondition(), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(SpeciesId.DUDUNSPARCE, "", "two-segment", 32, null, new MoveEvolutionCondition(MoveId.HYPER_DRILL), SpeciesWildEvolutionDelay.LONG) + new SpeciesFormEvolution(SpeciesId.DUDUNSPARCE, "", "three-segment", 32, null, [{key: EvoCondKey.RANDOM_FORM, value: 4}, {key: EvoCondKey.MOVE, move: MoveId.HYPER_DRILL}], SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(SpeciesId.DUDUNSPARCE, "", "two-segment", 32, null, {key: EvoCondKey.MOVE, move: MoveId.HYPER_DRILL}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.GLIGAR]: [ - new SpeciesEvolution(SpeciesId.GLISCOR, 1, EvolutionItem.RAZOR_FANG, new TimeOfDayEvolutionCondition("night") /* Razor fang at night*/, SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.GLISCOR, 1, EvolutionItem.RAZOR_FANG, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]} /* Razor fang at night*/, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.SNEASEL]: [ - new SpeciesEvolution(SpeciesId.WEAVILE, 1, EvolutionItem.RAZOR_CLAW, new TimeOfDayEvolutionCondition("night") /* Razor claw at night*/, SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.WEAVILE, 1, EvolutionItem.RAZOR_CLAW, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]} /* Razor claw at night*/, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.URSARING]: [ new SpeciesEvolution(SpeciesId.URSALUNA, 1, EvolutionItem.PEAT_BLOCK, null, SpeciesWildEvolutionDelay.VERY_LONG) //Ursaring does not evolve into Bloodmoon Ursaluna ], [SpeciesId.PILOSWINE]: [ - new SpeciesEvolution(SpeciesId.MAMOSWINE, 1, null, new MoveEvolutionCondition(MoveId.ANCIENT_POWER), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.MAMOSWINE, 1, null, {key: EvoCondKey.MOVE, move: MoveId.ANCIENT_POWER}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.STANTLER]: [ - new SpeciesEvolution(SpeciesId.WYRDEER, 25, null, new MoveEvolutionCondition(MoveId.PSYSHIELD_BASH), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.WYRDEER, 25, null, {key: EvoCondKey.MOVE, move: MoveId.PSYSHIELD_BASH}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.LOMBRE]: [ new SpeciesEvolution(SpeciesId.LUDICOLO, 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG) @@ -1595,11 +1563,11 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.ROSERADE, 1, EvolutionItem.SHINY_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.BONSLY]: [ - new SpeciesEvolution(SpeciesId.SUDOWOODO, 1, null, new MoveEvolutionCondition(MoveId.MIMIC), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(SpeciesId.SUDOWOODO, 1, null, {key: EvoCondKey.MOVE, move: MoveId.MIMIC}, SpeciesWildEvolutionDelay.MEDIUM) ], [SpeciesId.MIME_JR]: [ - new SpeciesEvolution(SpeciesId.GALAR_MR_MIME, 1, null, new MoveTimeOfDayEvolutionCondition(MoveId.MIMIC, "night"), SpeciesWildEvolutionDelay.MEDIUM), - new SpeciesEvolution(SpeciesId.MR_MIME, 1, null, new MoveTimeOfDayEvolutionCondition(MoveId.MIMIC, "day"), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(SpeciesId.GALAR_MR_MIME, 1, null, [{key: EvoCondKey.MOVE, move: MoveId.MIMIC}, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}], SpeciesWildEvolutionDelay.MEDIUM), + new SpeciesEvolution(SpeciesId.MR_MIME, 1, null, [{key: EvoCondKey.MOVE, move: MoveId.MIMIC}, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}], SpeciesWildEvolutionDelay.MEDIUM) ], [SpeciesId.PANSAGE]: [ new SpeciesEvolution(SpeciesId.SIMISAGE, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG) @@ -1621,8 +1589,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.LILLIGANT, 1, EvolutionItem.SUN_STONE, null, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.BASCULIN]: [ - new SpeciesFormEvolution(SpeciesId.BASCULEGION, "white-striped", "female", 40, null, new GenderEvolutionCondition(Gender.FEMALE), SpeciesWildEvolutionDelay.VERY_LONG), - new SpeciesFormEvolution(SpeciesId.BASCULEGION, "white-striped", "male", 40, null, new GenderEvolutionCondition(Gender.MALE), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesFormEvolution(SpeciesId.BASCULEGION, "white-striped", "female", 40, null, [{key: EvoCondKey.GENDER, gender: Gender.FEMALE}], SpeciesWildEvolutionDelay.VERY_LONG), + new SpeciesFormEvolution(SpeciesId.BASCULEGION, "white-striped", "male", 40, null, [{key: EvoCondKey.GENDER, gender: Gender.MALE}], SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.MINCCINO]: [ new SpeciesEvolution(SpeciesId.CINCCINO, 1, EvolutionItem.SHINY_STONE, null, SpeciesWildEvolutionDelay.LONG) @@ -1650,14 +1618,14 @@ export const pokemonEvolutions: PokemonEvolutions = { ], [SpeciesId.ROCKRUFF]: [ new SpeciesFormEvolution(SpeciesId.LYCANROC, "own-tempo", "dusk", 25, null, null), - new SpeciesFormEvolution(SpeciesId.LYCANROC, "", "midday", 25, null, new TimeOfDayEvolutionCondition("day")), - new SpeciesFormEvolution(SpeciesId.LYCANROC, "", "midnight", 25, null, new TimeOfDayEvolutionCondition("night")) + new SpeciesFormEvolution(SpeciesId.LYCANROC, "", "midday", 25, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}), + new SpeciesFormEvolution(SpeciesId.LYCANROC, "", "midnight", 25, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}) ], [SpeciesId.STEENEE]: [ - new SpeciesEvolution(SpeciesId.TSAREENA, 28, null, new MoveEvolutionCondition(MoveId.STOMP), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.TSAREENA, 28, null, {key: EvoCondKey.MOVE, move: MoveId.STOMP}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.POIPOLE]: [ - new SpeciesEvolution(SpeciesId.NAGANADEL, 1, null, new MoveEvolutionCondition(MoveId.DRAGON_PULSE), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.NAGANADEL, 1, null, {key: EvoCondKey.MOVE, move: MoveId.DRAGON_PULSE}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.ALOLA_SANDSHREW]: [ new SpeciesEvolution(SpeciesId.ALOLA_SANDSLASH, 1, EvolutionItem.ICE_STONE, null, SpeciesWildEvolutionDelay.LONG) @@ -1671,7 +1639,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.APPLETUN, 1, EvolutionItem.SWEET_APPLE, null, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.CLOBBOPUS]: [ - new SpeciesEvolution(SpeciesId.GRAPPLOCT, 35, null, new MoveEvolutionCondition(MoveId.TAUNT)/*Once Taunt is implemented, change evo level to 1 and delay to LONG*/) + new SpeciesEvolution(SpeciesId.GRAPPLOCT, 35, null, {key: EvoCondKey.MOVE, move: MoveId.TAUNT}/*Once Taunt is implemented, change evo level to 1 and delay to LONG*/) ], [SpeciesId.SINISTEA]: [ new SpeciesFormEvolution(SpeciesId.POLTEAGEIST, "phony", "phony", 1, EvolutionItem.CRACKED_POT, null, SpeciesWildEvolutionDelay.LONG), @@ -1679,31 +1647,31 @@ export const pokemonEvolutions: PokemonEvolutions = { ], [SpeciesId.MILCERY]: [ new SpeciesFormEvolution(SpeciesId.ALCREMIE, "", "vanilla-cream", 1, EvolutionItem.STRAWBERRY_SWEET, - new BiomeEvolutionCondition([ BiomeId.TOWN, BiomeId.PLAINS, BiomeId.GRASS, BiomeId.TALL_GRASS, BiomeId.METROPOLIS ]), + {key: EvoCondKey.BIOME, biome: [ BiomeId.TOWN, BiomeId.PLAINS, BiomeId.GRASS, BiomeId.TALL_GRASS, BiomeId.METROPOLIS ]}, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.ALCREMIE, "", "ruby-cream", 1, EvolutionItem.STRAWBERRY_SWEET, - new BiomeEvolutionCondition([ BiomeId.BADLANDS, BiomeId.VOLCANO, BiomeId.GRAVEYARD, BiomeId.FACTORY, BiomeId.SLUM ]), + {key: EvoCondKey.BIOME, biome: [ BiomeId.BADLANDS, BiomeId.VOLCANO, BiomeId.GRAVEYARD, BiomeId.FACTORY, BiomeId.SLUM ]}, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.ALCREMIE, "", "matcha-cream", 1, EvolutionItem.STRAWBERRY_SWEET, - new BiomeEvolutionCondition([ BiomeId.FOREST, BiomeId.SWAMP, BiomeId.MEADOW, BiomeId.JUNGLE ]), + {key: EvoCondKey.BIOME, biome: [ BiomeId.FOREST, BiomeId.SWAMP, BiomeId.MEADOW, BiomeId.JUNGLE ]}, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.ALCREMIE, "", "mint-cream", 1, EvolutionItem.STRAWBERRY_SWEET, - new BiomeEvolutionCondition([ BiomeId.SEA, BiomeId.BEACH, BiomeId.LAKE, BiomeId.SEABED ]), + {key: EvoCondKey.BIOME, biome: [ BiomeId.SEA, BiomeId.BEACH, BiomeId.LAKE, BiomeId.SEABED ]}, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.ALCREMIE, "", "lemon-cream", 1, EvolutionItem.STRAWBERRY_SWEET, - new BiomeEvolutionCondition([ BiomeId.DESERT, BiomeId.POWER_PLANT, BiomeId.DOJO, BiomeId.RUINS, BiomeId.CONSTRUCTION_SITE ]), + {key: EvoCondKey.BIOME, biome: [ BiomeId.DESERT, BiomeId.POWER_PLANT, BiomeId.DOJO, BiomeId.RUINS, BiomeId.CONSTRUCTION_SITE ]}, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.ALCREMIE, "", "salted-cream", 1, EvolutionItem.STRAWBERRY_SWEET, - new BiomeEvolutionCondition([ BiomeId.MOUNTAIN, BiomeId.CAVE, BiomeId.ICE_CAVE, BiomeId.FAIRY_CAVE, BiomeId.SNOWY_FOREST ]), + {key: EvoCondKey.BIOME, biome: [ BiomeId.MOUNTAIN, BiomeId.CAVE, BiomeId.ICE_CAVE, BiomeId.FAIRY_CAVE, BiomeId.SNOWY_FOREST ]}, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.ALCREMIE, "", "ruby-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, - new BiomeEvolutionCondition([ BiomeId.WASTELAND, BiomeId.LABORATORY ]), + {key: EvoCondKey.BIOME, biome: [ BiomeId.WASTELAND, BiomeId.LABORATORY ]}, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.ALCREMIE, "", "caramel-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, - new BiomeEvolutionCondition([ BiomeId.TEMPLE, BiomeId.ISLAND ]), + {key: EvoCondKey.BIOME, biome: [ BiomeId.TEMPLE, BiomeId.ISLAND ]}, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(SpeciesId.ALCREMIE, "", "rainbow-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, - new BiomeEvolutionCondition([ BiomeId.ABYSS, BiomeId.SPACE, BiomeId.END ]), + {key: EvoCondKey.BIOME, biome: [ BiomeId.ABYSS, BiomeId.SPACE, BiomeId.END ]}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.DURALUDON]: [ @@ -1723,10 +1691,10 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.HISUI_ELECTRODE, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.HISUI_QWILFISH]: [ - new SpeciesEvolution(SpeciesId.OVERQWIL, 28, null, new MoveEvolutionCondition(MoveId.BARB_BARRAGE), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.OVERQWIL, 28, null, {key: EvoCondKey.MOVE, move: MoveId.BARB_BARRAGE}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.HISUI_SNEASEL]: [ - new SpeciesEvolution(SpeciesId.SNEASLER, 1, EvolutionItem.RAZOR_CLAW, new TimeOfDayEvolutionCondition("day") /* Razor claw at day*/, SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.SNEASLER, 1, EvolutionItem.RAZOR_CLAW, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]} /* Razor claw at day*/, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.CHARCADET]: [ new SpeciesEvolution(SpeciesId.ARMAROUGE, 1, EvolutionItem.AUSPICIOUS_ARMOR, null, SpeciesWildEvolutionDelay.LONG), @@ -1746,7 +1714,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesFormEvolution(SpeciesId.SINISTCHA, "artisan", "masterpiece", 1, EvolutionItem.MASTERPIECE_TEACUP, null, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.DIPPLIN]: [ - new SpeciesEvolution(SpeciesId.HYDRAPPLE, 1, null, new MoveEvolutionCondition(MoveId.DRAGON_CHEER), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.HYDRAPPLE, 1, null, {key: EvoCondKey.MOVE, move: MoveId.DRAGON_CHEER}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.KADABRA]: [ new SpeciesEvolution(SpeciesId.ALAKAZAM, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) @@ -1761,7 +1729,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.GENGAR, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.ONIX]: [ - new SpeciesEvolution(SpeciesId.STEELIX, 1, EvolutionItem.LINKING_CORD, new MoveTypeEvolutionCondition(PokemonType.STEEL), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.STEELIX, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.MOVE_TYPE, pkmnType: PokemonType.STEEL}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.RHYDON]: [ new SpeciesEvolution(SpeciesId.RHYPERIOR, 1, EvolutionItem.PROTECTOR, null, SpeciesWildEvolutionDelay.VERY_LONG) @@ -1770,7 +1738,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.KINGDRA, 1, EvolutionItem.DRAGON_SCALE, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.SCYTHER]: [ - new SpeciesEvolution(SpeciesId.SCIZOR, 1, EvolutionItem.LINKING_CORD, new MoveTypeEvolutionCondition(PokemonType.STEEL), SpeciesWildEvolutionDelay.VERY_LONG), + new SpeciesEvolution(SpeciesId.SCIZOR, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.MOVE_TYPE, pkmnType: PokemonType.STEEL}, SpeciesWildEvolutionDelay.VERY_LONG), new SpeciesEvolution(SpeciesId.KLEAVOR, 1, EvolutionItem.BLACK_AUGURITE, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.ELECTABUZZ]: [ @@ -1792,9 +1760,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.DUSKNOIR, 1, EvolutionItem.REAPER_CLOTH, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.CLAMPERL]: [ - // TODO: Change the SpeciesEvolutionConditions here to use a bespoke HeldItemEvolutionCondition after the modifier rework - new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.getHeldItems().some(m => m.is("SpeciesStatBoosterModifier") && (m.type as SpeciesStatBoosterModifierType).key === "DEEP_SEA_TOOTH")), SpeciesWildEvolutionDelay.VERY_LONG), - new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.getHeldItems().some(m => m.is("SpeciesStatBoosterModifier") && (m.type as SpeciesStatBoosterModifierType).key === "DEEP_SEA_SCALE")), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: "DEEP_SEA_TOOTH"}, SpeciesWildEvolutionDelay.VERY_LONG), + new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: "DEEP_SEA_SCALE"}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.BOLDORE]: [ new SpeciesEvolution(SpeciesId.GIGALITH, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) @@ -1803,10 +1770,10 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.CONKELDURR, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.KARRABLAST]: [ - new SpeciesEvolution(SpeciesId.ESCAVALIER, 1, EvolutionItem.LINKING_CORD, new CaughtEvolutionCondition(SpeciesId.SHELMET), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.ESCAVALIER, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.SPECIES_CAUGHT, speciesCaught: SpeciesId.SHELMET}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.SHELMET]: [ - new SpeciesEvolution(SpeciesId.ACCELGOR, 1, EvolutionItem.LINKING_CORD, new CaughtEvolutionCondition(SpeciesId.KARRABLAST), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.ACCELGOR, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.SPECIES_CAUGHT, speciesCaught: SpeciesId.KARRABLAST}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.SPRITZEE]: [ new SpeciesEvolution(SpeciesId.AROMATISSE, 1, EvolutionItem.SACHET, null, SpeciesWildEvolutionDelay.VERY_LONG) @@ -1824,66 +1791,66 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.ALOLA_GOLEM, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.PRIMEAPE]: [ - new SpeciesEvolution(SpeciesId.ANNIHILAPE, 35, null, new MoveEvolutionCondition(MoveId.RAGE_FIST), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.ANNIHILAPE, 35, null, {key: EvoCondKey.MOVE, move: MoveId.RAGE_FIST}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.GOLBAT]: [ - new SpeciesEvolution(SpeciesId.CROBAT, 1, null, new FriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.CROBAT, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 120}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.CHANSEY]: [ - new SpeciesEvolution(SpeciesId.BLISSEY, 1, null, new FriendshipEvolutionCondition(200), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.BLISSEY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 200}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.PICHU]: [ - new SpeciesFormEvolution(SpeciesId.PIKACHU, "spiky", "partner", 1, null, new FriendshipEvolutionCondition(90), SpeciesWildEvolutionDelay.SHORT), - new SpeciesFormEvolution(SpeciesId.PIKACHU, "", "", 1, null, new FriendshipEvolutionCondition(90), SpeciesWildEvolutionDelay.SHORT), + new SpeciesFormEvolution(SpeciesId.PIKACHU, "spiky", "partner", 1, null, {key: EvoCondKey.FRIENDSHIP, value: 90}, SpeciesWildEvolutionDelay.SHORT), + new SpeciesFormEvolution(SpeciesId.PIKACHU, "", "", 1, null, {key: EvoCondKey.FRIENDSHIP, value: 90}, SpeciesWildEvolutionDelay.SHORT), ], [SpeciesId.CLEFFA]: [ - new SpeciesEvolution(SpeciesId.CLEFAIRY, 1, null, new FriendshipEvolutionCondition(160), SpeciesWildEvolutionDelay.SHORT) + new SpeciesEvolution(SpeciesId.CLEFAIRY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 160}, SpeciesWildEvolutionDelay.SHORT) ], [SpeciesId.IGGLYBUFF]: [ - new SpeciesEvolution(SpeciesId.JIGGLYPUFF, 1, null, new FriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.SHORT) + new SpeciesEvolution(SpeciesId.JIGGLYPUFF, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 70}, SpeciesWildEvolutionDelay.SHORT) ], [SpeciesId.TOGEPI]: [ - new SpeciesEvolution(SpeciesId.TOGETIC, 1, null, new FriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.SHORT) + new SpeciesEvolution(SpeciesId.TOGETIC, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 70}, SpeciesWildEvolutionDelay.SHORT) ], [SpeciesId.AZURILL]: [ - new SpeciesEvolution(SpeciesId.MARILL, 1, null, new FriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.SHORT) + new SpeciesEvolution(SpeciesId.MARILL, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 70}, SpeciesWildEvolutionDelay.SHORT) ], [SpeciesId.BUDEW]: [ - new SpeciesEvolution(SpeciesId.ROSELIA, 1, null, new FriendshipTimeOfDayEvolutionCondition(70, "day"), SpeciesWildEvolutionDelay.SHORT) + new SpeciesEvolution(SpeciesId.ROSELIA, 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 70}, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}], SpeciesWildEvolutionDelay.SHORT) ], [SpeciesId.BUNEARY]: [ - new SpeciesEvolution(SpeciesId.LOPUNNY, 1, null, new FriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(SpeciesId.LOPUNNY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 70}, SpeciesWildEvolutionDelay.MEDIUM) ], [SpeciesId.CHINGLING]: [ - new SpeciesEvolution(SpeciesId.CHIMECHO, 1, null, new FriendshipTimeOfDayEvolutionCondition(90, "night"), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(SpeciesId.CHIMECHO, 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 90}, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}], SpeciesWildEvolutionDelay.MEDIUM) ], [SpeciesId.HAPPINY]: [ - new SpeciesEvolution(SpeciesId.CHANSEY, 1, null, new FriendshipEvolutionCondition(160), SpeciesWildEvolutionDelay.SHORT) + new SpeciesEvolution(SpeciesId.CHANSEY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 160}, SpeciesWildEvolutionDelay.SHORT) ], [SpeciesId.MUNCHLAX]: [ - new SpeciesEvolution(SpeciesId.SNORLAX, 1, null, new FriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.SNORLAX, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 120}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.RIOLU]: [ - new SpeciesEvolution(SpeciesId.LUCARIO, 1, null, new FriendshipTimeOfDayEvolutionCondition(120, "day"), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.LUCARIO, 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 120}, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}], SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.WOOBAT]: [ - new SpeciesEvolution(SpeciesId.SWOOBAT, 1, null, new FriendshipEvolutionCondition(90), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(SpeciesId.SWOOBAT, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 90}, SpeciesWildEvolutionDelay.MEDIUM) ], [SpeciesId.SWADLOON]: [ - new SpeciesEvolution(SpeciesId.LEAVANNY, 1, null, new FriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.LEAVANNY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 120}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.TYPE_NULL]: [ - new SpeciesEvolution(SpeciesId.SILVALLY, 1, null, new FriendshipEvolutionCondition(100), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.SILVALLY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 100}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.ALOLA_MEOWTH]: [ - new SpeciesEvolution(SpeciesId.ALOLA_PERSIAN, 1, null, new FriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(SpeciesId.ALOLA_PERSIAN, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 120}, SpeciesWildEvolutionDelay.LONG) ], [SpeciesId.SNOM]: [ - new SpeciesEvolution(SpeciesId.FROSMOTH, 1, null, new FriendshipTimeOfDayEvolutionCondition(90, "night"), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(SpeciesId.FROSMOTH, 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 90}, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}], SpeciesWildEvolutionDelay.MEDIUM) ], [SpeciesId.GIMMIGHOUL]: [ - new SpeciesFormEvolution(SpeciesId.GHOLDENGO, "chest", "", 1, null, new TreasureEvolutionCondition(), SpeciesWildEvolutionDelay.VERY_LONG), - new SpeciesFormEvolution(SpeciesId.GHOLDENGO, "roaming", "", 1, null, new TreasureEvolutionCondition(), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesFormEvolution(SpeciesId.GHOLDENGO, "chest", "", 1, null, {key: EvoCondKey.EVO_TREASURE_TRACKER, value: 10}, SpeciesWildEvolutionDelay.VERY_LONG), + new SpeciesFormEvolution(SpeciesId.GHOLDENGO, "roaming", "", 1, null, {key: EvoCondKey.EVO_TREASURE_TRACKER, value: 10}, SpeciesWildEvolutionDelay.VERY_LONG) ] }; diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 683fb48a9ba..3fdd83c185d 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -4,7 +4,8 @@ import i18next from "i18next"; import type { DexAttrProps, GameData } from "#app/system/game-data"; import { defaultStarterSpecies } from "#app/constants"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { speciesStarterCosts } from "#app/data/balance/starters"; import type Pokemon from "#app/field/pokemon"; import { PokemonMove } from "./moves/pokemon-move"; diff --git a/src/data/daily-run.ts b/src/data/daily-run.ts index fc60e5795dc..e869ba7f28f 100644 --- a/src/data/daily-run.ts +++ b/src/data/daily-run.ts @@ -5,7 +5,8 @@ import { PlayerPokemon } from "#app/field/pokemon"; import type { Starter } from "#app/ui/starter-select-ui-handler"; import { randSeedGauss, randSeedInt, randSeedItem, getEnumValues } from "#app/utils/common"; import type { PokemonSpeciesForm } from "#app/data/pokemon-species"; -import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import PokemonSpecies, { getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { BiomeId } from "#enums/biome-id"; diff --git a/src/data/data-lists.ts b/src/data/data-lists.ts index 4d482dc2d7d..ed172846fe1 100644 --- a/src/data/data-lists.ts +++ b/src/data/data-lists.ts @@ -1,9 +1,11 @@ +import type PokemonSpecies from "#app/data/pokemon-species"; import type { ModifierTypes } from "#app/modifier/modifier-type"; import type { Ability } from "./abilities/ability"; import type Move from "./moves/move"; export const allAbilities: Ability[] = []; export const allMoves: Move[] = []; +export const allSpecies: PokemonSpecies[] = []; // TODO: Figure out what this is used for and provide an appropriate tsdoc comment export const modifierTypes = {} as ModifierTypes; diff --git a/src/data/egg.ts b/src/data/egg.ts index 67cdb7b1344..a6e2e04a5fe 100644 --- a/src/data/egg.ts +++ b/src/data/egg.ts @@ -1,7 +1,7 @@ import type BattleScene from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { VariantTier } from "#enums/variant-tier"; import { randInt, randomString, randSeedInt, getIvsFromId } from "#app/utils/common"; diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index e3741226335..bec75288837 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -22,7 +22,7 @@ import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encoun import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { BerryModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoveId } from "#enums/move-id"; import { BattlerTagType } from "#enums/battler-tag-type"; import { randInt } from "#app/utils/common"; diff --git a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts index 271346616d1..f8a904cdb45 100644 --- a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts +++ b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts @@ -18,7 +18,7 @@ import { } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index b873e30fe0c..9bdaa603540 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -22,7 +22,7 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { AbilityId } from "#enums/ability-id"; import { applyAbilityOverrideToPokemon, diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index 42af5339a80..c68e395b379 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -19,7 +19,7 @@ import { getEncounterPokemonLevelForWave, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER, } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { TrainerSlot } from "#enums/trainer-slot"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 002b38f445d..4056ba3532e 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -4,7 +4,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { globalScene } from "#app/global-scene"; import { modifierTypes } from "#app/data/data-lists"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index 692ffe6e80e..842fc9d73bd 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -15,7 +15,7 @@ import { updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 94dc4ab6153..e8900f8def4 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -20,7 +20,7 @@ import { TypeRequirement, } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { SpeciesId } from "#enums/species-id"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { Gender } from "#app/data/gender"; import { PokemonType } from "#enums/pokemon-type"; import { BattlerIndex } from "#enums/battler-index"; diff --git a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts index 48557541512..c57232402dd 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -14,7 +14,7 @@ import { TrainerSlot } from "#enums/trainer-slot"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { FieldPosition } from "#enums/field-position"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 0393bcdaa62..6bbc1a68772 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -16,7 +16,8 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { SpeciesId } from "#enums/species-id"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; +import { allSpecies } from "#app/data/data-lists"; import { getTypeRgb } from "#app/data/type"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; diff --git a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts index 8eea0623cd4..2b54b0a42f5 100644 --- a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts +++ b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts @@ -1,4 +1,4 @@ -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index 022b0125fde..321e65d7008 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -14,7 +14,7 @@ import { getHighestLevelPlayerPokemon, koPlayerPokemon, } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { ModifierTier } from "#enums/modifier-tier"; import { randSeedInt } from "#app/utils/common"; diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index d324e9f9b6c..207e6ca400d 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -17,7 +17,7 @@ import { PokeballType } from "#enums/pokeball"; import { PlayerGender } from "#enums/player-gender"; import { NumberHolder, randSeedInt } from "#app/utils/common"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { doPlayerFlee, diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index 6b6cd71af1f..4169fd6d7c5 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -24,7 +24,7 @@ import { MoveId } from "#enums/move-id"; import { BattlerIndex } from "#enums/battler-index"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import { AiType } from "#enums/ai-type"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { BerryType } from "#enums/berry-type"; diff --git a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts index 0676b40c548..03c09f6918e 100644 --- a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts @@ -15,7 +15,7 @@ import { BiomeId } from "#enums/biome-id"; import { TrainerType } from "#enums/trainer-type"; import i18next from "i18next"; import { SpeciesId } from "#enums/species-id"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { Nature } from "#enums/nature"; import { MoveId } from "#enums/move-id"; diff --git a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts index 0ad209ae4c6..c3bcf9ceb24 100644 --- a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts @@ -15,7 +15,7 @@ import { getSpriteKeysFromPokemon, } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { SpeciesId } from "#enums/species-id"; import { PokeballType } from "#enums/pokeball"; diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index adf4f9dde8f..37d16075543 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -13,7 +13,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { SpeciesId } from "#enums/species-id"; import { Nature } from "#enums/nature"; import type Pokemon from "#app/field/pokemon"; diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index e2c87d8c0ae..eba8a6ba00e 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -17,7 +17,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { TrainerType } from "#enums/trainer-type"; import { SpeciesId } from "#enums/species-id"; import { AbilityId } from "#enums/ability-id"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoveId } from "#enums/move-id"; import { Nature } from "#enums/nature"; import { PokemonType } from "#enums/pokemon-type"; diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index 62413b96523..e2a740c4900 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -22,7 +22,7 @@ import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/u import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import i18next from "#app/plugins/i18n"; import { ModifierTier } from "#enums/modifier-tier"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoveId } from "#enums/move-id"; import { BattlerIndex } from "#enums/battler-index"; import { PokemonMove } from "#app/data/moves/pokemon-move"; diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 83e876d1aa8..1ba756c7f5d 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -19,7 +19,8 @@ import type Pokemon from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import { NumberHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils/common"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; +import { allSpecies } from "#app/data/data-lists"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier } from "#app/modifier/modifier"; import { achvs } from "#app/system/achv"; diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index a6e6e84846f..07fd155b2b2 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -1,6 +1,5 @@ import { globalScene } from "#app/global-scene"; import { allAbilities } from "../data-lists"; -import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { Nature } from "#enums/nature"; import { pokemonFormChanges } from "#app/data/pokemon-forms"; import { SpeciesFormChangeItemTrigger } from "../pokemon-forms/form-change-triggers"; @@ -16,7 +15,6 @@ import type { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; -import { SpeciesFormKey } from "#enums/species-form-key"; import { TimeOfDay } from "#enums/time-of-day"; export interface EncounterRequirement { @@ -834,70 +832,6 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen } } -export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement { - requiredEvolutionItem: EvolutionItem[]; - minNumberOfPokemon: number; - invertQuery: boolean; - - constructor(evolutionItems: EvolutionItem | EvolutionItem[], minNumberOfPokemon = 1, invertQuery = false) { - super(); - this.minNumberOfPokemon = minNumberOfPokemon; - this.invertQuery = invertQuery; - this.requiredEvolutionItem = coerceArray(evolutionItems); - } - - override meetsRequirement(): boolean { - const partyPokemon = globalScene.getPlayerParty(); - if (isNullOrUndefined(partyPokemon) || this.requiredEvolutionItem?.length < 0) { - return false; - } - return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon; - } - - filterByEvo(pokemon, evolutionItem) { - if ( - pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) && - pokemonEvolutions[pokemon.species.speciesId].filter( - e => e.item === evolutionItem && (!e.condition || e.condition.predicate(pokemon)), - ).length && - pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX - ) { - return true; - } - - return ( - pokemon.isFusion() && - pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) && - pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter( - e => e.item === evolutionItem && (!e.condition || e.condition.predicate(pokemon)), - ).length && - pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX - ); - } - - override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { - if (!this.invertQuery) { - return partyPokemon.filter( - pokemon => - this.requiredEvolutionItem.filter(evolutionItem => this.filterByEvo(pokemon, evolutionItem)).length > 0, - ); - } - // for an inverted query, we only want to get the pokemon that don't have ANY of the listed evolutionItemss - return partyPokemon.filter( - pokemon => - this.requiredEvolutionItem.filter(evolutionItems => this.filterByEvo(pokemon, evolutionItems)).length === 0, - ); - } - - override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { - const requiredItems = this.requiredEvolutionItem.filter(evoItem => this.filterByEvo(pokemon, evoItem)); - if (requiredItems.length > 0) { - return ["evolutionItem", EvolutionItem[requiredItems[0]]]; - } - return ["evolutionItem", ""]; - } -} - export class HeldItemRequirement extends EncounterPokemonRequirement { requiredHeldItemModifiers: string[]; minNumberOfPokemon: number; diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index eaa4f08ef70..bb74f11ce60 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -48,7 +48,7 @@ import type HeldModifierConfig from "#app/@types/held-modifier-config"; import type { Variant } from "#app/sprites/variant"; import { StatusEffect } from "#enums/status-effect"; import { globalScene } from "#app/global-scene"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { PokemonType } from "#enums/pokemon-type"; import { getNatureName } from "#app/data/nature"; import { getPokemonNameWithAffix } from "#app/messages"; diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index 4671869a2ba..93abd432ef5 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -20,7 +20,7 @@ import { PartyUiMode } from "#app/ui/party-ui-handler"; import { SpeciesId } from "#enums/species-id"; import type { PokemonType } from "#enums/pokemon-type"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { getEncounterText, diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index bb6ede7731d..8e7e029fd56 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -42,6 +42,8 @@ import { starterPassiveAbilities } from "#app/data/balance/passives"; import { loadPokemonVariantAssets } from "#app/sprites/pokemon-sprite"; import { hasExpSprite } from "#app/sprites/sprite-utils"; import { Gender } from "./gender"; +import { allSpecies } from "#app/data/data-lists"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; export enum Region { NORMAL, @@ -82,24 +84,6 @@ export const normalForm: SpeciesId[] = [ SpeciesId.CALYREX, ]; -/** - * Gets the {@linkcode PokemonSpecies} object associated with the {@linkcode SpeciesId} enum given - * @param species - The {@linkcode SpeciesId} to fetch. - * If an array of `SpeciesId`s is passed (such as for named trainer spawn pools), - * one will be selected at random. - * @returns The associated {@linkcode PokemonSpecies} object - */ -export function getPokemonSpecies(species: SpeciesId | SpeciesId[]): PokemonSpecies { - if (Array.isArray(species)) { - // TODO: this RNG roll should not be handled by this function - species = species[Math.floor(Math.random() * species.length)]; - } - if (species >= 2000) { - return allSpecies.find(s => s.speciesId === species)!; // TODO: is this bang correct? - } - return allSpecies[species - 1]; -} - export function getPokemonSpeciesForm(species: SpeciesId, formIndex: number): PokemonSpeciesForm { const retSpecies: PokemonSpecies = species >= 2000 @@ -1449,8 +1433,6 @@ export function getPokerusStarters(): PokemonSpecies[] { return pokerusStarters; } -export const allSpecies: PokemonSpecies[] = []; - // biome-ignore format: manually formatted export function initSpecies() { allSpecies.push( diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 063dddafee8..6786aa00ef7 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -10,7 +10,7 @@ import { randSeedIntRange, } from "#app/utils/common"; import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { tmSpecies } from "#app/data/balance/tms"; import { doubleBattleDialogue } from "../double-battle-dialogue"; import { TrainerVariant } from "#enums/trainer-variant"; diff --git a/src/field/arena.ts b/src/field/arena.ts index aece908d653..8d7e5037852 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -3,7 +3,7 @@ import type { BiomeTierTrainerPools, PokemonPools } from "#app/data/balance/biom import { biomePokemonPools, BiomePoolTier, biomeTrainerPools } from "#app/data/balance/biomes"; import { randSeedInt, NumberHolder, isNullOrUndefined, type Constructor } from "#app/utils/common"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { getTerrainClearMessage, getTerrainStartMessage, diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index fe89142b5c6..e9cc4f70d70 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -15,12 +15,8 @@ import { allMoves } from "#app/data/data-lists"; import { MoveTarget } from "#enums/MoveTarget"; import { MoveCategory } from "#enums/MoveCategory"; import type { PokemonSpeciesForm } from "#app/data/pokemon-species"; -import { - default as PokemonSpecies, - getFusedSpeciesName, - getPokemonSpecies, - getPokemonSpeciesForm, -} from "#app/data/pokemon-species"; +import { default as PokemonSpecies, getFusedSpeciesName, getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { NumberHolder, @@ -79,11 +75,12 @@ import { import { PokeballType } from "#enums/pokeball"; import { Gender } from "#app/data/gender"; import { Status, getRandomStatus } from "#app/data/status-effect"; -import type { SpeciesFormEvolution, SpeciesEvolutionCondition } from "#app/data/balance/pokemon-evolutions"; +import type { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions, FusionSpeciesFormEvolution, + validateShedinjaEvo, } from "#app/data/balance/pokemon-evolutions"; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms"; import { @@ -370,7 +367,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.metWave = dataSource.metWave ?? (this.metBiome === -1 ? -1 : 0); this.pauseEvolutions = dataSource.pauseEvolutions; this.pokerus = !!dataSource.pokerus; - this.evoCounter = dataSource.evoCounter ?? 0; this.fusionSpecies = dataSource.fusionSpecies instanceof PokemonSpecies ? dataSource.fusionSpecies @@ -2518,14 +2514,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (pokemonEvolutions.hasOwnProperty(this.species.speciesId)) { const evolutions = pokemonEvolutions[this.species.speciesId]; for (const e of evolutions) { - if ( - !e.item && - this.level >= e.level && - (isNullOrUndefined(e.preFormKey) || this.getFormKey() === e.preFormKey) - ) { - if (e.condition === null || (e.condition as SpeciesEvolutionCondition).predicate(this)) { - return e; - } + if (e.validate(this)) { + return e; } } } @@ -2535,14 +2525,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { e => new FusionSpeciesFormEvolution(this.species.speciesId, e), ); for (const fe of fusionEvolutions) { - if ( - !fe.item && - this.level >= fe.level && - (isNullOrUndefined(fe.preFormKey) || this.getFusionFormKey() === fe.preFormKey) - ) { - if (fe.condition === null || (fe.condition as SpeciesEvolutionCondition).predicate(this)) { - return fe; - } + if (fe.validate(this)) { + return fe; } } } @@ -5487,6 +5471,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } this.turnData.berriesEaten.push(berryType); } + + getPersistentTreasureCount(): number { + return ( + this.getHeldItems().filter(m => m.is("DamageMoneyRewardModifier")).length + + globalScene.findModifiers(m => m.is("MoneyMultiplierModifier") || m.is("ExtraModifierModifier")).length + ); + } } export class PlayerPokemon extends Pokemon { @@ -5825,7 +5816,7 @@ export class PlayerPokemon extends Pokemon { if (evoSpecies?.speciesId === SpeciesId.NINCADA && evolution.speciesId === SpeciesId.NINJASK) { const newEvolution = pokemonEvolutions[evoSpecies.speciesId][1]; - if (newEvolution.condition?.predicate(this)) { + if (validateShedinjaEvo()) { const newPokemon = globalScene.addPlayerPokemon( this.species, this.level, @@ -5855,7 +5846,6 @@ export class PlayerPokemon extends Pokemon { newPokemon.fusionLuck = this.fusionLuck; newPokemon.fusionTeraType = this.fusionTeraType; newPokemon.usedTMs = this.usedTMs; - newPokemon.evoCounter = this.evoCounter; globalScene.getPlayerParty().push(newPokemon); newPokemon.evolve(!isFusion ? newEvolution : new FusionSpeciesFormEvolution(this.id, newEvolution), evoSpecies); @@ -5944,7 +5934,6 @@ export class PlayerPokemon extends Pokemon { this.fusionGender = pokemon.gender; this.fusionLuck = pokemon.luck; this.fusionCustomPokemonData = pokemon.customPokemonData; - this.evoCounter = Math.max(pokemon.evoCounter, this.evoCounter); if (pokemon.pauseEvolutions || this.pauseEvolutions) { this.pauseEvolutions = true; } @@ -6100,18 +6089,6 @@ export class EnemyPokemon extends Pokemon { this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0); - let prevolution: SpeciesId; - let speciesId = species.speciesId; - while ((prevolution = pokemonPrevolutions[speciesId])) { - const evolution = pokemonEvolutions[prevolution].find( - pe => pe.speciesId === speciesId && (!pe.evoFormKey || pe.evoFormKey === this.getFormKey()), - ); - if (evolution?.condition?.enforceFunc) { - evolution.condition.enforceFunc(this); - } - speciesId = prevolution; - } - if (this.hasTrainer() && globalScene.currentBattle) { const { waveIndex } = globalScene.currentBattle; const ivs: number[] = []; diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 8d950b08507..b64821d259a 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -1,7 +1,7 @@ import { globalScene } from "#app/global-scene"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import type { TrainerConfig } from "#app/data/trainers/trainer-config"; import type { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { trainerConfigs } from "#app/data/trainers/trainer-config"; diff --git a/src/game-mode.ts b/src/game-mode.ts index 9722d564e09..da6ef62e33c 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -5,7 +5,7 @@ import type { Challenge } from "./data/challenge"; import { allChallenges, applyChallenges, copyChallenge } from "./data/challenge"; import { ChallengeType } from "#enums/challenge-type"; import type PokemonSpecies from "./data/pokemon-species"; -import { allSpecies } from "./data/pokemon-species"; +import { allSpecies } from "#app/data/data-lists"; import type { Arena } from "./field/arena"; import Overrides from "#app/overrides"; import { isNullOrUndefined, randSeedInt, randSeedItem } from "#app/utils/common"; diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index cf373e6441a..a22486210b0 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1218,12 +1218,8 @@ export class EvolutionItemModifierType extends PokemonModifierType implements Ge (pokemon: PlayerPokemon) => { if ( pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) && - pokemonEvolutions[pokemon.species.speciesId].filter( - e => - e.item === this.evolutionItem && - (!e.condition || e.condition.predicate(pokemon)) && - (e.preFormKey === null || e.preFormKey === pokemon.getFormKey()), - ).length && + pokemonEvolutions[pokemon.species.speciesId].filter(e => e.validate(pokemon, false, this.evolutionItem)) + .length && pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX ) { return null; @@ -1232,12 +1228,8 @@ export class EvolutionItemModifierType extends PokemonModifierType implements Ge pokemon.isFusion() && pokemon.fusionSpecies && pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) && - pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter( - e => - e.item === this.evolutionItem && - (!e.condition || e.condition.predicate(pokemon)) && - (e.preFormKey === null || e.preFormKey === pokemon.getFusionFormKey()), - ).length && + pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter(e => e.validate(pokemon, true, this.evolutionItem)) + .length && pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX ) { return null; @@ -1597,12 +1589,7 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator { ) .flatMap(p => { const evolutions = pokemonEvolutions[p.species.speciesId]; - return evolutions.filter( - e => - e.item !== EvolutionItem.NONE && - (e.evoFormKey === null || (e.preFormKey || "") === p.getFormKey()) && - (!e.condition || e.condition.predicate(p)), - ); + return evolutions.filter(e => e.isValidItemEvolution(p)); }), party .filter( @@ -1616,16 +1603,11 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator { ) .flatMap(p => { const evolutions = pokemonEvolutions[p.fusionSpecies!.speciesId]; - return evolutions.filter( - e => - e.item !== EvolutionItem.NONE && - (e.evoFormKey === null || (e.preFormKey || "") === p.getFusionFormKey()) && - (!e.condition || e.condition.predicate(p)), - ); + return evolutions.filter(e => e.validate(p, true)); }), ] .flat() - .flatMap(e => e.item) + .flatMap(e => e.evoItem) .filter(i => (!!i && i > 50) === rare); if (!evolutionItemPool.length) { @@ -1892,7 +1874,8 @@ const modifierTypeInitObj = Object.freeze({ new PokemonHeldItemModifierType( "modifierType:ModifierType.EVOLUTION_TRACKER_GIMMIGHOUL", "relic_gold", - (type, args) => new EvoTrackerModifier(type, (args[0] as Pokemon).id, SpeciesId.GIMMIGHOUL, 10), + (type, args) => + new EvoTrackerModifier(type, (args[0] as Pokemon).id, SpeciesId.GIMMIGHOUL, 10, (args[1] as number) ?? 1), ), MEGA_BRACELET: () => diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index e11f2c07ce8..54b7323569a 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -772,6 +772,10 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier { return this.getMaxHeldItemCount(pokemon); } + getSpecies(): SpeciesId | null { + return null; + } + abstract getMaxHeldItemCount(pokemon?: Pokemon): number; } @@ -918,27 +922,14 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier { return true; } - getIconStackText(virtual?: boolean): Phaser.GameObjects.BitmapText | null { - if (this.getMaxStackCount() === 1 || (virtual && !this.virtualStackCount)) { - return null; - } + getIconStackText(_virtual?: boolean): Phaser.GameObjects.BitmapText | null { + const pokemon = this.getPokemon(); - const pokemon = globalScene.getPokemonById(this.pokemonId); + const count = (pokemon?.getPersistentTreasureCount() || 0) + this.getStackCount(); - this.stackCount = pokemon - ? pokemon.evoCounter + - pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length + - globalScene.findModifiers( - m => - m instanceof MoneyMultiplierModifier || - m instanceof ExtraModifierModifier || - m instanceof TempExtraModifierModifier, - ).length - : this.stackCount; - - const text = globalScene.add.bitmapText(10, 15, "item-count", this.stackCount.toString(), 11); + const text = globalScene.add.bitmapText(10, 15, "item-count", count.toString(), 11); text.letterSpacing = -0.5; - if (this.getStackCount() >= this.required) { + if (count >= this.required) { text.setTint(0xf89890); } text.setOrigin(0, 0); @@ -946,18 +937,13 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier { return text; } - getMaxHeldItemCount(pokemon: Pokemon): number { - this.stackCount = - pokemon.evoCounter + - pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length + - globalScene.findModifiers( - m => - m instanceof MoneyMultiplierModifier || - m instanceof ExtraModifierModifier || - m instanceof TempExtraModifierModifier, - ).length; + getMaxHeldItemCount(_pokemon: Pokemon): number { return 999; } + + override getSpecies(): SpeciesId { + return this.species; + } } /** @@ -2402,19 +2388,13 @@ export class EvolutionItemModifier extends ConsumablePokemonModifier { override apply(playerPokemon: PlayerPokemon): boolean { let matchingEvolution = pokemonEvolutions.hasOwnProperty(playerPokemon.species.speciesId) ? pokemonEvolutions[playerPokemon.species.speciesId].find( - e => - e.item === this.type.evolutionItem && - (e.evoFormKey === null || (e.preFormKey || "") === playerPokemon.getFormKey()) && - (!e.condition || e.condition.predicate(playerPokemon)), + e => e.evoItem === this.type.evolutionItem && e.validate(playerPokemon, false, e.item!), ) : null; if (!matchingEvolution && playerPokemon.isFusion()) { matchingEvolution = pokemonEvolutions[playerPokemon.fusionSpecies!.speciesId].find( - e => - e.item === this.type.evolutionItem && // TODO: is the bang correct? - (e.evoFormKey === null || (e.preFormKey || "") === playerPokemon.getFusionFormKey()) && - (!e.condition || e.condition.predicate(playerPokemon)), + e => e.evoItem === this.type.evolutionItem && e.validate(playerPokemon, true, e.item!), ); if (matchingEvolution) { matchingEvolution = new FusionSpeciesFormEvolution(playerPokemon.species.speciesId, matchingEvolution); @@ -2934,11 +2914,10 @@ export class MoneyRewardModifier extends ConsumableModifier { globalScene.getPlayerParty().map(p => { if (p.species?.speciesId === SpeciesId.GIMMIGHOUL || p.fusionSpecies?.speciesId === SpeciesId.GIMMIGHOUL) { - p.evoCounter - ? (p.evoCounter += Math.min(Math.floor(this.moneyMultiplier), 3)) - : (p.evoCounter = Math.min(Math.floor(this.moneyMultiplier), 3)); + const factor = Math.min(Math.floor(this.moneyMultiplier), 3); const modifier = getModifierType(modifierTypes.EVOLUTION_TRACKER_GIMMIGHOUL).newModifier( p, + factor, ) as EvoTrackerModifier; globalScene.addModifier(modifier); } diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts index 2cd8bf120b5..eaf1e4f58d7 100644 --- a/src/phases/game-over-phase.ts +++ b/src/phases/game-over-phase.ts @@ -4,7 +4,7 @@ import { globalScene } from "#app/global-scene"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { getCharVariantFromDialogue } from "#app/data/dialogue"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { trainerConfigs } from "#app/data/trainers/trainer-config"; import type Pokemon from "#app/field/pokemon"; import { modifierTypes } from "#app/data/data-lists"; diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index e7e87f5a25f..76247c14ce0 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -3,7 +3,7 @@ import { applyChallenges } from "#app/data/challenge"; import { ChallengeType } from "#enums/challenge-type"; import { Gender } from "#app/data/gender"; import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms/form-change-triggers"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { overrideHeldItems, overrideModifiers } from "#app/modifier/modifier"; import Overrides from "#app/overrides"; import { Phase } from "#app/phase"; diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 00b46b9e5f4..e933c5704f9 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -6,7 +6,8 @@ import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; +import { allSpecies } from "#app/data/data-lists"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { randInt, getEnumKeys, isLocal, executeIf, fixedInt, randSeedItem, NumberHolder } from "#app/utils/common"; import Overrides from "#app/overrides"; diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 7571f0cc82f..050da57e0be 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -3,7 +3,8 @@ import { globalScene } from "#app/global-scene"; import type { Gender } from "../data/gender"; import { Nature } from "#enums/nature"; import { PokeballType } from "#enums/pokeball"; -import { getPokemonSpecies, getPokemonSpeciesForm } from "../data/pokemon-species"; +import { getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { Status } from "../data/status-effect"; import Pokemon, { EnemyPokemon, PokemonBattleData, PokemonSummonData } from "../field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; @@ -45,7 +46,6 @@ export default class PokemonData { public pauseEvolutions: boolean; public pokerus: boolean; public usedTMs: MoveId[]; - public evoCounter: number; public teraType: PokemonType; public isTerastallized: boolean; public stellarTypesBoosted: PokemonType[]; @@ -118,7 +118,6 @@ export default class PokemonData { this.pauseEvolutions = !!source.pauseEvolutions; this.pokerus = !!source.pokerus; this.usedTMs = source.usedTMs ?? []; - this.evoCounter = source.evoCounter ?? 0; this.teraType = source.teraType as PokemonType; this.isTerastallized = !!source.isTerastallized; this.stellarTypesBoosted = source.stellarTypesBoosted ?? []; diff --git a/src/system/version_migration/versions/v1_0_4.ts b/src/system/version_migration/versions/v1_0_4.ts index fbbde49bc08..a08327a3b70 100644 --- a/src/system/version_migration/versions/v1_0_4.ts +++ b/src/system/version_migration/versions/v1_0_4.ts @@ -3,7 +3,7 @@ import type { SystemSaveData, SessionSaveData } from "#app/system/game-data"; import { defaultStarterSpecies } from "#app/constants"; import { AbilityAttr } from "#enums/ability-attr"; import { DexAttr } from "#enums/dex-attr"; -import { allSpecies } from "#app/data/pokemon-species"; +import { allSpecies } from "#app/data/data-lists"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { isNullOrUndefined } from "#app/utils/common"; import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; diff --git a/src/system/version_migration/versions/v1_7_0.ts b/src/system/version_migration/versions/v1_7_0.ts index e309959317e..e3c599bf77b 100644 --- a/src/system/version_migration/versions/v1_7_0.ts +++ b/src/system/version_migration/versions/v1_7_0.ts @@ -1,6 +1,7 @@ import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator"; import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; -import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { globalScene } from "#app/global-scene"; import type { SessionSaveData, SystemSaveData } from "#app/system/game-data"; import { DexAttr } from "#enums/dex-attr"; diff --git a/src/system/version_migration/versions/v1_8_3.ts b/src/system/version_migration/versions/v1_8_3.ts index bd963290800..81430659e0b 100644 --- a/src/system/version_migration/versions/v1_8_3.ts +++ b/src/system/version_migration/versions/v1_8_3.ts @@ -1,5 +1,5 @@ import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import type { SystemSaveData } from "#app/system/game-data"; import { DexAttr } from "#enums/dex-attr"; import { SpeciesId } from "#enums/species-id"; diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index 1b1aed91203..0c8d90fa138 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -5,7 +5,7 @@ import { getEnumValues, getEnumKeys, fixedInt, randSeedShuffle } from "#app/util import type { IEggOptions } from "../data/egg"; import { Egg, getLegendaryGachaSpeciesForTimestamp } from "../data/egg"; import { VoucherType, getVoucherTypeIcon } from "../system/voucher"; -import { getPokemonSpecies } from "../data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { addWindow } from "./ui-theme"; import { Tutorial, handleTutorial } from "../tutorial"; import { Button } from "#enums/buttons"; diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts index 7ef4f8f920b..50c15336e36 100644 --- a/src/ui/pokedex-page-ui-handler.ts +++ b/src/ui/pokedex-page-ui-handler.ts @@ -16,7 +16,9 @@ import { pokemonFormChanges } from "#app/data/pokemon-forms"; import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { allSpecies, getPokemonSpecies, getPokemonSpeciesForm, normalForm } from "#app/data/pokemon-species"; +import { getPokemonSpeciesForm, normalForm } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; +import { allSpecies } from "#app/data/data-lists"; import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { starterPassiveAbilities } from "#app/data/balance/passives"; import { PokemonType } from "#enums/pokemon-type"; diff --git a/src/ui/pokedex-scan-ui-handler.ts b/src/ui/pokedex-scan-ui-handler.ts index df3e7cbc8c4..2bffc464793 100644 --- a/src/ui/pokedex-scan-ui-handler.ts +++ b/src/ui/pokedex-scan-ui-handler.ts @@ -8,7 +8,7 @@ import { UiMode } from "#enums/ui-mode"; import { FilterTextRow } from "./filter-text"; import { allAbilities } from "#app/data/data-lists"; import { allMoves } from "#app/data/data-lists"; -import { allSpecies } from "#app/data/pokemon-species"; +import { allSpecies } from "#app/data/data-lists"; import i18next from "i18next"; export default class PokedexScanUiHandler extends FormModalUiHandler { diff --git a/src/ui/pokedex-ui-handler.ts b/src/ui/pokedex-ui-handler.ts index 5b292e7232f..1732bb005d3 100644 --- a/src/ui/pokedex-ui-handler.ts +++ b/src/ui/pokedex-ui-handler.ts @@ -7,7 +7,8 @@ import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; import type { PokemonForm } from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { allSpecies, getPokemonSpeciesForm, getPokerusStarters, normalForm } from "#app/data/pokemon-species"; +import { getPokemonSpeciesForm, getPokerusStarters, normalForm } from "#app/data/pokemon-species"; +import { allSpecies } from "#app/data/data-lists"; import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { catchableSpecies } from "#app/data/balance/biomes"; import { PokemonType } from "#enums/pokemon-type"; diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 7c8b05b6f76..20f613fb694 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -19,7 +19,8 @@ import { pokemonFormChanges } from "#app/data/pokemon-forms"; import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { allSpecies, getPokemonSpeciesForm, getPokerusStarters } from "#app/data/pokemon-species"; +import { getPokemonSpeciesForm, getPokerusStarters } from "#app/data/pokemon-species"; +import { allSpecies } from "#app/data/data-lists"; import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { PokemonType } from "#enums/pokemon-type"; import { GameModes } from "#enums/game-modes"; diff --git a/src/ui/title-ui-handler.ts b/src/ui/title-ui-handler.ts index 29a354dbe01..50e77bbdd14 100644 --- a/src/ui/title-ui-handler.ts +++ b/src/ui/title-ui-handler.ts @@ -9,7 +9,7 @@ import { version } from "../../package.json"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { globalScene } from "#app/global-scene"; import type { SpeciesId } from "#enums/species-id"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { PlayerGender } from "#enums/player-gender"; import { timedEventManager } from "#app/global-event-manager"; diff --git a/src/utils/pokemon-utils.ts b/src/utils/pokemon-utils.ts new file mode 100644 index 00000000000..9f87c36b050 --- /dev/null +++ b/src/utils/pokemon-utils.ts @@ -0,0 +1,21 @@ +import { allSpecies } from "#app/data/data-lists"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import type { SpeciesId } from "#enums/species-id"; + +/** + * Gets the {@linkcode PokemonSpecies} object associated with the {@linkcode SpeciesId} enum given + * @param species - The {@linkcode SpeciesId} to fetch. + * If an array of `SpeciesId`s is passed (such as for named trainer spawn pools), + * one will be selected at random. + * @returns The associated {@linkcode PokemonSpecies} object + */ +export function getPokemonSpecies(species: SpeciesId | SpeciesId[]): PokemonSpecies { + if (Array.isArray(species)) { + // TODO: this RNG roll should not be handled by this function + species = species[Math.floor(Math.random() * species.length)]; + } + if (species >= 2000) { + return allSpecies.find(s => s.speciesId === species)!; // TODO: is this bang correct? + } + return allSpecies[species - 1]; +} diff --git a/test/battle/battle.test.ts b/test/battle/battle.test.ts index a71dca111e3..bf2c3968aa6 100644 --- a/test/battle/battle.test.ts +++ b/test/battle/battle.test.ts @@ -1,4 +1,4 @@ -import { allSpecies } from "#app/data/pokemon-species"; +import { allSpecies } from "#app/data/data-lists"; import { Stat } from "#enums/stat"; import { getGameMode } from "#app/game-mode"; import { GameModes } from "#enums/game-modes"; diff --git a/test/boss-pokemon.test.ts b/test/boss-pokemon.test.ts index afcf5e8fa77..6eb6d262c56 100644 --- a/test/boss-pokemon.test.ts +++ b/test/boss-pokemon.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import GameManager from "#test/testUtils/gameManager"; import { SpeciesId } from "#enums/species-id"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { EFFECTIVE_STATS } from "#app/enums/stat"; diff --git a/test/eggs/egg.test.ts b/test/eggs/egg.test.ts index 7792756a8a3..0d5d09c5179 100644 --- a/test/eggs/egg.test.ts +++ b/test/eggs/egg.test.ts @@ -1,6 +1,6 @@ import { speciesEggTiers } from "#app/data/balance/species-egg-tiers"; import { Egg, getLegendaryGachaSpeciesForTimestamp, getValidLegendaryGachaSpecies } from "#app/data/egg"; -import { allSpecies } from "#app/data/pokemon-species"; +import { allSpecies } from "#app/data/data-lists"; import { EggSourceType } from "#app/enums/egg-source-types"; import { EggTier } from "#app/enums/egg-type"; import { VariantTier } from "#app/enums/variant-tier"; diff --git a/test/moves/effectiveness.test.ts b/test/moves/effectiveness.test.ts index b906e00e1a0..58b2d07b1b6 100644 --- a/test/moves/effectiveness.test.ts +++ b/test/moves/effectiveness.test.ts @@ -1,5 +1,5 @@ import { allMoves } from "#app/data/data-lists"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { TrainerSlot } from "#enums/trainer-slot"; import { PokemonType } from "#enums/pokemon-type"; import { AbilityId } from "#enums/ability-id"; diff --git a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts index 4b0f1d50b74..4f986f58b88 100644 --- a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts +++ b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts @@ -13,7 +13,7 @@ import { AnOfferYouCantRefuseEncounter } from "#app/data/mystery-encounters/enco import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoveId } from "#enums/move-id"; import { ShinyRateBoosterModifier } from "#app/modifier/modifier"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; diff --git a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index 738b3a54067..85193d1ec72 100644 --- a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -4,7 +4,7 @@ import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import * as BattleAnims from "#app/data/battle-anims"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; diff --git a/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts b/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts index 5e6b8507a91..16adc47ff11 100644 --- a/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts +++ b/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts @@ -6,7 +6,7 @@ import GameManager from "#test/testUtils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter"; import { Gender } from "#app/data/gender"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import * as BattleAnims from "#app/data/battle-anims"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { diff --git a/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts b/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts index aa367277ac6..a55806e5f48 100644 --- a/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts +++ b/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts @@ -1,7 +1,7 @@ import { LostAtSeaEncounter } from "#app/data/mystery-encounters/encounters/lost-at-sea-encounter"; import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { BiomeId } from "#enums/biome-id"; import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index d8626a7bf8a..96060e4114c 100644 --- a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -4,7 +4,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import * as BattleAnims from "#app/data/battle-anims"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { diff --git a/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts b/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts index 87caa6ccd40..012b88bcd73 100644 --- a/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts @@ -17,7 +17,7 @@ import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { TrainerType } from "#enums/trainer-type"; import { Nature } from "#enums/nature"; import { MoveId } from "#enums/move-id"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { TheWinstrateChallengeEncounter } from "#app/data/mystery-encounters/encounters/the-winstrate-challenge-encounter"; import { Status } from "#app/data/status-effect"; import { MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases"; diff --git a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index 2bffa26ff4a..9ab5f16d1b9 100644 --- a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -8,7 +8,7 @@ import { type EnemyPokemonConfig, generateModifierType, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { BiomeId } from "#enums/biome-id"; import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts index 65c266a5a6c..ec64a17d291 100644 --- a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts +++ b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts @@ -21,7 +21,7 @@ import { CommandPhase } from "#app/phases/command-phase"; import { UncommonBreedEncounter } from "#app/data/mystery-encounters/encounters/uncommon-breed-encounter"; import { MovePhase } from "#app/phases/move-phase"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { BerryType } from "#enums/berry-type"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { Stat } from "#enums/stat"; diff --git a/test/mystery-encounter/mystery-encounter-utils.test.ts b/test/mystery-encounter/mystery-encounter-utils.test.ts index f327d8f9e9c..b775ce8df60 100644 --- a/test/mystery-encounter/mystery-encounter-utils.test.ts +++ b/test/mystery-encounter/mystery-encounter-utils.test.ts @@ -14,7 +14,7 @@ import { getRandomSpeciesByStarterCost, koPlayerPokemon, } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { PokemonType } from "#enums/pokemon-type"; import { MessagePhase } from "#app/phases/message-phase"; import GameManager from "#test/testUtils/gameManager"; diff --git a/test/phases/select-modifier-phase.test.ts b/test/phases/select-modifier-phase.test.ts index 6e92861260e..b6c3089e236 100644 --- a/test/phases/select-modifier-phase.test.ts +++ b/test/phases/select-modifier-phase.test.ts @@ -1,5 +1,5 @@ import type BattleScene from "#app/battle-scene"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { PlayerPokemon } from "#app/field/pokemon"; import { ModifierTier } from "#enums/modifier-tier"; import type { CustomModifierSettings } from "#app/modifier/modifier-type"; diff --git a/test/testUtils/gameManagerUtils.ts b/test/testUtils/gameManagerUtils.ts index eda3921e9e0..57fd9b91d26 100644 --- a/test/testUtils/gameManagerUtils.ts +++ b/test/testUtils/gameManagerUtils.ts @@ -3,7 +3,8 @@ import { BattleType } from "#enums/battle-type"; import type BattleScene from "#app/battle-scene"; import { getDailyRunStarters } from "#app/data/daily-run"; import { Gender } from "#app/data/gender"; -import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { PlayerPokemon } from "#app/field/pokemon"; import { getGameMode } from "#app/game-mode"; import { GameModes } from "#enums/game-modes"; diff --git a/test/ui/pokedex.test.ts b/test/ui/pokedex.test.ts index d3fc4b11968..13f595e0c60 100644 --- a/test/ui/pokedex.test.ts +++ b/test/ui/pokedex.test.ts @@ -6,7 +6,9 @@ import { FilterTextRow } from "#app/ui/filter-text"; import { allAbilities } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { SpeciesId } from "#enums/species-id"; -import { allSpecies, getPokemonSpecies, type PokemonForm } from "#app/data/pokemon-species"; +import type { PokemonForm } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/utils/pokemon-utils"; +import { allSpecies } from "#app/data/data-lists"; import { Button } from "#enums/buttons"; import { DropDownColumn } from "#enums/drop-down-column"; import type PokemonSpecies from "#app/data/pokemon-species"; diff --git a/test/ui/starter-select.test.ts b/test/ui/starter-select.test.ts index 3e540c4e2c5..8167ab17957 100644 --- a/test/ui/starter-select.test.ts +++ b/test/ui/starter-select.test.ts @@ -1,6 +1,6 @@ import { Gender } from "#app/data/gender"; import { Nature } from "#enums/nature"; -import { allSpecies } from "#app/data/pokemon-species"; +import { allSpecies } from "#app/data/data-lists"; import { GameModes } from "#enums/game-modes"; import { EncounterPhase } from "#app/phases/encounter-phase"; import { SelectStarterPhase } from "#app/phases/select-starter-phase"; From 6ff258fb378590d0ac65576232520120105c368c Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:02:09 -0500 Subject: [PATCH 51/51] [Bug] Fix weather form changes not changing with weather abilities https://github.com/pagefaultgames/pokerogue/pull/5857 * Add test for forecast * Fix PostSummonFormChangeByWeatherAbAttr * Fix overrides in forecast test * Remove a test whose trigger conditions can no longer happen * Update src/data/abilities/ability.ts Co-authored-by: Dean <69436131+emdeann@users.noreply.github.com> * Fix missing tsdoc param * Apply kev's suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Simplify PostSummonFormChangeByWeather's canApplyPostSummon * Fix unused params that messed up after rebase * Fix form change import Messed up due to improper rebase --------- Co-authored-by: Dean <69436131+emdeann@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/abilities/ability.ts | 44 +++++++++++++++++++-------------- test/abilities/forecast.test.ts | 40 ++++++++++++++---------------- 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 3124f782ff5..70195d6a152 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -24,11 +24,11 @@ import { allMoves } from "../data-lists"; import { ArenaTagSide } from "#enums/arena-tag-side"; import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { TerrainType } from "#app/data/terrain"; +import { pokemonFormChanges } from "../pokemon-forms"; import { - SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger, + SpeciesFormChangeAbilityTrigger, } from "../pokemon-forms/form-change-triggers"; -import { SpeciesFormChangeAbilityTrigger } from "../pokemon-forms/form-change-triggers"; import i18next from "i18next"; import { Command } from "#enums/command"; import { BerryModifierType } from "#app/modifier/modifier-type"; @@ -3971,27 +3971,32 @@ export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr { this.ability = ability; } + /** + * Determine if the pokemon has a forme change that is triggered by the weather + * + * @param pokemon - The pokemon with the forme change ability + * @param _passive - unused + * @param _simulated - unused + * @param _args - unused + */ override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { - const isCastformWithForecast = - pokemon.species.speciesId === SpeciesId.CASTFORM && this.ability === AbilityId.FORECAST; - const isCherrimWithFlowerGift = - pokemon.species.speciesId === SpeciesId.CHERRIM && this.ability === AbilityId.FLOWER_GIFT; - return isCastformWithForecast || isCherrimWithFlowerGift; + return !!pokemonFormChanges[pokemon.species.speciesId]?.some( + fc => fc.findTrigger(SpeciesFormChangeWeatherTrigger) && fc.canChange(pokemon), + ); } /** - * Calls the {@linkcode BattleScene.triggerPokemonFormChange | triggerPokemonFormChange} for both - * {@linkcode SpeciesFormChange.SpeciesFormChangeWeatherTrigger | SpeciesFormChangeWeatherTrigger} and - * {@linkcode SpeciesFormChange.SpeciesFormChangeWeatherTrigger | SpeciesFormChangeRevertWeatherFormTrigger} if it - * is the specific Pokemon and ability - * @param {Pokemon} pokemon the Pokemon with this ability - * @param _passive n/a - * @param _args n/a + * Trigger the pokemon's forme change by invoking + * {@linkcode BattleScene.triggerPokemonFormChange | triggerPokemonFormChange} + * + * @param pokemon - The Pokemon with this ability + * @param _passive - unused + * @param simulated - unused + * @param _args - unused */ override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { if (!simulated) { globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeWeatherTrigger); - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeRevertWeatherFormTrigger); } } } @@ -5301,10 +5306,11 @@ export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr { /** * Calls {@linkcode Arena.triggerWeatherBasedFormChangesToNormal | triggerWeatherBasedFormChangesToNormal} when the * weather changed to form-reverting weather, otherwise calls {@linkcode Arena.triggerWeatherBasedFormChanges | triggerWeatherBasedFormChanges} - * @param {Pokemon} _pokemon the Pokemon with this ability - * @param _passive n/a - * @param _weather n/a - * @param _args n/a + * @param _pokemon - The Pokemon with this ability + * @param _passive - unused + * @param simulated - unused + * @param _weather - unused + * @param _args - unused */ override applyPostWeatherChange( _pokemon: Pokemon, diff --git a/test/abilities/forecast.test.ts b/test/abilities/forecast.test.ts index 8d3a679c917..9111766ebdf 100644 --- a/test/abilities/forecast.test.ts +++ b/test/abilities/forecast.test.ts @@ -21,26 +21,10 @@ describe("Abilities - Forecast", () => { const RAINY_FORM = 2; const SNOWY_FORM = 3; - /** - * Tests form changes based on weather changes - * @param {GameManager} game The game manager instance - * @param {WeatherType} weather The active weather to set - * @param form The expected form based on the active weather - * @param initialForm The initial form pre form change - */ - const testWeatherFormChange = async (game: GameManager, weather: WeatherType, form: number, initialForm?: number) => { - game.override.weather(weather).starterForms({ [SpeciesId.CASTFORM]: initialForm }); - await game.classicMode.startBattle([SpeciesId.CASTFORM]); - - game.move.select(MoveId.SPLASH); - - expect(game.scene.getPlayerPokemon()?.formIndex).toBe(form); - }; - /** * Tests reverting to normal form when Cloud Nine/Air Lock is active on the field - * @param {GameManager} game The game manager instance - * @param {AbilityId} ability The ability that is active on the field + * @param game - The game manager instance + * @param ability - The ability that is active on the field */ const testRevertFormAgainstAbility = async (game: GameManager, ability: AbilityId) => { game.override.starterForms({ [SpeciesId.CASTFORM]: SUNNY_FORM }).enemyAbility(ability); @@ -191,10 +175,6 @@ describe("Abilities - Forecast", () => { 30 * 1000, ); - it("reverts to Normal Form during Clear weather", async () => { - await testWeatherFormChange(game, WeatherType.NONE, NORMAL_FORM, SUNNY_FORM); - }); - it("reverts to Normal Form if a Pokémon on the field has Air Lock", async () => { await testRevertFormAgainstAbility(game, AbilityId.AIR_LOCK); }); @@ -277,4 +257,20 @@ describe("Abilities - Forecast", () => { expect(castform.formIndex).toBe(NORMAL_FORM); }); + + // NOTE: The following pairs of tests are intentionally testing the same scenario, switching the player and enemy pokemon + // as this is a regression test where the order of player and enemy mattered. + it("should trigger player's form change when summoned at the same time as an enemy with a weather changing ability", async () => { + game.override.enemyAbility(AbilityId.DROUGHT); + await game.classicMode.startBattle([SpeciesId.CASTFORM, SpeciesId.MAGIKARP]); + const castform = game.scene.getPlayerPokemon()!; + expect(castform.formIndex).toBe(SUNNY_FORM); + }); + + it("should trigger enemy's form change when summoned at the same time as a player with a weather changing ability", async () => { + game.override.ability(AbilityId.DROUGHT).enemySpecies(SpeciesId.CASTFORM).enemyAbility(AbilityId.FORECAST); + await game.classicMode.startBattle([SpeciesId.MAGIKARP]); + const castform = game.scene.getEnemyPokemon()!; + expect(castform.formIndex).toBe(SUNNY_FORM); + }); });