From 8410aee3a1315dac368bb3d6b59ae0caf27d5e84 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Thu, 22 May 2025 15:25:48 -0400 Subject: [PATCH 01/14] [Sprite] Fix Cacturne and Kyurem variants --- public/images/pokemon/332.png | Bin 6892 -> 6886 bytes public/images/pokemon/646.png | Bin 17027 -> 50251 bytes public/images/pokemon/back/332.png | Bin 19460 -> 19467 bytes public/images/pokemon/back/female/332.png | Bin 19460 -> 19467 bytes public/images/pokemon/female/332.png | Bin 6911 -> 6904 bytes public/images/pokemon/variant/332.json | 38 +++++++++--------- public/images/pokemon/variant/back/332.json | 36 ++++++++--------- .../pokemon/variant/back/female/332.json | 36 ++++++++--------- public/images/pokemon/variant/female/332.json | 38 +++++++++--------- 9 files changed, 76 insertions(+), 72 deletions(-) diff --git a/public/images/pokemon/332.png b/public/images/pokemon/332.png index a9979480673646722fc78640e946c91e4e45b676..44e374426b4dd37171447e1c4d155753e44a3cee 100644 GIT binary patch delta 6534 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 diff --git a/public/images/pokemon/646.png b/public/images/pokemon/646.png index 64b0af2d1518727150cbcd49eaa5747ed8f11b80..e54083bfc7318d9a27185f7b948294ba32950d8b 100644 GIT binary patch literal 50251 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 literal 17027 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 P-RnCl*`L%vjAZ>At>jlK literal 19460 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 P-RnCl*`L%vjAZ>At>jlK literal 19460 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 diff --git a/public/images/pokemon/variant/332.json b/public/images/pokemon/variant/332.json index e9b487bca25..336ef4a22f3 100644 --- a/public/images/pokemon/variant/332.json +++ b/public/images/pokemon/variant/332.json @@ -1,34 +1,36 @@ { "1": { - "319452": "831a1f", - "4a7310": "982443", - "7ba563": "b44040", - "bdef84": "ec8c8c", "8cbd63": "c54b4b", - "215200": "710f2e", + "a5d670": "df5252", + "4aa552": "9f2f2c", "a5d674": "e16363", - "196b21": "891222", + "7aa953": "c54b4b", + "7ba563": "b44040", + "215200": "710f2e", "f7ce00": "7aa1df", "525252": "123a5a", - "63b56b": "b2332f", - "a5d673": "df5252", "8c6b3a": "448bc3", - "4aa552": "9f2f2c" + "bdef84": "ec8c8c", + "63b56b": "b2332f", + "319452": "831a1f", + "196b21": "891222", + "4a7310": "982443" }, "2": { - "319452": "b08d72", - "4a7310": "4f3956", - "7ba563": "704e7e", - "bdef84": "a779ba", "8cbd63": "e3d7a6", - "215200": "583823", + "a5d670": "d7cda7", + "4aa552": "c5a77f", "a5d674": "8c669b", - "196b21": "78582c", + "7aa953": "704e7e", + "7ba563": "704e7e", + "215200": "583823", "f7ce00": "f2aacd", "525252": "a53b6f", - "63b56b": "cfc191", - "a5d673": "d7cda7", "8c6b3a": "df87bb", - "4aa552": "c5a77f" + "bdef84": "a779ba", + "63b56b": "cfc191", + "319452": "b08d72", + "196b21": "78582c", + "4a7310": "4f3956" } } \ No newline at end of file diff --git a/public/images/pokemon/variant/back/332.json b/public/images/pokemon/variant/back/332.json index c13c07c34b4..fbfb3705202 100644 --- a/public/images/pokemon/variant/back/332.json +++ b/public/images/pokemon/variant/back/332.json @@ -1,28 +1,28 @@ { "1": { + "196b21": "831a1f", + "7ba563": "b44040", + "215201": "630d28", + "215200": "710f2f", + "a5d674": "df5252", + "8cbd63": "c54b4b", + "63b56b": "b2332f", + "a5d670": "e16363", "319452": "831a1f", "4aa552": "9f2f2c", - "7ba563": "b44040", - "8cbd63": "c54b4b", - "215200": "710f2f", - "196b21": "831a1f", - "a5d674": "df5252", - "4a7310": "982443", - "a5d673": "e16363", - "63b56b": "b2332f", - "215201": "630d28" + "4a7310": "982443" }, "2": { + "196b21": "b08d72", + "7ba563": "704e7e", + "215201": "583823", + "215200": "3f3249", + "a5d674": "d7cda7", + "8cbd63": "e3d7a6", + "63b56b": "cfc191", + "a5d670": "8c669b", "319452": "b08d72", "4aa552": "c5a77f", - "7ba563": "704e7e", - "8cbd63": "e3d7a6", - "215200": "3f3249", - "196b21": "b08d72", - "a5d674": "d7cda7", - "4a7310": "4f3956", - "a5d673": "8c669b", - "63b56b": "cfc191", - "215201": "583823" + "4a7310": "4f3956" } } \ No newline at end of file diff --git a/public/images/pokemon/variant/back/female/332.json b/public/images/pokemon/variant/back/female/332.json index 9ec50cb7e92..17f8d3c5f74 100644 --- a/public/images/pokemon/variant/back/female/332.json +++ b/public/images/pokemon/variant/back/female/332.json @@ -1,28 +1,28 @@ { "1": { + "196b21": "780d4a", + "7ba563": "b44040", + "215201": "710f2e", + "215200": "710f2f", + "a5d674": "de5b6f", + "8cbd63": "bf3d64", + "63b56b": "9e2056", + "a5d670": "e16363", "319452": "780d4a", "4aa552": "8a1652", - "7ba563": "b44040", - "8cbd63": "bf3d64", - "215200": "710f2f", - "196b21": "780d4a", - "a5d674": "de5b6f", - "4a7310": "982443", - "a5d673": "e16363", - "63b56b": "9e2056", - "215201": "710f2e" + "4a7310": "982443" }, "2": { + "196b21": "b59c72", + "7ba563": "805a9c", + "215201": "694d37", + "215200": "41334d", + "a5d674": "f6f7df", + "8cbd63": "ebe9ca", + "63b56b": "e3ddb8", + "a5d670": "a473ba", "319452": "b59c72", "4aa552": "c9b991", - "7ba563": "805a9c", - "8cbd63": "ebe9ca", - "215200": "41334d", - "196b21": "b59c72", - "a5d674": "f6f7df", - "4a7310": "4f3956", - "a5d673": "a473ba", - "63b56b": "e3ddb8", - "215201": "694d37" + "4a7310": "4f3956" } } \ No newline at end of file diff --git a/public/images/pokemon/variant/female/332.json b/public/images/pokemon/variant/female/332.json index c86429d13c4..7a1dc0f1457 100644 --- a/public/images/pokemon/variant/female/332.json +++ b/public/images/pokemon/variant/female/332.json @@ -1,34 +1,36 @@ { "1": { - "319452": "780d4a", - "4a7310": "982443", - "7ba563": "b44040", - "bdef84": "ec8c8c", "8cbd63": "bf3d64", - "215200": "710f2e", + "a5d670": "de5b6f", + "4aa552": "8a1652", "a5d674": "e16363", - "196b21": "7d1157", + "7aa953": "bf3d64", + "7ba563": "b44040", + "215200": "710f2e", "f7ce00": "5bcfc3", "525252": "20668c", - "63b56b": "9e2056", - "a5d673": "de5b6f", "8c6b3a": "33a3b0", - "4aa552": "8a1652" + "bdef84": "ec8c8c", + "63b56b": "9e2056", + "319452": "780d4a", + "196b21": "7d1157", + "4a7310": "982443" }, "2": { - "319452": "b59c72", - "4a7310": "4f3956", - "7ba563": "805a9c", - "bdef84": "c193cf", "8cbd63": "f6f7df", - "215200": "694d37", + "a5d670": "ebe9ca", + "4aa552": "c9b991", "a5d674": "a473ba", - "196b21": "9c805f", + "7aa953": "805a9c", + "7ba563": "805a9c", + "215200": "694d37", "f7ce00": "f2aab6", "525252": "983364", - "63b56b": "e3ddb8", - "a5d673": "ebe9ca", "8c6b3a": "df879f", - "4aa552": "c9b991" + "bdef84": "c193cf", + "63b56b": "e3ddb8", + "319452": "b59c72", + "196b21": "9c805f", + "4a7310": "4f3956" } } \ No newline at end of file From e053ead67cbe4c19aeadb9d61ae5bb791c7a0411 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Sun, 25 May 2025 13:37:52 -0500 Subject: [PATCH 02/14] [Bug] Fix crash caused by switching in a transformed pokemon (#5864) * Force reset summon data and load assets prior to switch in * Update src/phases/switch-summon-phase.ts --- src/phases/switch-summon-phase.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index a063b6e6863..3e9cc7718f1 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -124,6 +124,12 @@ export class SwitchSummonPhase extends SummonPhase { const switchedInPokemon: Pokemon | undefined = party[this.slotIndex]; this.lastPokemon = this.getPokemon(); + // Defensive programming: Overcome the bug where the summon data has somehow not been reset + // prior to switching in a new Pokemon. + // Force the switch to occur and load the assets for the new pokemon, ignoring override. + switchedInPokemon.resetSummonData(); + switchedInPokemon.loadAssets(true); + applyPreSummonAbAttrs(PreSummonAbAttr, switchedInPokemon); applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon); if (!switchedInPokemon) { @@ -131,6 +137,7 @@ 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) => From b803f6f18a9b7a921dff2910bfd440814032bf5e Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 27 May 2025 02:38:30 -0700 Subject: [PATCH 03/14] [i18n] Update locales (#5875) --- public/locales | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales b/public/locales index 42cd5cf577f..e9ccbadb6ea 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 42cd5cf577f475c22bc82d55e7ca358eb4f3184f +Subproject commit e9ccbadb6eaa3b797f3dec919745befda2ec74bd From 999cbf911e900723a433a8d949f8cfb2084421d0 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 27 May 2025 03:56:52 -0700 Subject: [PATCH 04/14] [Bug] Fix Pichu form weights (61.5 -> 2) --- src/data/pokemon-species.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 59167ba47f6..6f05d17db6f 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -1697,8 +1697,8 @@ export function initSpecies() { new PokemonSpecies(Species.CHINCHOU, 2, false, false, false, "Angler Pokémon", PokemonType.WATER, PokemonType.ELECTRIC, 0.5, 12, Abilities.VOLT_ABSORB, Abilities.ILLUMINATE, Abilities.WATER_ABSORB, 330, 75, 38, 38, 56, 56, 67, 190, 50, 66, GrowthRate.SLOW, 50, false), new PokemonSpecies(Species.LANTURN, 2, false, false, false, "Light Pokémon", PokemonType.WATER, PokemonType.ELECTRIC, 1.2, 22.5, Abilities.VOLT_ABSORB, Abilities.ILLUMINATE, Abilities.WATER_ABSORB, 460, 125, 58, 58, 76, 76, 67, 75, 50, 161, GrowthRate.SLOW, 50, false), new PokemonSpecies(Species.PICHU, 2, false, false, false, "Tiny Mouse Pokémon", PokemonType.ELECTRIC, null, 0.3, 2, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Normal", "", PokemonType.ELECTRIC, null, 1.4, 61.5, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true), - new PokemonForm("Spiky-Eared", "spiky", PokemonType.ELECTRIC, null, 1.4, 61.5, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true), + new PokemonForm("Normal", "", PokemonType.ELECTRIC, null, 1.4, 2, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true), + new PokemonForm("Spiky-Eared", "spiky", PokemonType.ELECTRIC, null, 1.4, 2, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true), ), new PokemonSpecies(Species.CLEFFA, 2, false, false, false, "Star Shape Pokémon", PokemonType.FAIRY, null, 0.3, 3, Abilities.CUTE_CHARM, Abilities.MAGIC_GUARD, Abilities.FRIEND_GUARD, 218, 50, 25, 28, 45, 55, 15, 150, 140, 44, GrowthRate.FAST, 25, false), new PokemonSpecies(Species.IGGLYBUFF, 2, false, false, false, "Balloon Pokémon", PokemonType.NORMAL, PokemonType.FAIRY, 0.3, 1, Abilities.CUTE_CHARM, Abilities.COMPETITIVE, Abilities.FRIEND_GUARD, 210, 90, 30, 15, 40, 20, 15, 170, 50, 42, GrowthRate.FAST, 25, false), From 65a90a3a8d4204197e975f6b5da1163d5ef0c136 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 27 May 2025 13:12:29 +0200 Subject: [PATCH 05/14] [UI/UX] Party UI handler refactor (part I) (#5821) * Splitting process input for menu into its own function * Making logic for return button more expressive * Breaking up processOptionMenuInput * Extracting filterResult logic from processInput * Inverting order of several conditional checks (if the function always returns after seeing the summary option, may as well check for it straight away...) * Moving edge case for release option into processReleaseOption * Splitting up options for when selectCallback is present * Added some TODOs for later * Extracted setOptionsCursor function * Extracted updateOptionsWindow() * Changing options so that each case is completely separate (almost) * Added some TODOs * Reorganizing option processing * Fixed Baton Pass; logging for testing * Fixed case of switching out by selecting the Pokemon command * Clearing options when switching out * Changed condition on switch for clarity * Updating TODO * Fixed options not clearing after item transfer * Splitting up processing of transfer and move recall mode; ensuring that the cancel option works properly * Breaking up processInput() * Removed some redundant playSelect * Cleaned up some TODOs * Added private to all new methods * Apply 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/ui/party-ui-handler.ts | 1470 +++++++++++++++++++++--------------- 1 file changed, 845 insertions(+), 625 deletions(-) diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index f6105c51ee3..74d4e5bcb17 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -52,6 +52,7 @@ export enum PartyUiMode { * Indicates that the party UI is open because of a start-of-encounter optional * switch. This type of switch can be cancelled. */ + // TODO: Rename to PRE_BATTLE_SWITCH POST_BATTLE_SWITCH, /** * Indicates that the party UI is open because of the move Revival Blessing. @@ -356,6 +357,522 @@ export default class PartyUiHandler extends MessageUiHandler { return true; } + private processSummaryOption(pokemon: Pokemon): boolean { + const ui = this.getUi(); + ui.playSelect(); + ui.setModeWithoutClear(UiMode.SUMMARY, pokemon).then(() => this.clearOptions()); + return true; + } + + private processPokedexOption(pokemon: Pokemon): boolean { + const ui = this.getUi(); + ui.playSelect(); + const attributes = { + shiny: pokemon.shiny, + variant: pokemon.variant, + form: pokemon.formIndex, + female: pokemon.gender === Gender.FEMALE, + }; + ui.setOverlayMode(UiMode.POKEDEX_PAGE, pokemon.species, attributes).then(() => this.clearOptions()); + return true; + } + + private processUnpauseEvolutionOption(pokemon: Pokemon): boolean { + const ui = this.getUi(); + this.clearOptions(); + ui.playSelect(); + pokemon.pauseEvolutions = !pokemon.pauseEvolutions; + this.showText( + i18next.t(pokemon.pauseEvolutions ? "partyUiHandler:pausedEvolutions" : "partyUiHandler:unpausedEvolutions", { + pokemonName: getPokemonNameWithAffix(pokemon, false), + }), + undefined, + () => this.showText("", 0), + null, + true, + ); + return true; + } + + private processUnspliceOption(pokemon: PlayerPokemon): boolean { + const ui = this.getUi(); + this.clearOptions(); + ui.playSelect(); + this.showText( + i18next.t("partyUiHandler:unspliceConfirmation", { + fusionName: pokemon.fusionSpecies?.name, + pokemonName: pokemon.getName(), + }), + null, + () => { + ui.setModeWithoutClear( + UiMode.CONFIRM, + () => { + const fusionName = pokemon.getName(); + pokemon.unfuse().then(() => { + this.clearPartySlots(); + this.populatePartySlots(); + ui.setMode(UiMode.PARTY); + this.showText( + i18next.t("partyUiHandler:wasReverted", { + fusionName: fusionName, + pokemonName: pokemon.getName(false), + }), + undefined, + () => { + ui.setMode(UiMode.PARTY); + this.showText("", 0); + }, + null, + true, + ); + }); + }, + () => { + ui.setMode(UiMode.PARTY); + this.showText("", 0); + }, + ); + }, + ); + return true; + } + + private processReleaseOption(pokemon: Pokemon): boolean { + const ui = this.getUi(); + this.clearOptions(); + ui.playSelect(); + // In release mode, we do not ask for confirmation when clicking release. + if (this.partyUiMode === PartyUiMode.RELEASE) { + this.doRelease(this.cursor); + return true; + } + if (this.cursor >= globalScene.currentBattle.getBattlerCount() || !pokemon.isAllowedInBattle()) { + this.blockInput = true; + this.showText( + i18next.t("partyUiHandler:releaseConfirmation", { + pokemonName: getPokemonNameWithAffix(pokemon, false), + }), + null, + () => { + this.blockInput = false; + ui.setModeWithoutClear( + UiMode.CONFIRM, + () => { + ui.setMode(UiMode.PARTY); + this.doRelease(this.cursor); + }, + () => { + ui.setMode(UiMode.PARTY); + this.showText("", 0); + }, + ); + }, + ); + } else { + this.showText(i18next.t("partyUiHandler:releaseInBattle"), null, () => this.showText("", 0), null, true); + } + return true; + } + + private processRenameOption(pokemon: Pokemon): boolean { + const ui = this.getUi(); + this.clearOptions(); + ui.playSelect(); + ui.setModeWithoutClear( + UiMode.RENAME_POKEMON, + { + buttonActions: [ + (nickname: string) => { + ui.playSelect(); + pokemon.nickname = nickname; + pokemon.updateInfo(); + this.clearPartySlots(); + this.populatePartySlots(); + ui.setMode(UiMode.PARTY); + }, + () => { + ui.setMode(UiMode.PARTY); + }, + ], + }, + pokemon, + ); + return true; + } + + // TODO: Does this need to check that selectCallback exists? + private processTransferOption(): boolean { + const ui = this.getUi(); + if (this.transferCursor !== this.cursor) { + if (this.transferAll) { + this.getTransferrableItemsFromPokemon(globalScene.getPlayerParty()[this.transferCursor]).forEach( + (_, i, array) => { + const invertedIndex = array.length - 1 - i; + (this.selectCallback as PartyModifierTransferSelectCallback)( + this.transferCursor, + invertedIndex, + this.transferQuantitiesMax[invertedIndex], + this.cursor, + ); + }, + ); + } else { + (this.selectCallback as PartyModifierTransferSelectCallback)( + this.transferCursor, + this.transferOptionCursor, + this.transferQuantities[this.transferOptionCursor], + this.cursor, + ); + } + } + this.clearTransfer(); + this.clearOptions(); + ui.playSelect(); + return true; + } + + // TODO: This will be largely changed with the modifier rework + private processModifierTransferModeInput(pokemon: PlayerPokemon) { + const ui = this.getUi(); + const option = this.options[this.optionsCursor]; + + if (option === PartyOption.TRANSFER) { + return this.processTransferOption(); + } + + // TODO: Revise this condition + if (!this.transferMode) { + this.startTransfer(); + + let ableToTransferText: string; + for (let p = 0; p < globalScene.getPlayerParty().length; p++) { + // this for look goes through each of the party pokemon + const newPokemon = globalScene.getPlayerParty()[p]; + // this next bit checks to see if the the selected item from the original transfer pokemon exists on the new pokemon `p` + // 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.pokemonId === newPokemon.id && + m.matchType(this.getTransferrableItemsFromPokemon(pokemon)[this.transferOptionCursor]), + ) as PokemonHeldItemModifier; + const partySlot = this.partySlots.filter(m => m.getPokemon() === newPokemon)[0]; // this gets pokemon [p] for us + if (p !== this.transferCursor) { + // this skips adding the able/not able labels on the pokemon doing the transfer + if (matchingModifier) { + // if matchingModifier exists then the item exists on the new pokemon + if (matchingModifier.getMaxStackCount() === matchingModifier.stackCount) { + // checks to see if the stack of items is at max stack; if so, set the description label to "Not able" + ableToTransferText = i18next.t("partyUiHandler:notAble"); + } else { + // if the pokemon isn't at max stack, make the label "Able" + ableToTransferText = i18next.t("partyUiHandler:able"); + } + } else { + // if matchingModifier doesn't exist, that means the pokemon doesn't have any of the item, and we need to show "Able" + ableToTransferText = i18next.t("partyUiHandler:able"); + } + } else { + // this else relates to the transfer pokemon. We set the text to be blank so there's no "Able"/"Not able" text + ableToTransferText = ""; + } + partySlot.slotHpBar.setVisible(false); + partySlot.slotHpOverlay.setVisible(false); + partySlot.slotHpText.setVisible(false); + partySlot.slotDescriptionLabel.setText(ableToTransferText); + partySlot.slotDescriptionLabel.setVisible(true); + } + this.clearOptions(); + ui.playSelect(); + return true; + } + return false; + } + + // TODO: Might need to check here for when this.transferMode is active. + private processModifierTransferModeLeftRightInput(button: Button) { + let success = false; + const option = this.options[this.optionsCursor]; + if (button === Button.LEFT) { + /** Decrease quantity for the current item and update UI */ + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + this.transferQuantities[option] = + this.transferQuantities[option] === 1 + ? this.transferQuantitiesMax[option] + : this.transferQuantities[option] - 1; + this.updateOptions(); + success = this.setCursor( + this.optionsCursor, + ); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ + } + } + + if (button === Button.RIGHT) { + /** Increase quantity for the current item and update UI */ + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + this.transferQuantities[option] = + this.transferQuantities[option] === this.transferQuantitiesMax[option] + ? 1 + : this.transferQuantities[option] + 1; + this.updateOptions(); + success = this.setCursor( + this.optionsCursor, + ); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ + } + } + return success; + } + + // TODO: Might need to check here for when this.transferMode is active. + private processModifierTransferModeUpDownInput(button: Button.UP | Button.DOWN) { + let success = false; + const option = this.options[this.optionsCursor]; + + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + if (option !== PartyOption.ALL) { + this.transferQuantities[option] = this.transferQuantitiesMax[option]; + } + this.updateOptions(); + } + success = this.moveOptionCursor(button); + + return success; + } + + private moveOptionCursor(button: Button.UP | Button.DOWN): boolean { + if (button === Button.UP) { + return this.setCursor(this.optionsCursor ? this.optionsCursor - 1 : this.options.length - 1); + } + return this.setCursor(this.optionsCursor < this.options.length - 1 ? this.optionsCursor + 1 : 0); + } + + private processRememberMoveModeInput(pokemon: PlayerPokemon) { + const ui = this.getUi(); + const option = this.options[this.optionsCursor]; + + // clear overlay on cancel + this.moveInfoOverlay.clear(); + const filterResult = (this.selectFilter as PokemonSelectFilter)(pokemon); + if (filterResult === null) { + this.selectCallback?.(this.cursor, option); + this.clearOptions(); + } else { + this.clearOptions(); + this.showText(filterResult as string, undefined, () => this.showText("", 0), undefined, true); + } + ui.playSelect(); + return true; + } + + private processRememberMoveModeUpDownInput(button: Button.UP | Button.DOWN) { + let success = false; + + success = this.moveOptionCursor(button); + + // show move description + const option = this.options[this.optionsCursor]; + const pokemon = globalScene.getPlayerParty()[this.cursor]; + const move = allMoves[pokemon.getLearnableLevelMoves()[option]]; + if (move) { + this.moveInfoOverlay.show(move); + } else { + // or hide the overlay, in case it's the cancel button + this.moveInfoOverlay.clear(); + } + + return success; + } + + private getTransferrableItemsFromPokemon(pokemon: PlayerPokemon) { + return globalScene.findModifiers( + m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === pokemon.id, + ) as PokemonHeldItemModifier[]; + } + + private getFilterResult(option: number, pokemon: PlayerPokemon): string | null { + let filterResult: string | null; + if (option !== PartyOption.TRANSFER && option !== PartyOption.SPLICE) { + filterResult = (this.selectFilter as PokemonSelectFilter)(pokemon); + if (filterResult === null && (option === PartyOption.SEND_OUT || option === PartyOption.PASS_BATON)) { + filterResult = this.FilterChallengeLegal(pokemon); + } + if (filterResult === null && this.partyUiMode === PartyUiMode.MOVE_MODIFIER) { + filterResult = this.moveSelectFilter(pokemon.moveset[this.optionsCursor]); + } + } else { + filterResult = (this.selectFilter as PokemonModifierTransferSelectFilter)( + pokemon, + this.getTransferrableItemsFromPokemon(globalScene.getPlayerParty()[this.transferCursor])[ + this.transferOptionCursor + ], + ); + } + return filterResult; + } + + private processActionButtonForOptions(option: PartyOption) { + const ui = this.getUi(); + if (option === PartyOption.CANCEL) { + return this.processOptionMenuInput(Button.CANCEL); + } + + // If the input has been already processed we are done, otherwise move on until the correct option is found + const pokemon = globalScene.getPlayerParty()[this.cursor]; + + // TODO: Careful about using success for the return values here. Find a better way + // PartyOption.ALL, and options specific to the mode (held items) + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + return this.processModifierTransferModeInput(pokemon); + } + + // options specific to the mode (moves) + if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { + return this.processRememberMoveModeInput(pokemon); + } + + // These are the options that do not involve a callback + if (option === PartyOption.SUMMARY) { + return this.processSummaryOption(pokemon); + } + if (option === PartyOption.POKEDEX) { + return this.processPokedexOption(pokemon); + } + if (option === PartyOption.UNPAUSE_EVOLUTION) { + return this.processUnpauseEvolutionOption(pokemon); + } + if (option === PartyOption.UNSPLICE) { + return this.processUnspliceOption(pokemon); + } + if (option === PartyOption.RENAME) { + return this.processRenameOption(pokemon); + } + // This is only relevant for PartyUiMode.CHECK + // 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 && + this.partyUiMode === PartyUiMode.CHECK + ) { + const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); + const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM]; + modifier.active = !modifier.active; + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger, false, true); + } + + // If the pokemon is filtered out for this option, we cannot continue + const filterResult = this.getFilterResult(option, pokemon); + if (filterResult) { + this.clearOptions(); + this.showText(filterResult as string, undefined, () => this.showText("", 0), undefined, true); + return true; + } + + // For what modes is a selectCallback needed? + // PartyUiMode.SELECT (SELECT) + // PartyUiMode.RELEASE (RELEASE) + // PartyUiMode.FAINT_SWITCH (SEND_OUT or PASS_BATON (?)) + // PartyUiMode.REVIVAL_BLESSING (REVIVE) + // PartyUiMode.MODIFIER_TRANSFER (held items, and ALL) + // PartyUiMode.CHECK --- no specific option, only relevant on cancel? + // PartyUiMode.SPLICE (SPLICE) + // PartyUiMode.MOVE_MODIFIER (MOVE_1, MOVE_2, MOVE_3, MOVE_4) + // PartyUiMode.TM_MODIFIER (TEACH) + // PartyUiMode.REMEMBER_MOVE_MODIFIER (no specific option, callback is invoked when selecting a move) + // PartyUiMode.MODIFIER (APPLY option) + // PartyUiMode.POST_BATTLE_SWITCH (SEND_OUT) + + // These are the options that need a callback + if (option === PartyOption.RELEASE) { + return this.processReleaseOption(pokemon); + } + + if (this.partyUiMode === PartyUiMode.SPLICE) { + if (option === PartyOption.SPLICE) { + (this.selectCallback as PartyModifierSpliceSelectCallback)(this.transferCursor, this.cursor); + this.clearTransfer(); + } else if (option === PartyOption.APPLY) { + this.startTransfer(); + } + this.clearOptions(); + ui.playSelect(); + return true; + } + + // This is used when switching out using the Pokemon command (possibly holding a Baton held item). In this case there is no callback. + if ( + (option === PartyOption.PASS_BATON || option === PartyOption.SEND_OUT) && + this.partyUiMode === PartyUiMode.SWITCH + ) { + this.clearOptions(); + (globalScene.getCurrentPhase() as CommandPhase).handleCommand( + Command.POKEMON, + this.cursor, + option === PartyOption.PASS_BATON, + ); + } + + if ( + [ + PartyOption.SEND_OUT, // When sending out at the start of battle, or due to an effect + PartyOption.PASS_BATON, // When passing the baton due to the Baton Pass move + PartyOption.REVIVE, + PartyOption.APPLY, + PartyOption.TEACH, + PartyOption.MOVE_1, + PartyOption.MOVE_2, + PartyOption.MOVE_3, + PartyOption.MOVE_4, + PartyOption.SELECT, + ].includes(option) && + this.selectCallback + ) { + this.clearOptions(); + const selectCallback = this.selectCallback; + this.selectCallback = null; + selectCallback(this.cursor, option); + return true; + } + + return false; + } + + private processOptionMenuInput(button: Button) { + const ui = this.getUi(); + const option = this.options[this.optionsCursor]; + + // Button.CANCEL has no special behavior for any option + if (button === Button.CANCEL) { + this.clearOptions(); + ui.playSelect(); + return true; + } + + if (button === Button.ACTION) { + return this.processActionButtonForOptions(option); + } + + if (button === Button.UP || button === Button.DOWN) { + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + return this.processModifierTransferModeUpDownInput(button); + } + + if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { + return this.processRememberMoveModeUpDownInput(button); + } + + return this.moveOptionCursor(button); + } + + if (button === Button.LEFT || button === Button.RIGHT) { + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + return this.processModifierTransferModeLeftRightInput(button); + } + } + + return false; + } + processInput(button: Button): boolean { const ui = this.getUi(); @@ -375,463 +892,120 @@ export default class PartyUiHandler extends MessageUiHandler { return false; } - let success = false; - if (this.optionsMode) { - const option = this.options[this.optionsCursor]; - if (button === Button.ACTION) { - const pokemon = globalScene.getPlayerParty()[this.cursor]; - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && !this.transferMode && option !== PartyOption.CANCEL) { - this.startTransfer(); - - let ableToTransfer: string; - for (let p = 0; p < globalScene.getPlayerParty().length; p++) { - // this for look goes through each of the party pokemon - const newPokemon = globalScene.getPlayerParty()[p]; - // this next line gets all of the transferable items from pokemon [p]; it does this by getting all the held modifiers that are transferable and checking to see if they belong to pokemon [p] - const getTransferrableItemsFromPokemon = (newPokemon: PlayerPokemon) => - globalScene.findModifiers( - m => - m instanceof PokemonHeldItemModifier && - (m as PokemonHeldItemModifier).isTransferable && - (m as PokemonHeldItemModifier).pokemonId === newPokemon.id, - ) as PokemonHeldItemModifier[]; - // this next bit checks to see if the the selected item from the original transfer pokemon exists on the new pokemon [p]; 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.pokemonId === newPokemon.id && - m.matchType(getTransferrableItemsFromPokemon(pokemon)[this.transferOptionCursor]), - ) as PokemonHeldItemModifier; - const partySlot = this.partySlots.filter(m => m.getPokemon() === newPokemon)[0]; // this gets pokemon [p] for us - if (p !== this.transferCursor) { - // this skips adding the able/not able labels on the pokemon doing the transfer - if (matchingModifier) { - // if matchingModifier exists then the item exists on the new pokemon - if (matchingModifier.getMaxStackCount() === matchingModifier.stackCount) { - // checks to see if the stack of items is at max stack; if so, set the description label to "Not able" - ableToTransfer = i18next.t("partyUiHandler:notAble"); - } else { - // if the pokemon isn't at max stack, make the label "Able" - ableToTransfer = i18next.t("partyUiHandler:able"); - } - } else { - // if matchingModifier doesn't exist, that means the pokemon doesn't have any of the item, and we need to show "Able" - ableToTransfer = i18next.t("partyUiHandler:able"); - } - } else { - // this else relates to the transfer pokemon. We set the text to be blank so there's no "Able"/"Not able" text - ableToTransfer = ""; - } - partySlot.slotHpBar.setVisible(false); - partySlot.slotHpOverlay.setVisible(false); - partySlot.slotHpText.setVisible(false); - partySlot.slotDescriptionLabel.setText(ableToTransfer); - partySlot.slotDescriptionLabel.setVisible(true); - } - - this.clearOptions(); - ui.playSelect(); - return true; - } - if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER && option !== PartyOption.CANCEL) { - // clear overlay on cancel - this.moveInfoOverlay.clear(); - const filterResult = (this.selectFilter as PokemonSelectFilter)(pokemon); - if (filterResult === null) { - this.selectCallback?.(this.cursor, option); - this.clearOptions(); - } else { - this.clearOptions(); - this.showText(filterResult as string, undefined, () => this.showText("", 0), undefined, true); - } - ui.playSelect(); - return true; - } - if ( - ![ - PartyOption.SUMMARY, - PartyOption.POKEDEX, - PartyOption.UNPAUSE_EVOLUTION, - PartyOption.UNSPLICE, - PartyOption.RELEASE, - PartyOption.CANCEL, - PartyOption.RENAME, - ].includes(option) || - (option === PartyOption.RELEASE && this.partyUiMode === PartyUiMode.RELEASE) - ) { - let filterResult: string | null; - const getTransferrableItemsFromPokemon = (pokemon: PlayerPokemon) => - globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === pokemon.id, - ) as PokemonHeldItemModifier[]; - if (option !== PartyOption.TRANSFER && option !== PartyOption.SPLICE) { - filterResult = (this.selectFilter as PokemonSelectFilter)(pokemon); - if (filterResult === null && (option === PartyOption.SEND_OUT || option === PartyOption.PASS_BATON)) { - filterResult = this.FilterChallengeLegal(pokemon); - } - if (filterResult === null && this.partyUiMode === PartyUiMode.MOVE_MODIFIER) { - filterResult = this.moveSelectFilter(pokemon.moveset[this.optionsCursor]); - } - } else { - filterResult = (this.selectFilter as PokemonModifierTransferSelectFilter)( - pokemon, - getTransferrableItemsFromPokemon(globalScene.getPlayerParty()[this.transferCursor])[ - this.transferOptionCursor - ], - ); - } - if (filterResult === null) { - if (this.partyUiMode !== PartyUiMode.SPLICE) { - this.clearOptions(); - } - if (this.selectCallback && this.partyUiMode !== PartyUiMode.CHECK) { - if (option === PartyOption.TRANSFER) { - if (this.transferCursor !== this.cursor) { - if (this.transferAll) { - getTransferrableItemsFromPokemon(globalScene.getPlayerParty()[this.transferCursor]).forEach( - (_, i, array) => { - const invertedIndex = array.length - 1 - i; - (this.selectCallback as PartyModifierTransferSelectCallback)( - this.transferCursor, - invertedIndex, - this.transferQuantitiesMax[invertedIndex], - this.cursor, - ); - }, - ); - } else { - (this.selectCallback as PartyModifierTransferSelectCallback)( - this.transferCursor, - this.transferOptionCursor, - this.transferQuantities[this.transferOptionCursor], - this.cursor, - ); - } - } - this.clearTransfer(); - } else if (this.partyUiMode === PartyUiMode.SPLICE) { - if (option === PartyOption.SPLICE) { - (this.selectCallback as PartyModifierSpliceSelectCallback)(this.transferCursor, this.cursor); - this.clearTransfer(); - } else { - this.startTransfer(); - } - this.clearOptions(); - } else if (option === PartyOption.RELEASE) { - this.doRelease(this.cursor); - } else { - const selectCallback = this.selectCallback; - this.selectCallback = null; - selectCallback(this.cursor, option); - } - } else { - if ( - option >= PartyOption.FORM_CHANGE_ITEM && - globalScene.getCurrentPhase() instanceof SelectModifierPhase - ) { - if (this.partyUiMode === PartyUiMode.CHECK) { - const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); - const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM]; - modifier.active = !modifier.active; - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger, false, true); - } - } else if (this.cursor) { - (globalScene.getCurrentPhase() as CommandPhase).handleCommand( - Command.POKEMON, - this.cursor, - option === PartyOption.PASS_BATON, - ); - } - } - if ( - this.partyUiMode !== PartyUiMode.MODIFIER && - this.partyUiMode !== PartyUiMode.TM_MODIFIER && - this.partyUiMode !== PartyUiMode.MOVE_MODIFIER - ) { - ui.playSelect(); - } - return true; - } - this.clearOptions(); - this.showText(filterResult as string, undefined, () => this.showText("", 0), undefined, true); - } else if (option === PartyOption.SUMMARY) { - ui.playSelect(); - ui.setModeWithoutClear(UiMode.SUMMARY, pokemon).then(() => this.clearOptions()); - return true; - } else if (option === PartyOption.POKEDEX) { - ui.playSelect(); - const attributes = { - shiny: pokemon.shiny, - variant: pokemon.variant, - form: pokemon.formIndex, - female: pokemon.gender === Gender.FEMALE, - }; - ui.setOverlayMode(UiMode.POKEDEX_PAGE, pokemon.species, attributes).then(() => this.clearOptions()); - return true; - } else if (option === PartyOption.UNPAUSE_EVOLUTION) { - this.clearOptions(); - ui.playSelect(); - pokemon.pauseEvolutions = !pokemon.pauseEvolutions; - this.showText( - i18next.t( - pokemon.pauseEvolutions ? "partyUiHandler:pausedEvolutions" : "partyUiHandler:unpausedEvolutions", - { pokemonName: getPokemonNameWithAffix(pokemon, false) }, - ), - undefined, - () => this.showText("", 0), - null, - true, - ); - } else if (option === PartyOption.UNSPLICE) { - this.clearOptions(); - ui.playSelect(); - this.showText( - i18next.t("partyUiHandler:unspliceConfirmation", { - fusionName: pokemon.fusionSpecies?.name, - pokemonName: pokemon.getName(), - }), - null, - () => { - ui.setModeWithoutClear( - UiMode.CONFIRM, - () => { - const fusionName = pokemon.getName(); - pokemon.unfuse().then(() => { - this.clearPartySlots(); - this.populatePartySlots(); - ui.setMode(UiMode.PARTY); - this.showText( - i18next.t("partyUiHandler:wasReverted", { - fusionName: fusionName, - pokemonName: pokemon.getName(false), - }), - undefined, - () => { - ui.setMode(UiMode.PARTY); - this.showText("", 0); - }, - null, - true, - ); - }); - }, - () => { - ui.setMode(UiMode.PARTY); - this.showText("", 0); - }, - ); - }, - ); - } else if (option === PartyOption.RELEASE) { - this.clearOptions(); - ui.playSelect(); - if (this.cursor >= globalScene.currentBattle.getBattlerCount() || !pokemon.isAllowedInBattle()) { - this.blockInput = true; - this.showText( - i18next.t("partyUiHandler:releaseConfirmation", { - pokemonName: getPokemonNameWithAffix(pokemon, false), - }), - null, - () => { - this.blockInput = false; - ui.setModeWithoutClear( - UiMode.CONFIRM, - () => { - ui.setMode(UiMode.PARTY); - this.doRelease(this.cursor); - }, - () => { - ui.setMode(UiMode.PARTY); - this.showText("", 0); - }, - ); - }, - ); - } else { - this.showText(i18next.t("partyUiHandler:releaseInBattle"), null, () => this.showText("", 0), null, true); - } - return true; - } else if (option === PartyOption.RENAME) { - this.clearOptions(); - ui.playSelect(); - ui.setModeWithoutClear( - UiMode.RENAME_POKEMON, - { - buttonActions: [ - (nickname: string) => { - ui.playSelect(); - pokemon.nickname = nickname; - pokemon.updateInfo(); - this.clearPartySlots(); - this.populatePartySlots(); - ui.setMode(UiMode.PARTY); - }, - () => { - ui.setMode(UiMode.PARTY); - }, - ], - }, - pokemon, - ); - return true; - } else if (option === PartyOption.CANCEL) { - return this.processInput(Button.CANCEL); - } else if (option === PartyOption.SELECT) { - ui.playSelect(); - return true; - } - } else if (button === Button.CANCEL) { - this.clearOptions(); + let success = false; + success = this.processOptionMenuInput(button); + if (success) { ui.playSelect(); - return true; + } + return success; + } + + if (button === Button.ACTION) { + return this.processPartyActionInput(); + } + + if (button === Button.CANCEL) { + return this.processPartyCancelInput(); + } + + if (button === Button.UP || button === Button.DOWN || button === Button.RIGHT || button === Button.LEFT) { + return this.processPartyDirectionalInput(button); + } + + return false; + } + + private allowCancel(): boolean { + return !(this.partyUiMode === PartyUiMode.FAINT_SWITCH || this.partyUiMode === PartyUiMode.REVIVAL_BLESSING); + } + + private processPartyActionInput(): boolean { + const ui = this.getUi(); + if (this.cursor < 6) { + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && !this.transferMode) { + /** Initialize item quantities for the selected Pokemon */ + const itemModifiers = globalScene.findModifiers( + m => + m instanceof PokemonHeldItemModifier && + m.isTransferable && + m.pokemonId === globalScene.getPlayerParty()[this.cursor].id, + ) as PokemonHeldItemModifier[]; + this.transferQuantities = itemModifiers.map(item => item.getStackCount()); + this.transferQuantitiesMax = itemModifiers.map(item => item.getStackCount()); + } + this.showOptions(); + ui.playSelect(); + } + // Pressing return button + if (this.cursor === 6) { + if (!this.allowCancel()) { + ui.playError(); } else { - switch (button) { - case Button.LEFT: - /** Decrease quantity for the current item and update UI */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - this.transferQuantities[option] = - this.transferQuantities[option] === 1 - ? this.transferQuantitiesMax[option] - : this.transferQuantities[option] - 1; - this.updateOptions(); - success = this.setCursor( - this.optionsCursor, - ); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ - } - break; - case Button.RIGHT: - /** Increase quantity for the current item and update UI */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - this.transferQuantities[option] = - this.transferQuantities[option] === this.transferQuantitiesMax[option] - ? 1 - : this.transferQuantities[option] + 1; - this.updateOptions(); - success = this.setCursor( - this.optionsCursor, - ); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ - } - break; - case Button.UP: - /** If currently selecting items to transfer, reset quantity selection */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - if (option !== PartyOption.ALL) { - this.transferQuantities[option] = this.transferQuantitiesMax[option]; - } - this.updateOptions(); - } - success = this.setCursor( - this.optionsCursor ? this.optionsCursor - 1 : this.options.length - 1, - ); /** Move cursor */ - break; - case Button.DOWN: - /** If currently selecting items to transfer, reset quantity selection */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - if (option !== PartyOption.ALL) { - this.transferQuantities[option] = this.transferQuantitiesMax[option]; - } - this.updateOptions(); - } - success = this.setCursor( - this.optionsCursor < this.options.length - 1 ? this.optionsCursor + 1 : 0, - ); /** Move cursor */ - break; - } - - // show move description - if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { - const option = this.options[this.optionsCursor]; - const pokemon = globalScene.getPlayerParty()[this.cursor]; - const move = allMoves[pokemon.getLearnableLevelMoves()[option]]; - if (move) { - this.moveInfoOverlay.show(move); - } else { - // or hide the overlay, in case it's the cancel button - this.moveInfoOverlay.clear(); - } - } + return this.processInput(Button.CANCEL); } - } else { - if (button === Button.ACTION) { - if (this.cursor < 6) { - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && !this.transferMode) { - /** Initialize item quantities for the selected Pokemon */ - const itemModifiers = globalScene.findModifiers( - m => - m instanceof PokemonHeldItemModifier && - m.isTransferable && - m.pokemonId === globalScene.getPlayerParty()[this.cursor].id, - ) as PokemonHeldItemModifier[]; - this.transferQuantities = itemModifiers.map(item => item.getStackCount()); - this.transferQuantitiesMax = itemModifiers.map(item => item.getStackCount()); - } - this.showOptions(); - ui.playSelect(); - } else if (this.partyUiMode === PartyUiMode.FAINT_SWITCH || this.partyUiMode === PartyUiMode.REVIVAL_BLESSING) { - ui.playError(); - } else { - return this.processInput(Button.CANCEL); - } - return true; + } + return true; + } + + private processPartyCancelInput(): boolean { + const ui = this.getUi(); + if ( + (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER || this.partyUiMode === PartyUiMode.SPLICE) && + this.transferMode + ) { + this.clearTransfer(); + ui.playSelect(); + } else if (this.allowCancel()) { + if (this.selectCallback) { + const selectCallback = this.selectCallback; + this.selectCallback = null; + selectCallback(6, PartyOption.CANCEL); + ui.playSelect(); + } else { + ui.setMode(UiMode.COMMAND, this.fieldIndex); + ui.playSelect(); } - if (button === Button.CANCEL) { - if ( - (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER || this.partyUiMode === PartyUiMode.SPLICE) && - this.transferMode - ) { - this.clearTransfer(); - ui.playSelect(); - } else if (this.partyUiMode !== PartyUiMode.FAINT_SWITCH && this.partyUiMode !== PartyUiMode.REVIVAL_BLESSING) { - if (this.selectCallback) { - const selectCallback = this.selectCallback; - this.selectCallback = null; - selectCallback(6, PartyOption.CANCEL); - ui.playSelect(); - } else { - ui.setMode(UiMode.COMMAND, this.fieldIndex); - ui.playSelect(); - } + } + return true; + } + + private processPartyDirectionalInput(button: Button.UP | Button.DOWN | Button.LEFT | Button.RIGHT): boolean { + const ui = this.getUi(); + const slotCount = this.partySlots.length; + const battlerCount = globalScene.currentBattle.getBattlerCount(); + + let success = false; + switch (button) { + case Button.UP: + success = this.setCursor(this.cursor ? (this.cursor < 6 ? this.cursor - 1 : slotCount - 1) : 6); + break; + case Button.DOWN: + success = this.setCursor(this.cursor < 6 ? (this.cursor < slotCount - 1 ? this.cursor + 1 : 6) : 0); + break; + case Button.LEFT: + if (this.cursor >= battlerCount && this.cursor <= 6) { + success = this.setCursor(0); } - - return true; - } - - const slotCount = this.partySlots.length; - const battlerCount = globalScene.currentBattle.getBattlerCount(); - - switch (button) { - case Button.UP: - success = this.setCursor(this.cursor ? (this.cursor < 6 ? this.cursor - 1 : slotCount - 1) : 6); + break; + case Button.RIGHT: + if (slotCount === battlerCount) { + success = this.setCursor(6); break; - case Button.DOWN: - success = this.setCursor(this.cursor < 6 ? (this.cursor < slotCount - 1 ? this.cursor + 1 : 6) : 0); + } + if (battlerCount >= 2 && slotCount > battlerCount && this.getCursor() === 0 && this.lastCursor === 1) { + success = this.setCursor(2); break; - case Button.LEFT: - if (this.cursor >= battlerCount && this.cursor <= 6) { - success = this.setCursor(0); - } + } + if (slotCount > battlerCount && this.cursor < battlerCount) { + success = this.setCursor(this.lastCursor < 6 ? this.lastCursor || battlerCount : battlerCount); break; - case Button.RIGHT: - if (slotCount === battlerCount) { - success = this.setCursor(6); - break; - } - if (battlerCount >= 2 && slotCount > battlerCount && this.getCursor() === 0 && this.lastCursor === 1) { - success = this.setCursor(2); - break; - } - if (slotCount > battlerCount && this.cursor < battlerCount) { - success = this.setCursor(this.lastCursor < 6 ? this.lastCursor || battlerCount : battlerCount); - break; - } - } + } } if (success) { ui.playSelect(); } - return success; } @@ -860,64 +1034,66 @@ export default class PartyUiHandler extends MessageUiHandler { } setCursor(cursor: number): boolean { - let changed: boolean; - if (this.optionsMode) { - changed = this.optionsCursor !== cursor; - let isScroll = false; - if (changed && this.optionsScroll) { - if (Math.abs(cursor - this.optionsCursor) === this.options.length - 1) { - this.optionsScrollCursor = cursor ? this.optionsScrollTotal - 8 : 0; - this.updateOptions(); - } else { - const isDown = cursor && cursor > this.optionsCursor; - if (isDown) { - if (this.options[cursor] === PartyOption.SCROLL_DOWN) { - isScroll = true; - this.optionsScrollCursor++; - } - } else { - if (!cursor && this.optionsScrollCursor) { - isScroll = true; - this.optionsScrollCursor--; - } - } - if (isScroll && this.optionsScrollCursor === 1) { - this.optionsScrollCursor += isDown ? 1 : -1; - } - } + return this.setOptionsCursor(cursor); + } + const changed = this.cursor !== cursor; + if (changed) { + this.lastCursor = this.cursor; + this.cursor = cursor; + if (this.lastCursor < 6) { + this.partySlots[this.lastCursor].deselect(); + } else if (this.lastCursor === 6) { + this.partyCancelButton.deselect(); } - if (isScroll) { + if (cursor < 6) { + this.partySlots[cursor].select(); + } else if (cursor === 6) { + this.partyCancelButton.select(); + } + } + return changed; + } + + private setOptionsCursor(cursor: number): boolean { + const changed = this.optionsCursor !== cursor; + let isScroll = false; + if (changed && this.optionsScroll) { + if (Math.abs(cursor - this.optionsCursor) === this.options.length - 1) { + this.optionsScrollCursor = cursor ? this.optionsScrollTotal - 8 : 0; this.updateOptions(); } else { - this.optionsCursor = cursor; - } - if (!this.optionsCursorObj) { - this.optionsCursorObj = globalScene.add.image(0, 0, "cursor"); - this.optionsCursorObj.setOrigin(0, 0); - this.optionsContainer.add(this.optionsCursorObj); - } - this.optionsCursorObj.setPosition( - 8 - this.optionsBg.displayWidth, - -19 - 16 * (this.options.length - 1 - this.optionsCursor), - ); - } else { - changed = this.cursor !== cursor; - if (changed) { - this.lastCursor = this.cursor; - this.cursor = cursor; - if (this.lastCursor < 6) { - this.partySlots[this.lastCursor].deselect(); - } else if (this.lastCursor === 6) { - this.partyCancelButton.deselect(); + const isDown = cursor && cursor > this.optionsCursor; + if (isDown) { + if (this.options[cursor] === PartyOption.SCROLL_DOWN) { + isScroll = true; + this.optionsScrollCursor++; + } + } else { + if (!cursor && this.optionsScrollCursor) { + isScroll = true; + this.optionsScrollCursor--; + } } - if (cursor < 6) { - this.partySlots[cursor].select(); - } else if (cursor === 6) { - this.partyCancelButton.select(); + if (isScroll && this.optionsScrollCursor === 1) { + this.optionsScrollCursor += isDown ? 1 : -1; } } } + if (isScroll) { + this.updateOptions(); + } else { + this.optionsCursor = cursor; + } + if (!this.optionsCursorObj) { + this.optionsCursorObj = globalScene.add.image(0, 0, "cursor"); + this.optionsCursorObj.setOrigin(0, 0); + this.optionsContainer.add(this.optionsCursorObj); + } + this.optionsCursorObj.setPosition( + 8 - this.optionsBg.displayWidth, + -19 - 16 * (this.options.length - 1 - this.optionsCursor), + ); return changed; } @@ -984,149 +1160,81 @@ export default class PartyUiHandler extends MessageUiHandler { this.setCursor(0); } - updateOptions(): void { - const pokemon = globalScene.getPlayerParty()[this.cursor]; + private allowBatonModifierSwitch(): boolean { + return !!( + this.partyUiMode !== PartyUiMode.FAINT_SWITCH && + globalScene.findModifier( + m => + m instanceof SwitchEffectTransferModifier && + m.pokemonId === globalScene.getPlayerField()[this.fieldIndex].id, + ) + ); + } - const learnableLevelMoves = - this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER ? pokemon.getLearnableLevelMoves() : []; + // TODO: add FORCED_SWITCH (and perhaps also BATON_PASS_SWITCH) to the modes + private isBatonPassMove(): boolean { + const moveHistory = globalScene.getPlayerField()[this.fieldIndex].getMoveHistory(); + 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 + ); + } - if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER && learnableLevelMoves?.length) { + private getItemModifiers(pokemon: Pokemon): PokemonHeldItemModifier[] { + return ( + (globalScene.findModifiers( + m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === pokemon.id, + ) as PokemonHeldItemModifier[]) ?? [] + ); + } + + private updateOptionsWithRememberMoveModifierMode(pokemon): void { + const learnableMoves = pokemon.getLearnableLevelMoves(); + for (let m = 0; m < learnableMoves.length; m++) { + this.options.push(m); + } + if (learnableMoves?.length) { // show the move overlay with info for the first move - this.moveInfoOverlay.show(allMoves[learnableLevelMoves[0]]); + this.moveInfoOverlay.show(allMoves[learnableMoves[0]]); } + } - const itemModifiers = - this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER - ? (globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === pokemon.id, - ) as PokemonHeldItemModifier[]) - : []; - - if (this.options.length) { - this.options.splice(0, this.options.length); - this.optionsContainer.removeAll(true); - this.eraseOptionsCursor(); + private updateOptionsWithMoveModifierMode(pokemon): void { + // MOVE_1, MOVE_2, MOVE_3, MOVE_4 + for (let m = 0; m < pokemon.moveset.length; m++) { + this.options.push(PartyOption.MOVE_1 + m); } + } - let formChangeItemModifiers: PokemonFormChangeItemModifier[] | undefined; + private updateOptionsWithModifierTransferMode(pokemon): void { + const itemModifiers = this.getItemModifiers(pokemon); + for (let im = 0; im < itemModifiers.length; im++) { + this.options.push(im); + } + if (itemModifiers.length > 1) { + this.options.push(PartyOption.ALL); + } + } + + private addCommonOptions(pokemon): void { + this.options.push(PartyOption.SUMMARY); + this.options.push(PartyOption.POKEDEX); + this.options.push(PartyOption.RENAME); if ( - this.partyUiMode !== PartyUiMode.MOVE_MODIFIER && - this.partyUiMode !== PartyUiMode.REMEMBER_MOVE_MODIFIER && - (this.transferMode || this.partyUiMode !== PartyUiMode.MODIFIER_TRANSFER) + pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) || + (pokemon.isFusion() && pokemon.fusionSpecies && pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId)) ) { - switch (this.partyUiMode) { - case PartyUiMode.SWITCH: - case PartyUiMode.FAINT_SWITCH: - case PartyUiMode.POST_BATTLE_SWITCH: - if (this.cursor >= globalScene.currentBattle.getBattlerCount()) { - const allowBatonModifierSwitch = - this.partyUiMode !== PartyUiMode.FAINT_SWITCH && - globalScene.findModifier( - m => - m instanceof SwitchEffectTransferModifier && - (m as SwitchEffectTransferModifier).pokemonId === globalScene.getPlayerField()[this.fieldIndex].id, - ); - - const moveHistory = globalScene.getPlayerField()[this.fieldIndex].getMoveHistory(); - const isBatonPassMove = - this.partyUiMode === PartyUiMode.FAINT_SWITCH && - moveHistory.length && - allMoves[moveHistory[moveHistory.length - 1].move].getAttrs(ForceSwitchOutAttr)[0]?.isBatonPass() && - moveHistory[moveHistory.length - 1].result === MoveResult.SUCCESS; - - // isBatonPassMove and allowBatonModifierSwitch shouldn't ever be true - // at the same time, because they both explicitly check for a mutually - // exclusive partyUiMode. But better safe than sorry. - this.options.push( - isBatonPassMove && !allowBatonModifierSwitch ? PartyOption.PASS_BATON : PartyOption.SEND_OUT, - ); - if (allowBatonModifierSwitch && !isBatonPassMove) { - // the BATON modifier gives an extra switch option for - // pokemon-command switches, allowing buffs to be optionally passed - this.options.push(PartyOption.PASS_BATON); - } - } - break; - case PartyUiMode.REVIVAL_BLESSING: - this.options.push(PartyOption.REVIVE); - break; - case PartyUiMode.MODIFIER: - this.options.push(PartyOption.APPLY); - break; - case PartyUiMode.TM_MODIFIER: - this.options.push(PartyOption.TEACH); - break; - case PartyUiMode.MODIFIER_TRANSFER: - this.options.push(PartyOption.TRANSFER); - break; - case PartyUiMode.SPLICE: - if (this.transferMode) { - if (this.cursor !== this.transferCursor) { - this.options.push(PartyOption.SPLICE); - } - } else { - this.options.push(PartyOption.APPLY); - } - break; - case PartyUiMode.RELEASE: - this.options.push(PartyOption.RELEASE); - break; - case PartyUiMode.CHECK: - if (globalScene.getCurrentPhase() instanceof SelectModifierPhase) { - formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); - for (let i = 0; i < formChangeItemModifiers.length; i++) { - this.options.push(PartyOption.FORM_CHANGE_ITEM + i); - } - } - break; - case PartyUiMode.SELECT: - this.options.push(PartyOption.SELECT); - break; - } - - this.options.push(PartyOption.SUMMARY); - this.options.push(PartyOption.POKEDEX); - this.options.push(PartyOption.RENAME); - - if ( - pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) || - (pokemon.isFusion() && - pokemon.fusionSpecies && - pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId)) - ) { - this.options.push(PartyOption.UNPAUSE_EVOLUTION); - } - - if (this.partyUiMode === PartyUiMode.SWITCH) { - if (pokemon.isFusion()) { - this.options.push(PartyOption.UNSPLICE); - } - this.options.push(PartyOption.RELEASE); - } else if (this.partyUiMode === PartyUiMode.SPLICE && pokemon.isFusion()) { - this.options.push(PartyOption.UNSPLICE); - } - } else if (this.partyUiMode === PartyUiMode.MOVE_MODIFIER) { - for (let m = 0; m < pokemon.moveset.length; m++) { - this.options.push(PartyOption.MOVE_1 + m); - } - } else if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { - const learnableMoves = pokemon.getLearnableLevelMoves(); - for (let m = 0; m < learnableMoves.length; m++) { - this.options.push(m); - } - } else { - for (let im = 0; im < itemModifiers.length; im++) { - this.options.push(im); - } - if (itemModifiers.length > 1) { - this.options.push(PartyOption.ALL); - } + this.options.push(PartyOption.UNPAUSE_EVOLUTION); } + } + private addCancelAndScrollOptions(): void { this.optionsScrollTotal = this.options.length; - let optionStartIndex = this.optionsScrollCursor; - let optionEndIndex = Math.min( + const optionStartIndex = this.optionsScrollCursor; + const optionEndIndex = Math.min( this.optionsScrollTotal, optionStartIndex + (!optionStartIndex || this.optionsScrollCursor + 8 >= this.optionsScrollTotal ? 8 : 7), ); @@ -1145,14 +1253,122 @@ export default class PartyUiHandler extends MessageUiHandler { } this.options.push(PartyOption.CANCEL); + } + + updateOptions(): void { + const pokemon = globalScene.getPlayerParty()[this.cursor]; + + if (this.options.length) { + this.options.splice(0, this.options.length); + this.optionsContainer.removeAll(true); + this.eraseOptionsCursor(); + } + + switch (this.partyUiMode) { + case PartyUiMode.MOVE_MODIFIER: + this.updateOptionsWithMoveModifierMode(pokemon); + break; + case PartyUiMode.REMEMBER_MOVE_MODIFIER: + this.updateOptionsWithRememberMoveModifierMode(pokemon); + break; + case PartyUiMode.MODIFIER_TRANSFER: + if (!this.transferMode) { + this.updateOptionsWithModifierTransferMode(pokemon); + } else { + this.options.push(PartyOption.TRANSFER); + this.addCommonOptions(pokemon); + } + break; + // TODO: This still needs to be broken up. + // It could use a rework differentiating different kind of switches + // to treat baton passing separately from switching on faint. + case PartyUiMode.SWITCH: + case PartyUiMode.FAINT_SWITCH: + case PartyUiMode.POST_BATTLE_SWITCH: + if (this.cursor >= globalScene.currentBattle.getBattlerCount()) { + const allowBatonModifierSwitch = this.allowBatonModifierSwitch(); + const isBatonPassMove = this.isBatonPassMove(); + + // isBatonPassMove and allowBatonModifierSwitch shouldn't ever be true + // at the same time, because they both explicitly check for a mutually + // exclusive partyUiMode. But better safe than sorry. + this.options.push( + isBatonPassMove && !allowBatonModifierSwitch ? PartyOption.PASS_BATON : PartyOption.SEND_OUT, + ); + if (allowBatonModifierSwitch && !isBatonPassMove) { + // the BATON modifier gives an extra switch option for + // pokemon-command switches, allowing buffs to be optionally passed + this.options.push(PartyOption.PASS_BATON); + } + } + this.addCommonOptions(pokemon); + if (this.partyUiMode === PartyUiMode.SWITCH) { + if (pokemon.isFusion()) { + this.options.push(PartyOption.UNSPLICE); + } + this.options.push(PartyOption.RELEASE); + } + break; + case PartyUiMode.REVIVAL_BLESSING: + this.options.push(PartyOption.REVIVE); + this.addCommonOptions(pokemon); + break; + case PartyUiMode.MODIFIER: + this.options.push(PartyOption.APPLY); + this.addCommonOptions(pokemon); + break; + case PartyUiMode.TM_MODIFIER: + this.options.push(PartyOption.TEACH); + this.addCommonOptions(pokemon); + break; + case PartyUiMode.SPLICE: + if (this.transferMode) { + if (this.cursor !== this.transferCursor) { + this.options.push(PartyOption.SPLICE); + } + } else { + this.options.push(PartyOption.APPLY); + } + this.addCommonOptions(pokemon); + if (pokemon.isFusion()) { + this.options.push(PartyOption.UNSPLICE); + } + break; + case PartyUiMode.RELEASE: + this.options.push(PartyOption.RELEASE); + this.addCommonOptions(pokemon); + break; + case PartyUiMode.CHECK: + if (globalScene.getCurrentPhase() instanceof SelectModifierPhase) { + const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); + for (let i = 0; i < formChangeItemModifiers.length; i++) { + this.options.push(PartyOption.FORM_CHANGE_ITEM + i); + } + } + this.addCommonOptions(pokemon); + break; + case PartyUiMode.SELECT: + this.options.push(PartyOption.SELECT); + this.addCommonOptions(pokemon); + break; + } + + // Generic, these are applied to all Modes + this.addCancelAndScrollOptions(); + + this.updateOptionsWindow(); + } + + private updateOptionsWindow(): void { + const pokemon = globalScene.getPlayerParty()[this.cursor]; this.optionsBg = addWindow(0, 0, 0, 16 * this.options.length + 13); this.optionsBg.setOrigin(1, 1); this.optionsContainer.add(this.optionsBg); - optionStartIndex = 0; - optionEndIndex = this.options.length; + const optionStartIndex = 0; + const optionEndIndex = this.options.length; let widestOptionWidth = 0; const optionTexts: BBCodeText[] = []; @@ -1185,6 +1401,7 @@ export default class PartyUiHandler extends MessageUiHandler { } break; default: + const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); if (formChangeItemModifiers && option >= PartyOption.FORM_CHANGE_ITEM) { const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM]; optionName = `${modifier.active ? i18next.t("partyUiHandler:DEACTIVATE") : i18next.t("partyUiHandler:ACTIVATE")} ${modifier.type.name}`; @@ -1200,6 +1417,7 @@ export default class PartyUiHandler extends MessageUiHandler { break; } } else if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { + const learnableLevelMoves = pokemon.getLearnableLevelMoves(); const move = learnableLevelMoves[option]; optionName = allMoves[move].name; altText = !pokemon @@ -1209,6 +1427,7 @@ export default class PartyUiHandler extends MessageUiHandler { } else if (option === PartyOption.ALL) { optionName = i18next.t("partyUiHandler:ALL"); } else { + const itemModifiers = this.getItemModifiers(pokemon); const itemModifier = itemModifiers[option]; optionName = itemModifier.type.name; } @@ -1222,6 +1441,7 @@ export default class PartyUiHandler extends MessageUiHandler { optionText.setOrigin(0, 0); /** For every item that has stack bigger than 1, display the current quantity selection */ + const itemModifiers = this.getItemModifiers(pokemon); const itemModifier = itemModifiers[option]; if ( this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && From a98f89759169201b671e1f17c973dce81af28bab Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 27 May 2025 05:02:39 -0700 Subject: [PATCH 06/14] [Bug] Fix Dipplin's weight (was mistakenly set to the lbs value) --- src/data/pokemon-species.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 6f05d17db6f..5c97f360094 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -3121,7 +3121,7 @@ export function initSpecies() { ), new PokemonSpecies(Species.WALKING_WAKE, 9, false, false, false, "Paradox Pokémon", PokemonType.WATER, PokemonType.DRAGON, 3.5, 280, Abilities.PROTOSYNTHESIS, Abilities.NONE, Abilities.NONE, 590, 99, 83, 91, 125, 83, 109, 10, 0, 295, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Gouging Fire and Raging Bolt new PokemonSpecies(Species.IRON_LEAVES, 9, false, false, false, "Paradox Pokémon", PokemonType.GRASS, PokemonType.PSYCHIC, 1.5, 125, Abilities.QUARK_DRIVE, Abilities.NONE, Abilities.NONE, 590, 90, 130, 88, 70, 108, 104, 10, 0, 295, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Iron Boulder and Iron Crown - new PokemonSpecies(Species.DIPPLIN, 9, false, false, false, "Candy Apple Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 0.4, 9.7, Abilities.SUPERSWEET_SYRUP, Abilities.GLUTTONY, Abilities.STICKY_HOLD, 485, 80, 80, 110, 95, 80, 40, 45, 50, 170, GrowthRate.ERRATIC, 50, false), + new PokemonSpecies(Species.DIPPLIN, 9, false, false, false, "Candy Apple Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 0.4, 4.4, Abilities.SUPERSWEET_SYRUP, Abilities.GLUTTONY, Abilities.STICKY_HOLD, 485, 80, 80, 110, 95, 80, 40, 45, 50, 170, GrowthRate.ERRATIC, 50, false), new PokemonSpecies(Species.POLTCHAGEIST, 9, false, false, false, "Matcha Pokémon", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, Abilities.HOSPITALITY, Abilities.NONE, Abilities.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, GrowthRate.SLOW, null, false, false, new PokemonForm("Counterfeit Form", "counterfeit", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, Abilities.HOSPITALITY, Abilities.NONE, Abilities.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, null, true), new PokemonForm("Artisan Form", "artisan", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, Abilities.HOSPITALITY, Abilities.NONE, Abilities.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, null, false, true), From 68dddbc4246aeedea3c8fdb506263c9c217caf08 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 27 May 2025 07:50:54 -0700 Subject: [PATCH 07/14] [Dev] Enable Biome checking of `pokemon.ts` (#5720) * [Dev] Enable biome parsing of `pokemon.ts` * Apply unsafe fixes * Add + apply rule disallowing the use of the `integer` type alias * Fix typo in comment; remove unnecessary `!!` * Re-apply Biome after merge * Re-apply Biome "unsafe" fixes after merge * Fix import * Add comment to `getFusionIconAtlasKey` too --- biome.jsonc | 20 +- .../the-pokemon-salesman-encounter.ts | 26 +- src/data/trainers/TrainerPartyTemplate.ts | 18 +- src/field/pokemon.ts | 2765 +++++------------ src/phases/revival-blessing-phase.ts | 2 +- src/ui/login-form-ui-handler.ts | 6 +- 6 files changed, 891 insertions(+), 1946 deletions(-) diff --git a/biome.jsonc b/biome.jsonc index 3385614635c..40301b3e0bc 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -32,7 +32,6 @@ // TODO: these files are too big and complex, ignore them until their respective refactors "src/data/moves/move.ts", "src/data/abilities/ability.ts", - "src/field/pokemon.ts", // this file is just too big: "src/data/balance/tms.ts" @@ -58,7 +57,7 @@ }, "style": { "noVar": "error", - "useEnumInitializers": "off", // large enums like Moves/Species would make this cumbersome + "useEnumInitializers": "off", // large enums like Moves/Species would make this cumbersome "useBlockStatements": "error", "useConst": "error", "useImportType": "error", @@ -73,9 +72,9 @@ }, "suspicious": { "noDoubleEquals": "error", - // While this would be a nice rule to enable, the current structure of the codebase makes this infeasible + // While this would be a nice rule to enable, the current structure of the codebase makes this infeasible // due to being used for move/ability `args` params and save data-related code. - // This can likely be enabled for all non-utils files once these are eventually reworked, but until then we leave it off. + // This can likely be enabled for all non-utils files once these are eventually reworked, but until then we leave it off. "noExplicitAny": "off", "noAssignInExpressions": "off", "noPrototypeBuiltins": "off", @@ -92,6 +91,19 @@ "noUselessSwitchCase": "off", // Explicit > Implicit "noUselessConstructor": "warn", // TODO: Refactor and make this an error "noBannedTypes": "warn" // TODO: Refactor and make this an error + }, + "nursery": { + "noRestrictedTypes": { + "level": "error", + "options": { + "types": { + "integer": { + "message": "This is an alias for 'number' that can provide false impressions of what values can actually be contained in this variable. Use 'number' instead.", + "use": "number" + } + } + } + } } } }, 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 25798de3b4a..50b9c2da78c 100644 --- a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts @@ -3,7 +3,7 @@ import { transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { isNullOrUndefined, NumberHolder, randSeedInt, randSeedItem } from "#app/utils/common"; +import { isNullOrUndefined, randSeedInt, randSeedItem } from "#app/utils/common"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; @@ -88,7 +88,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui const r = randSeedInt(SHINY_MAGIKARP_WEIGHT); - let validEventEncounters = timedEventManager + const validEventEncounters = timedEventManager .getEventEncounters() .filter( s => @@ -111,22 +111,26 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui if ( r === 0 || ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && - (validEventEncounters.length === 0)) + validEventEncounters.length === 0) ) { // If you roll 1%, give shiny Magikarp with random variant species = getPokemonSpecies(Species.MAGIKARP); pokemon = new PlayerPokemon(species, 5, 2, undefined, undefined, true); - } - else if ( - (validEventEncounters.length > 0 && (r <= EVENT_THRESHOLD || - (isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE))) + } else if ( + validEventEncounters.length > 0 && + (r <= EVENT_THRESHOLD || isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) ) { tries = 0; do { // If you roll 20%, give event encounter with 3 extra shiny rolls and its HA, if it has one const enc = randSeedItem(validEventEncounters); species = getPokemonSpecies(enc.species); - pokemon = new PlayerPokemon(species, 5, species.abilityHidden === Abilities.NONE ? undefined : 2, enc.formIndex); + pokemon = new PlayerPokemon( + species, + 5, + species.abilityHidden === Abilities.NONE ? undefined : 2, + enc.formIndex, + ); pokemon.trySetShinySeed(); pokemon.trySetShinySeed(); pokemon.trySetShinySeed(); @@ -145,15 +149,13 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui pokemon.trySetShinySeed(); pokemon.trySetShinySeed(); pokemon.trySetShinySeed(); - } - else { + } else { // If there's, and this would never happen, no eligible event encounters with a hidden ability, just do Magikarp species = getPokemonSpecies(Species.MAGIKARP); pokemon = new PlayerPokemon(species, 5, 2, undefined, undefined, true); } } - } - else { + } else { pokemon = new PlayerPokemon(species, 5, 2, species.formIndex); } pokemon.generateAndPopulateMoveset(); diff --git a/src/data/trainers/TrainerPartyTemplate.ts b/src/data/trainers/TrainerPartyTemplate.ts index ccc494218e9..86201589276 100644 --- a/src/data/trainers/TrainerPartyTemplate.ts +++ b/src/data/trainers/TrainerPartyTemplate.ts @@ -224,16 +224,16 @@ export const trainerPartyTemplates = { */ export function getEvilGruntPartyTemplate(): TrainerPartyTemplate { const waveIndex = globalScene.currentBattle?.waveIndex; - if (waveIndex <= ClassicFixedBossWaves.EVIL_GRUNT_1){ + if (waveIndex <= ClassicFixedBossWaves.EVIL_GRUNT_1) { return trainerPartyTemplates.TWO_AVG; } - if (waveIndex <= ClassicFixedBossWaves.EVIL_GRUNT_2){ + if (waveIndex <= ClassicFixedBossWaves.EVIL_GRUNT_2) { return trainerPartyTemplates.THREE_AVG; } - if (waveIndex <= ClassicFixedBossWaves.EVIL_GRUNT_3){ + if (waveIndex <= ClassicFixedBossWaves.EVIL_GRUNT_3) { return trainerPartyTemplates.TWO_AVG_ONE_STRONG; } - if (waveIndex <= ClassicFixedBossWaves.EVIL_ADMIN_1){ + if (waveIndex <= ClassicFixedBossWaves.EVIL_ADMIN_1) { return trainerPartyTemplates.GYM_LEADER_4; // 3avg 1 strong 1 stronger } return trainerPartyTemplates.GYM_LEADER_5; // 3 avg 2 strong 1 stronger @@ -251,7 +251,7 @@ export function getGymLeaderPartyTemplate() { switch (gameMode.modeId) { case GameModes.DAILY: if (currentBattle?.waveIndex <= 20) { - return trainerPartyTemplates.GYM_LEADER_2 + return trainerPartyTemplates.GYM_LEADER_2; } return trainerPartyTemplates.GYM_LEADER_3; case GameModes.CHALLENGE: // In the future, there may be a ChallengeType to call here. For now, use classic's. @@ -259,13 +259,15 @@ export function getGymLeaderPartyTemplate() { if (currentBattle?.waveIndex <= 20) { return trainerPartyTemplates.GYM_LEADER_1; // 1 avg 1 strong } - else if (currentBattle?.waveIndex <= 30) { + if (currentBattle?.waveIndex <= 30) { return trainerPartyTemplates.GYM_LEADER_2; // 1 avg 1 strong 1 stronger } - else if (currentBattle?.waveIndex <= 60) { // 50 and 60 + // 50 and 60 + if (currentBattle?.waveIndex <= 60) { return trainerPartyTemplates.GYM_LEADER_3; // 2 avg 1 strong 1 stronger } - else if (currentBattle?.waveIndex <= 90) { // 80 and 90 + // 80 and 90 + if (currentBattle?.waveIndex <= 90) { return trainerPartyTemplates.GYM_LEADER_4; // 3 avg 1 strong 1 stronger } // 110+ diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 62ec8081c5d..85b003517a6 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -5,10 +5,7 @@ import { globalScene } from "#app/global-scene"; import type { Variant } from "#app/sprites/variant"; import { populateVariantColors, variantColorCache } from "#app/sprites/variant"; import { variantData } from "#app/sprites/variant"; -import BattleInfo, { - PlayerBattleInfo, - EnemyBattleInfo, -} from "#app/ui/battle-info"; +import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "#app/ui/battle-info"; import type Move from "#app/data/moves/move"; import { HighCritAttr, @@ -50,11 +47,26 @@ import { getPokemonSpecies, getPokemonSpeciesForm, } from "#app/data/pokemon-species"; +import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { - getStarterValueFriendshipCap, - speciesStarterCosts, -} from "#app/data/balance/starters"; -import { NumberHolder, randSeedInt, getIvsFromId, BooleanHolder, randSeedItem, isNullOrUndefined, getEnumValues, toDmgValue, fixedInt, rgbaToInt, rgbHexToRgba, rgbToHsv, deltaRgb, isBetween, type nil, type Constructor, randSeedIntRange } from "#app/utils/common"; + NumberHolder, + randSeedInt, + getIvsFromId, + BooleanHolder, + randSeedItem, + isNullOrUndefined, + getEnumValues, + toDmgValue, + fixedInt, + rgbaToInt, + rgbHexToRgba, + rgbToHsv, + deltaRgb, + isBetween, + type nil, + type Constructor, + randSeedIntRange, +} from "#app/utils/common"; import type { TypeDamageMultiplier } from "#app/data/type"; import { getTypeDamageMultiplier, getTypeRgb } from "#app/data/type"; import { PokemonType } from "#enums/pokemon-type"; @@ -92,20 +104,13 @@ 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, SpeciesEvolutionCondition } from "#app/data/balance/pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions, FusionSpeciesFormEvolution, } from "#app/data/balance/pokemon-evolutions"; -import { - reverseCompatibleTms, - tmSpecies, - tmPoolTiers, -} from "#app/data/balance/tms"; +import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms"; import { BattlerTag, BattlerTagLapseType, @@ -128,11 +133,7 @@ import { type GrudgeTag, } from "../data/battler-tags"; import { WeatherType } from "#enums/weather-type"; -import { - ArenaTagSide, - NoCritTag, - WeakenMoveScreenTag, -} from "#app/data/arena-tag"; +import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; 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"; @@ -171,7 +172,8 @@ import { MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, - CheckTrappedAbAttr, InfiltratorAbAttr, + CheckTrappedAbAttr, + InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, @@ -196,25 +198,18 @@ import type { PartyOption } from "#app/ui/party-ui-handler"; import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; -import { - EVOLVE_MOVE, - RELEARN_MOVE, -} 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 { - QuantizerCelebi, - argbFromRgba, - rgbaFromArgb, -} from "@material/material-color-utilities"; +import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities"; import { getNatureStatMultiplier } from "#app/data/nature"; import type { SpeciesFormChange } from "#app/data/pokemon-forms"; import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeMoveLearnedTrigger, - SpeciesFormChangePostMoveTrigger + SpeciesFormChangePostMoveTrigger, } from "#app/data/pokemon-forms"; import { TerrainType } from "#app/data/terrain"; import type { TrainerSlot } from "#enums/trainer-slot"; @@ -298,10 +293,10 @@ type damageParams = { simulated?: boolean; /** If defined, used in place of calculated effectiveness values */ effectiveness?: number; -} +}; /** Type for the parameters of {@linkcode Pokemon#getBaseDamage | getBaseDamage} */ -type getBaseDamageParams = Omit +type getBaseDamageParams = Omit; /** Type for the parameters of {@linkcode Pokemon#getAttackDamage | getAttackDamage} */ type getAttackDamageParams = Omit; @@ -413,7 +408,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.pokeball = dataSource?.pokeball || PokeballType.POKEBALL; this.level = level; - this.abilityIndex = abilityIndex ?? this.generateAbilityIndex() + this.abilityIndex = abilityIndex ?? this.generateAbilityIndex(); if (formIndex !== undefined) { this.formIndex = formIndex; @@ -427,8 +422,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (variant !== undefined) { this.variant = variant; } - this.exp = - dataSource?.exp || getLevelTotalExp(this.level, species.growthRate); + this.exp = dataSource?.exp || getLevelTotalExp(this.level, species.growthRate); this.levelExp = dataSource?.levelExp || 0; if (dataSource) { @@ -444,18 +438,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.nickname = dataSource.nickname; this.moveset = dataSource.moveset; this.status = dataSource.status!; // TODO: is this bang correct? - this.friendship = - dataSource.friendship !== undefined - ? dataSource.friendship - : this.species.baseFriendship; + this.friendship = dataSource.friendship ?? this.species.baseFriendship; this.metLevel = dataSource.metLevel || 5; this.luck = dataSource.luck; this.metBiome = dataSource.metBiome; this.metSpecies = - dataSource.metSpecies ?? - (this.metBiome !== -1 - ? this.species.speciesId - : this.species.getRootSpeciesId(true)); + dataSource.metSpecies ?? (this.metBiome !== -1 ? this.species.speciesId : this.species.getRootSpeciesId(true)); this.metWave = dataSource.metWave ?? (this.metBiome === -1 ? -1 : 0); this.pauseEvolutions = dataSource.pauseEvolutions; this.pokerus = !!dataSource.pokerus; @@ -475,9 +463,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.fusionCustomPokemonData = dataSource.fusionCustomPokemonData; this.fusionTeraType = dataSource.fusionTeraType; this.usedTMs = dataSource.usedTMs ?? []; - this.customPokemonData = new CustomPokemonData( - dataSource.customPokemonData, - ); + this.customPokemonData = new CustomPokemonData(dataSource.customPokemonData); this.teraType = dataSource.teraType; this.isTerastallized = dataSource.isTerastallized; this.stellarTypesBoosted = dataSource.stellarTypesBoosted ?? []; @@ -490,12 +476,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (this.formIndex === undefined) { - this.formIndex = globalScene.getSpeciesFormIndex( - species, - this.gender, - this.nature, - this.isPlayer(), - ); + this.formIndex = globalScene.getSpeciesFormIndex(species, this.gender, this.nature, this.isPlayer()); } if (this.shiny === undefined) { @@ -514,19 +495,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.friendship = species.baseFriendship; this.metLevel = level; - this.metBiome = globalScene.currentBattle - ? globalScene.arena.biomeType - : -1; + this.metBiome = globalScene.currentBattle ? globalScene.arena.biomeType : -1; this.metSpecies = species.speciesId; - this.metWave = globalScene.currentBattle - ? globalScene.currentBattle.waveIndex - : -1; + this.metWave = globalScene.currentBattle ? globalScene.currentBattle.waveIndex : -1; this.pokerus = false; if (level > 1) { - const fused = new BooleanHolder( - globalScene.gameMode.isSplicedOnly, - ); + const fused = new BooleanHolder(globalScene.gameMode.isSplicedOnly); if (!fused.value && !this.isPlayer() && !this.hasTrainer()) { globalScene.applyModifier(EnemyFusionChanceModifier, false, fused); } @@ -536,9 +511,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.generateFusionSpecies(); } } - this.luck = - (this.shiny ? this.variant + 1 : 0) + - (this.fusionShiny ? this.fusionVariant + 1 : 0); + this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0); this.fusionLuck = this.luck; this.teraType = randSeedItem(this.getTypes(false, false, true)); @@ -558,15 +531,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!dataSource) { this.calculateStats(); } - } /** - * @param {boolean} useIllusion - Whether we want the fake name or the real name of the Pokemon (for Illusion ability). + * @param useIllusion - Whether we want the fake name or the real name of the Pokemon (for Illusion ability). */ - getNameToRender(useIllusion: boolean = true) { - const name: string = (!useIllusion && this.summonData.illusion) ? this.summonData.illusion.basePokemon.name : this.name; - const nickname: string = (!useIllusion && this.summonData.illusion) ? this.summonData.illusion.basePokemon.nickname : this.nickname; + getNameToRender(useIllusion = true) { + const name: string = + !useIllusion && this.summonData.illusion ? this.summonData.illusion.basePokemon.name : this.name; + const nickname: string = + !useIllusion && this.summonData.illusion ? this.summonData.illusion.basePokemon.nickname : this.nickname; try { if (nickname) { return decodeURIComponent(escape(atob(nickname))); @@ -578,12 +552,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - getPokeball(useIllusion = false){ - if(useIllusion){ - return this.summonData.illusion?.pokeball ?? this.pokeball - } else { - return this.pokeball + getPokeball(useIllusion = false) { + if (useIllusion) { + return this.summonData.illusion?.pokeball ?? this.pokeball; } + return this.pokeball; } init(): void { @@ -645,10 +618,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns `true` if the pokemon is fainted */ public isFainted(checkStatus = false): boolean { - return ( - this.hp <= 0 && - (!checkStatus || this.status?.effect === StatusEffect.FAINT) - ); + return this.hp <= 0 && (!checkStatus || this.status?.effect === StatusEffect.FAINT); } /** @@ -666,11 +636,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ public isAllowedInChallenge(): boolean { const challengeAllowed = new BooleanHolder(true); - applyChallenges( - ChallengeType.POKEMON_IN_BATTLE, - this, - challengeAllowed, - ); + applyChallenges(ChallengeType.POKEMON_IN_BATTLE, this, challengeAllowed); return challengeAllowed.value; } @@ -691,12 +657,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let ret = 0n; ret |= this.gender !== Gender.FEMALE ? DexAttr.MALE : DexAttr.FEMALE; ret |= !this.shiny ? DexAttr.NON_SHINY : DexAttr.SHINY; - ret |= - this.variant >= 2 - ? DexAttr.VARIANT_3 - : this.variant === 1 - ? DexAttr.VARIANT_2 - : DexAttr.DEFAULT_VARIANT; + ret |= this.variant >= 2 ? DexAttr.VARIANT_3 : this.variant === 1 ? DexAttr.VARIANT_2 : DexAttr.DEFAULT_VARIANT; ret |= globalScene.gameData.getFormAttr(this.formIndex); return ret; } @@ -722,17 +683,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** Generate `abilityIndex` based on species and hidden ability if not pre-defined. */ private generateAbilityIndex(): number { - // Roll for hidden ability chance, applying any ability charms for enemy mons - const hiddenAbilityChance = new NumberHolder( - BASE_HIDDEN_ABILITY_CHANCE, - ); + const hiddenAbilityChance = new NumberHolder(BASE_HIDDEN_ABILITY_CHANCE); if (!this.hasTrainer()) { - globalScene.applyModifiers( - HiddenAbilityRateBoosterModifier, - true, - hiddenAbilityChance, - ); + globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); } // If the roll succeeded and we have one, use HA; otherwise pick a random ability @@ -745,8 +699,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.species.ability2 !== this.species.ability1 ? randSeedInt(2) : 0; } - - /** * Generate an illusion of the last pokemon in the party, as other wild pokemon in the area. */ @@ -764,7 +716,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { shiny: this.shiny, variant: this.variant, fusionShiny: this.fusionShiny, - fusionVariant: this.fusionVariant + fusionVariant: this.fusionVariant, }, species: speciesId, formIndex: pokemon.formIndex, @@ -772,7 +724,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { pokeball: pokemon.pokeball, fusionFormIndex: pokemon.fusionFormIndex, fusionSpecies: pokemon.fusionSpecies || undefined, - fusionGender: pokemon.fusionGender + fusionGender: pokemon.fusionGender, }; this.name = pokemon.name; @@ -787,7 +739,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.loadAssets(false, true).then(() => this.playAnim()); this.updateInfo(); } else { - const randomIllusion: PokemonSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, this.level); + const randomIllusion: PokemonSpecies = globalScene.arena.randomSpecies( + globalScene.currentBattle.waveIndex, + this.level, + ); this.summonData.illusion = { basePokemon: { @@ -796,12 +751,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { shiny: this.shiny, variant: this.variant, fusionShiny: this.fusionShiny, - fusionVariant: this.fusionVariant + fusionVariant: this.fusionVariant, }, species: randomIllusion.speciesId, formIndex: randomIllusion.formIndex, gender: this.gender, - pokeball: this.pokeball + pokeball: this.pokeball, }; this.name = randomIllusion.name; @@ -813,15 +768,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { breakIllusion(): boolean { if (!this.summonData.illusion) { return false; - } else { - this.name = this.summonData.illusion.basePokemon.name; - this.nickname = this.summonData.illusion.basePokemon.nickname; - this.shiny = this.summonData.illusion.basePokemon.shiny; - this.variant = this.summonData.illusion.basePokemon.variant; - this.fusionVariant = this.summonData.illusion.basePokemon.fusionVariant; - this.fusionShiny = this.summonData.illusion.basePokemon.fusionShiny; - this.summonData.illusion = null; } + this.name = this.summonData.illusion.basePokemon.name; + this.nickname = this.summonData.illusion.basePokemon.nickname; + this.shiny = this.summonData.illusion.basePokemon.shiny; + this.variant = this.summonData.illusion.basePokemon.variant; + this.fusionVariant = this.summonData.illusion.basePokemon.fusionVariant; + this.fusionShiny = this.summonData.illusion.basePokemon.fusionShiny; + this.summonData.illusion = null; if (this.isOnField()) { globalScene.playSound("PRSFX- Transform"); } @@ -842,9 +796,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { abstract getBattlerIndex(): BattlerIndex; /** -   * @param useIllusion - Whether we want the illusion or not. -   */ - async loadAssets(ignoreOverride = true, useIllusion: boolean = false): Promise { + * @param useIllusion - Whether we want the illusion or not. + */ + async loadAssets(ignoreOverride = true, useIllusion = false): Promise { /** Promises that are loading assets and can be run concurrently. */ const loadPromises: Promise[] = []; // Assets for moves @@ -857,7 +811,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.getGender(useIllusion) === Gender.FEMALE, formIndex, this.isShiny(useIllusion), - this.getVariant(useIllusion) + this.getVariant(useIllusion), ), ); @@ -868,15 +822,24 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ); } if (this.getFusionSpeciesForm()) { - const fusionFormIndex = useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionFormIndex : this.fusionFormIndex; - const fusionShiny = !useIllusion && this.summonData.illusion?.basePokemon ? this.summonData.illusion.basePokemon.fusionShiny : this.fusionShiny; - const fusionVariant = !useIllusion && this.summonData.illusion?.basePokemon ? this.summonData.illusion.basePokemon.fusionVariant : this.fusionVariant; - loadPromises.push(this.getFusionSpeciesForm(false, useIllusion).loadAssets( - this.getFusionGender(false, useIllusion) === Gender.FEMALE, - fusionFormIndex, - fusionShiny, - fusionVariant - )); + const fusionFormIndex = + useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionFormIndex : this.fusionFormIndex; + const fusionShiny = + !useIllusion && this.summonData.illusion?.basePokemon + ? this.summonData.illusion.basePokemon.fusionShiny + : this.fusionShiny; + const fusionVariant = + !useIllusion && this.summonData.illusion?.basePokemon + ? this.summonData.illusion.basePokemon.fusionVariant + : this.fusionVariant; + loadPromises.push( + this.getFusionSpeciesForm(false, useIllusion).loadAssets( + this.getFusionGender(false, useIllusion) === Gender.FEMALE, + fusionFormIndex, + fusionShiny, + fusionVariant, + ), + ); globalScene.loadPokemonAtlas( this.getFusionBattleSpriteKey(true, ignoreOverride), this.getFusionBattleSpriteAtlasPath(true, ignoreOverride), @@ -884,7 +847,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (this.isShiny(true)) { - loadPromises.push(populateVariantColors(this, false, ignoreOverride)) + loadPromises.push(populateVariantColors(this, false, ignoreOverride)); if (this.isPlayer()) { loadPromises.push(populateVariantColors(this, true, ignoreOverride)); } @@ -894,7 +857,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // This must be initiated before we queue loading, otherwise the load could have finished before // we reach the line of code that adds the listener, causing a deadlock. - const waitOnLoadPromise = new Promise(resolve => globalScene.load.once(Phaser.Loader.Events.COMPLETE, resolve)); + const waitOnLoadPromise = new Promise(resolve => + globalScene.load.once(Phaser.Loader.Events.COMPLETE, resolve), + ); if (!globalScene.load.isLoading()) { globalScene.load.start(); @@ -966,11 +931,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param useExpSprite should the experimental sprite be used * @param battleSpritePath the filename of the sprite */ - async populateVariantColorCache( - cacheKey: string, - useExpSprite: boolean, - battleSpritePath: string, - ) { + async populateVariantColorCache(cacheKey: string, useExpSprite: boolean, battleSpritePath: string) { const spritePath = `./images/pokemon/variant/${useExpSprite ? "exp/" : ""}${battleSpritePath}.json`; return globalScene .cachedFetch(spritePath) @@ -989,13 +950,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return res.json(); }) .catch(error => { - return this.fallbackVariantColor( - cacheKey, - spritePath, - useExpSprite, - battleSpritePath, - error, - ); + return this.fallbackVariantColor(cacheKey, spritePath, useExpSprite, battleSpritePath, error); }) .then(c => { if (!isNullOrUndefined(c)) { @@ -1005,10 +960,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getFormKey(): string { - if ( - !this.species.forms.length || - this.species.forms.length <= this.formIndex - ) { + if (!this.species.forms.length || this.species.forms.length <= this.formIndex) { return ""; } return this.species.forms[this.formIndex].formKey; @@ -1018,10 +970,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!this.fusionSpecies) { return null; } - if ( - !this.fusionSpecies.forms.length || - this.fusionSpecies.forms.length <= this.fusionFormIndex - ) { + if (!this.fusionSpecies.forms.length || this.fusionSpecies.forms.length <= this.fusionFormIndex) { return ""; } return this.fusionSpecies.forms[this.fusionFormIndex].formKey; @@ -1033,10 +982,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getBattleSpriteAtlasPath(back?: boolean, ignoreOverride?: boolean): string { - const spriteId = this.getBattleSpriteId(back, ignoreOverride).replace( - /\_{2}/g, - "/", - ); + const spriteId = this.getBattleSpriteId(back, ignoreOverride).replace(/\_{2}/g, "/"); return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`; } @@ -1046,7 +992,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.getGender(ignoreOverride, true) === Gender.FEMALE, formIndex, this.shiny, - this.variant + this.variant, ); } @@ -1062,7 +1008,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { formIndex, this.shiny, this.variant, - back + back, ); } @@ -1071,7 +1017,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.getGender(ignoreOverride) === Gender.FEMALE, this.formIndex, this.summonData.illusion?.basePokemon.shiny ?? this.shiny, - this.summonData.illusion?.basePokemon.variant ?? this.variant + this.summonData.illusion?.basePokemon.variant ?? this.variant, ); } @@ -1085,7 +1031,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.getFusionGender(ignoreOverride, true) === Gender.FEMALE, fusionFormIndex, this.fusionShiny, - this.fusionVariant + this.fusionVariant, ); } @@ -1101,7 +1047,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { fusionFormIndex, this.fusionShiny, this.fusionVariant, - back + back, ); } @@ -1109,55 +1055,67 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return `pkmn__${this.getFusionBattleSpriteId(back, ignoreOverride)}`; } - getFusionBattleSpriteAtlasPath( - back?: boolean, - ignoreOverride?: boolean, - ): string { - return this.getFusionBattleSpriteId(back, ignoreOverride).replace( - /\_{2}/g, - "/", - ); + getFusionBattleSpriteAtlasPath(back?: boolean, ignoreOverride?: boolean): string { + return this.getFusionBattleSpriteId(back, ignoreOverride).replace(/\_{2}/g, "/"); } - getIconAtlasKey(ignoreOverride?: boolean, useIllusion: boolean = true): string { - const formIndex = useIllusion && this.summonData.illusion?.formIndex ? this.summonData.illusion?.formIndex : this.formIndex; - const variant = !useIllusion && !!this.summonData.illusion ? this.summonData.illusion?.basePokemon.variant : this.variant; + getIconAtlasKey(ignoreOverride = false, useIllusion = true): string { + // TODO: confirm the correct behavior here (is it intentional that the check fails if `illusion.formIndex` is `0`?) + const formIndex = + useIllusion && this.summonData.illusion?.formIndex ? this.summonData.illusion.formIndex : this.formIndex; + const variant = + !useIllusion && this.summonData.illusion ? this.summonData.illusion.basePokemon.variant : this.variant; return this.getSpeciesForm(ignoreOverride, useIllusion).getIconAtlasKey( formIndex, this.isBaseShiny(useIllusion), - variant + variant, ); } - getFusionIconAtlasKey(ignoreOverride?: boolean, useIllusion: boolean = true): string { - const fusionFormIndex = useIllusion && this.summonData.illusion?.fusionFormIndex ? this.summonData.illusion?.fusionFormIndex : this.fusionFormIndex; - const fusionVariant = !useIllusion && !!this.summonData.illusion ? this.summonData.illusion?.basePokemon.fusionVariant : this.fusionVariant; + getFusionIconAtlasKey(ignoreOverride = false, useIllusion = true): string { + // TODO: confirm the correct behavior here (is it intentional that the check fails if `illusion.fusionFormIndex` is `0`?) + const fusionFormIndex = + useIllusion && this.summonData.illusion?.fusionFormIndex + ? this.summonData.illusion.fusionFormIndex + : this.fusionFormIndex; + const fusionVariant = + !useIllusion && this.summonData.illusion + ? this.summonData.illusion.basePokemon.fusionVariant + : this.fusionVariant; return this.getFusionSpeciesForm(ignoreOverride, useIllusion).getIconAtlasKey( fusionFormIndex, this.isFusionShiny(), - fusionVariant + fusionVariant, ); } - getIconId(ignoreOverride?: boolean, useIllusion: boolean = true): string { - const formIndex = useIllusion && this.summonData.illusion?.formIndex ? this.summonData.illusion?.formIndex : this.formIndex; - const variant = !useIllusion && !!this.summonData.illusion ? this.summonData.illusion?.basePokemon.variant : this.variant; + getIconId(ignoreOverride?: boolean, useIllusion = true): string { + const formIndex = + useIllusion && this.summonData.illusion?.formIndex ? this.summonData.illusion?.formIndex : this.formIndex; + const variant = + !useIllusion && !!this.summonData.illusion ? this.summonData.illusion?.basePokemon.variant : this.variant; return this.getSpeciesForm(ignoreOverride, useIllusion).getIconId( this.getGender(ignoreOverride, useIllusion) === Gender.FEMALE, formIndex, this.isBaseShiny(), - variant + variant, ); } - getFusionIconId(ignoreOverride?: boolean, useIllusion: boolean = true): string { - const fusionFormIndex = useIllusion && this.summonData.illusion?.fusionFormIndex ? this.summonData.illusion?.fusionFormIndex : this.fusionFormIndex; - const fusionVariant = !useIllusion && !!this.summonData.illusion ? this.summonData.illusion?.basePokemon.fusionVariant : this.fusionVariant; + getFusionIconId(ignoreOverride?: boolean, useIllusion = true): string { + const fusionFormIndex = + useIllusion && this.summonData.illusion?.fusionFormIndex + ? this.summonData.illusion?.fusionFormIndex + : this.fusionFormIndex; + const fusionVariant = + !useIllusion && !!this.summonData.illusion + ? this.summonData.illusion?.basePokemon.fusionVariant + : this.fusionVariant; return this.getFusionSpeciesForm(ignoreOverride, useIllusion).getIconId( this.getFusionGender(ignoreOverride, useIllusion) === Gender.FEMALE, fusionFormIndex, this.isFusionShiny(), - fusionVariant + fusionVariant, ); } @@ -1167,12 +1125,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * This overrides `useIllusion` if `true`. * @param useIllusion - `true` to use the speciesForm of the illusion; default `false`. */ - getSpeciesForm(ignoreOverride: boolean = false, useIllusion: boolean = false): PokemonSpeciesForm { + getSpeciesForm(ignoreOverride = false, useIllusion = false): PokemonSpeciesForm { if (!ignoreOverride && this.summonData.speciesForm) { return this.summonData.speciesForm; } - const species: PokemonSpecies = useIllusion && this.summonData.illusion ? getPokemonSpecies(this.summonData.illusion.species) : this.species; + const species: PokemonSpecies = + useIllusion && this.summonData.illusion ? getPokemonSpecies(this.summonData.illusion.species) : this.species; const formIndex = useIllusion && this.summonData.illusion ? this.summonData.illusion.formIndex : this.formIndex; if (species.forms && species.forms.length > 0) { @@ -1185,17 +1144,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** * @param {boolean} useIllusion - Whether we want the fusionSpeciesForm of the illusion or not. */ - getFusionSpeciesForm(ignoreOverride?: boolean, useIllusion: boolean = false): PokemonSpeciesForm { - const fusionSpecies: PokemonSpecies = useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionSpecies! : this.fusionSpecies!; - const fusionFormIndex = useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionFormIndex! : this.fusionFormIndex; + getFusionSpeciesForm(ignoreOverride?: boolean, useIllusion = false): PokemonSpeciesForm { + const fusionSpecies: PokemonSpecies = + useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionSpecies! : this.fusionSpecies!; + const fusionFormIndex = + useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionFormIndex! : this.fusionFormIndex; if (!ignoreOverride && this.summonData.fusionSpeciesForm) { return this.summonData.fusionSpeciesForm; } - if ( - !fusionSpecies?.forms?.length || - fusionFormIndex >= fusionSpecies?.forms.length - ) { + if (!fusionSpecies?.forms?.length || fusionFormIndex >= fusionSpecies?.forms.length) { return fusionSpecies; } return fusionSpecies?.forms[fusionFormIndex]; @@ -1206,9 +1164,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getTintSprite(): Phaser.GameObjects.Sprite | null { - return !this.maskEnabled - ? (this.getAt(1) as Phaser.GameObjects.Sprite) - : this.maskSprite; + return !this.maskEnabled ? (this.getAt(1) as Phaser.GameObjects.Sprite) : this.maskSprite; } getSpriteScale(): number { @@ -1289,20 +1245,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param animConfig {@linkcode String} to pass to {@linkcode Phaser.GameObjects.Sprite.play} * @returns true if the sprite was able to be animated */ - tryPlaySprite( - sprite: Phaser.GameObjects.Sprite, - tintSprite: Phaser.GameObjects.Sprite, - key: string, - ): boolean { + tryPlaySprite(sprite: Phaser.GameObjects.Sprite, tintSprite: Phaser.GameObjects.Sprite, key: string): boolean { // Catch errors when trying to play an animation that doesn't exist try { sprite.play(key); tintSprite.play(key); } catch (error: unknown) { - console.error( - `Couldn't play animation for '${key}'!\nIs the image for this Pokemon missing?\n`, - error, - ); + console.error(`Couldn't play animation for '${key}'!\nIs the image for this Pokemon missing?\n`, error); return false; } @@ -1311,11 +1260,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } playAnim(): void { - this.tryPlaySprite( - this.getSprite(), - this.getTintSprite()!, - this.getBattleSpriteKey(), - ); // TODO: is the bag correct? + this.tryPlaySprite(this.getSprite(), this.getTintSprite()!, this.getBattleSpriteKey()); // TODO: is the bang correct? } getFieldPositionOffset(): [number, number] { @@ -1353,30 +1298,23 @@ 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 instanceof MoveEffectPhase && currentPhase.getPokemon() === this) { return false; } return true; - } else { - return false; } + return false; } /** If this Pokemon has a Substitute on the field, removes its sprite from the field. */ destroySubstitute(): void { const substitute = this.getTag(SubstituteTag); - if (substitute && substitute.sprite) { + if (substitute?.sprite) { substitute.sprite.destroy(); } } - setFieldPosition( - fieldPosition: FieldPosition, - duration?: number, - ): Promise { + setFieldPosition(fieldPosition: FieldPosition, duration?: number): Promise { return new Promise(resolve => { if (fieldPosition === this.fieldPosition) { resolve(); @@ -1441,10 +1379,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns the numeric value of the desired {@linkcode Stat} */ getStat(stat: PermanentStat, bypassSummonData = true): number { - if ( - !bypassSummonData && - this.summonData.stats[stat] !== 0 - ) { + if (!bypassSummonData && this.summonData.stats[stat] !== 0) { return this.summonData.stats[stat]; } return this.stats[stat]; @@ -1518,9 +1453,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const critBoostTag = source.getTag(CritBoostTag); if (critBoostTag) { if (critBoostTag instanceof DragonCheerTag) { - critStage.value += critBoostTag.typesOnAdd.includes(PokemonType.DRAGON) - ? 2 - : 1; + critStage.value += critBoostTag.typesOnAdd.includes(PokemonType.DRAGON) ? 2 : 1; } else { critStage.value += 2; } @@ -1571,57 +1504,37 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ): number { const statValue = new NumberHolder(this.getStat(stat, false)); if (!ignoreHeldItems) { - globalScene.applyModifiers( - StatBoosterModifier, - this.isPlayer(), - this, - stat, - statValue, - ); + globalScene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statValue); } // 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, ally, stat, statValue, simulated, this, move?.hasFlag(MoveFlags.IGNORE_ABILITIES) || ignoreAllyAbility); + applyAllyStatMultiplierAbAttrs( + AllyStatMultiplierAbAttr, + ally, + stat, + statValue, + simulated, + this, + move?.hasFlag(MoveFlags.IGNORE_ABILITIES) || ignoreAllyAbility, + ); } let ret = statValue.value * - this.getStatStageMultiplier( - stat, - opponent, - move, - ignoreOppAbility, - isCritical, - simulated, - ignoreHeldItems, - ); + this.getStatStageMultiplier(stat, opponent, move, ignoreOppAbility, isCritical, simulated, ignoreHeldItems); switch (stat) { case Stat.ATK: @@ -1630,31 +1543,23 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } break; case Stat.DEF: - if ( - this.isOfType(PokemonType.ICE) && - globalScene.arena.weather?.weatherType === WeatherType.SNOW - ) { + if (this.isOfType(PokemonType.ICE) && globalScene.arena.weather?.weatherType === WeatherType.SNOW) { ret *= 1.5; } break; case Stat.SPATK: break; case Stat.SPDEF: - if ( - this.isOfType(PokemonType.ROCK) && - globalScene.arena.weather?.weatherType === WeatherType.SANDSTORM - ) { + if (this.isOfType(PokemonType.ROCK) && globalScene.arena.weather?.weatherType === WeatherType.SANDSTORM) { ret *= 1.5; } break; - case Stat.SPD: + case Stat.SPD: { const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (globalScene.arena.getTagOnSide(ArenaTagType.TAILWIND, side)) { ret *= 2; } - if ( - globalScene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, side) - ) { + if (globalScene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, side)) { ret >>= 2; } @@ -1664,19 +1569,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.status && this.status.effect === StatusEffect.PARALYSIS) { ret >>= 1; } - if ( - this.getTag(BattlerTagType.UNBURDEN) && - this.hasAbility(Abilities.UNBURDEN) - ) { + if (this.getTag(BattlerTagType.UNBURDEN) && this.hasAbility(Abilities.UNBURDEN)) { ret *= 2; } break; + } } const highestStatBoost = this.findTag( - t => - t instanceof HighestStatBoostTag && - (t as HighestStatBoostTag).stat === stat, + t => t instanceof HighestStatBoostTag && (t as HighestStatBoostTag).stat === stat, ) as HighestStatBoostTag; if (highestStatBoost) { ret *= highestStatBoost.multiplier; @@ -1694,18 +1595,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const baseStats = this.calculateBaseStats(); // Using base stats, calculate and store stats one by one for (const s of PERMANENT_STATS) { - const statHolder = new NumberHolder( - Math.floor((2 * baseStats[s] + this.ivs[s]) * this.level * 0.01), - ); + const statHolder = new NumberHolder(Math.floor((2 * baseStats[s] + this.ivs[s]) * this.level * 0.01)); if (s === Stat.HP) { statHolder.value = statHolder.value + this.level + 10; - globalScene.applyModifier( - PokemonIncrementingStatModifier, - this.isPlayer(), - this, - s, - statHolder, - ); + globalScene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder); if (this.hasAbility(Abilities.WONDER_GUARD, false, true)) { statHolder.value = 1; } @@ -1719,37 +1612,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } else { statHolder.value += 5; - const natureStatMultiplier = new NumberHolder( - getNatureStatMultiplier(this.getNature(), s), - ); - globalScene.applyModifier( - PokemonNatureWeightModifier, - this.isPlayer(), - this, - natureStatMultiplier, - ); + const natureStatMultiplier = new NumberHolder(getNatureStatMultiplier(this.getNature(), s)); + globalScene.applyModifier(PokemonNatureWeightModifier, this.isPlayer(), this, natureStatMultiplier); if (natureStatMultiplier.value !== 1) { statHolder.value = Math.max( - Math[natureStatMultiplier.value > 1 ? "ceil" : "floor"]( - statHolder.value * natureStatMultiplier.value, - ), + Math[natureStatMultiplier.value > 1 ? "ceil" : "floor"](statHolder.value * natureStatMultiplier.value), 1, ); } - globalScene.applyModifier( - PokemonIncrementingStatModifier, - this.isPlayer(), - this, - s, - statHolder, - ); + globalScene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder); } - statHolder.value = Phaser.Math.Clamp( - statHolder.value, - 1, - Number.MAX_SAFE_INTEGER, - ); + statHolder.value = Phaser.Math.Clamp(statHolder.value, 1, Number.MAX_SAFE_INTEGER); this.setStat(s, statHolder.value); } @@ -1757,32 +1631,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { calculateBaseStats(): number[] { const baseStats = this.getSpeciesForm(true).baseStats.slice(0); - applyChallenges( - ChallengeType.FLIP_STAT, - this, - baseStats, - ); + applyChallenges(ChallengeType.FLIP_STAT, this, baseStats); // Shuckle Juice - globalScene.applyModifiers( - PokemonBaseStatTotalModifier, - this.isPlayer(), - this, - baseStats, - ); + globalScene.applyModifiers(PokemonBaseStatTotalModifier, this.isPlayer(), this, baseStats); // Old Gateau - globalScene.applyModifiers( - PokemonBaseStatFlatModifier, - this.isPlayer(), - this, - baseStats, - ); + globalScene.applyModifiers(PokemonBaseStatFlatModifier, this.isPlayer(), this, baseStats); if (this.isFusion()) { const fusionBaseStats = this.getFusionSpeciesForm(true).baseStats; - applyChallenges( - ChallengeType.FLIP_STAT, - this, - fusionBaseStats, - ); + applyChallenges(ChallengeType.FLIP_STAT, this, fusionBaseStats); for (const s of PERMANENT_STATS) { baseStats[s] = Math.ceil((baseStats[s] + fusionBaseStats[s]) / 2); @@ -1793,20 +1649,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } // Vitamins - globalScene.applyModifiers( - BaseStatModifier, - this.isPlayer(), - this, - baseStats, - ); + globalScene.applyModifiers(BaseStatModifier, this.isPlayer(), this, baseStats); return baseStats; } getNature(): Nature { - return this.customPokemonData.nature !== -1 - ? this.customPokemonData.nature - : this.nature; + return this.customPokemonData.nature !== -1 ? this.customPokemonData.nature : this.nature; } setNature(nature: Nature): void { @@ -1841,9 +1690,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getHpRatio(precise = false): number { - return precise - ? this.hp / this.getMaxHp() - : Math.round((this.hp / this.getMaxHp()) * 100) / 100; + return precise ? this.hp / this.getMaxHp() : Math.round((this.hp / this.getMaxHp()) * 100) / 100; } generateGender(): void { @@ -1860,54 +1707,56 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * @param {boolean} useIllusion - Whether we want the fake or real gender (illusion ability). + * @param useIllusion - Whether we want the fake or real gender (illusion ability). */ - getGender(ignoreOverride?: boolean, useIllusion: boolean = false): Gender { + getGender(ignoreOverride?: boolean, useIllusion = false): Gender { if (useIllusion && this.summonData.illusion) { return this.summonData.illusion.gender; - } else if (!ignoreOverride && !isNullOrUndefined(this.summonData.gender)) { + } + if (!ignoreOverride && !isNullOrUndefined(this.summonData.gender)) { return this.summonData.gender; } return this.gender; } /** - * @param {boolean} useIllusion - Whether we want the fake or real gender (illusion ability). + * @param useIllusion - Whether we want the fake or real gender (illusion ability). */ - getFusionGender(ignoreOverride?: boolean, useIllusion: boolean = false): Gender { + getFusionGender(ignoreOverride?: boolean, useIllusion = false): Gender { if (useIllusion && this.summonData.illusion?.fusionGender) { return this.summonData.illusion.fusionGender; - } else if (!ignoreOverride && !isNullOrUndefined(this.summonData.fusionGender)) { + } + if (!ignoreOverride && !isNullOrUndefined(this.summonData.fusionGender)) { return this.summonData.fusionGender; } return this.fusionGender; } /** - * @param {boolean} useIllusion - Whether we want the fake or real shininess (illusion ability). + * @param useIllusion - Whether we want the fake or real shininess (illusion ability). */ - isShiny(useIllusion: boolean = false): boolean { + isShiny(useIllusion = false): boolean { if (!useIllusion && this.summonData.illusion) { - return this.summonData.illusion.basePokemon?.shiny || (this.summonData.illusion.fusionSpecies && this.summonData.illusion.basePokemon?.fusionShiny) || false; - } else { - return this.shiny || (this.isFusion(useIllusion) && this.fusionShiny); + return !!( + this.summonData.illusion.basePokemon?.shiny || + (this.summonData.illusion.fusionSpecies && this.summonData.illusion.basePokemon?.fusionShiny) + ); } + return this.shiny || (this.isFusion(useIllusion) && this.fusionShiny); } - isBaseShiny(useIllusion: boolean = false){ + isBaseShiny(useIllusion = false) { if (!useIllusion && this.summonData.illusion) { return !!this.summonData.illusion.basePokemon?.shiny; - } else { - return this.shiny; } + return this.shiny; } - isFusionShiny(useIllusion: boolean = false){ + isFusionShiny(useIllusion = false) { if (!useIllusion && this.summonData.illusion) { return !!this.summonData.illusion.basePokemon?.fusionShiny; - } else { - return this.isFusion(useIllusion) && this.fusionShiny; } + return this.isFusion(useIllusion) && this.fusionShiny; } /** @@ -1915,34 +1764,33 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param useIllusion - Whether we want the fake or real shininess (illusion ability). * @returns `true` if the {@linkcode Pokemon} is shiny and the fusion is shiny as well, `false` otherwise */ - isDoubleShiny(useIllusion: boolean = false): boolean { + isDoubleShiny(useIllusion = false): boolean { if (!useIllusion && this.summonData.illusion?.basePokemon) { - return this.isFusion(false) && this.summonData.illusion.basePokemon.shiny && this.summonData.illusion.basePokemon.fusionShiny; - } else { - return this.isFusion(useIllusion) && this.shiny && this.fusionShiny; + return ( + this.isFusion(false) && + this.summonData.illusion.basePokemon.shiny && + this.summonData.illusion.basePokemon.fusionShiny + ); } + return this.isFusion(useIllusion) && this.shiny && this.fusionShiny; } /** - * @param {boolean} useIllusion - Whether we want the fake or real variant (illusion ability). + * @param useIllusion - Whether we want the fake or real variant (illusion ability). */ - getVariant(useIllusion: boolean = false): Variant { + getVariant(useIllusion = false): Variant { if (!useIllusion && this.summonData.illusion) { return !this.isFusion(false) - ? this.summonData.illusion.basePokemon!.variant - : Math.max(this.variant, this.fusionVariant) as Variant; - } else { - return !this.isFusion(true) - ? this.variant - : Math.max(this.variant, this.fusionVariant) as Variant; + ? this.summonData.illusion.basePokemon!.variant + : (Math.max(this.variant, this.fusionVariant) as Variant); } + return !this.isFusion(true) ? this.variant : (Math.max(this.variant, this.fusionVariant) as Variant); } getBaseVariant(doubleShiny: boolean): Variant { if (doubleShiny) { return this.summonData.illusion?.basePokemon?.variant ?? this.variant; } - return this.getVariant(); } @@ -1950,7 +1798,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.luck + (this.isFusion() ? this.fusionLuck : 0); } - isFusion(useIllusion: boolean = false): boolean { + isFusion(useIllusion = false): boolean { if (useIllusion && this.summonData.illusion) { return !!this.summonData.illusion.fusionSpecies; } @@ -1958,12 +1806,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * @param {boolean} useIllusion - Whether we want the fake name or the real name of the Pokemon (for Illusion ability). + * @param useIllusion - Whether we want the fake name or the real name of the Pokemon (for Illusion ability). */ - getName(useIllusion: boolean = false): string { - return (!useIllusion && this.summonData.illusion?.basePokemon) - ? this.summonData.illusion.basePokemon.name - : this.name; + getName(useIllusion = false): string { + return !useIllusion && this.summonData.illusion?.basePokemon + ? this.summonData.illusion.basePokemon.name + : this.name; } /** @@ -1983,22 +1831,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ hasSpecies(species: Species, formKey?: string): boolean { if (isNullOrUndefined(formKey)) { - return ( - this.species.speciesId === species || - this.fusionSpecies?.speciesId === species - ); + return this.species.speciesId === species || this.fusionSpecies?.speciesId === species; } - return (this.species.speciesId === species && this.getFormKey() === formKey) || (this.fusionSpecies?.speciesId === species && this.getFusionFormKey() === formKey); + return ( + (this.species.speciesId === species && this.getFormKey() === formKey) || + (this.fusionSpecies?.speciesId === species && this.getFusionFormKey() === formKey) + ); } abstract isBoss(): boolean; getMoveset(ignoreOverride?: boolean): PokemonMove[] { - const ret = - !ignoreOverride && this.summonData.moveset - ? this.summonData.moveset - : this.moveset; + const ret = !ignoreOverride && this.summonData.moveset ? this.summonData.moveset : this.moveset; // Overrides moveset based on arrays specified in overrides.ts let overrideArray: Moves | Array = this.isPlayer() @@ -2013,10 +1858,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } overrideArray.forEach((move: Moves, index: number) => { const ppUsed = this.moveset[index]?.ppUsed ?? 0; - this.moveset[index] = new PokemonMove( - move, - Math.min(ppUsed, allMoves[move].pp), - ); + this.moveset[index] = new PokemonMove(move, Math.min(ppUsed, allMoves[move].pp)); }); } @@ -2033,9 +1875,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getUnlockedEggMoves(): Moves[] { const moves: Moves[] = []; const species = - this.metSpecies in speciesEggMoves - ? this.metSpecies - : this.getSpeciesForm(true).getRootSpeciesId(true); + this.metSpecies in speciesEggMoves ? this.metSpecies : this.getSpeciesForm(true).getRootSpeciesId(true); if (species in speciesEggMoves) { for (let i = 0; i < 4; i++) { if (globalScene.gameData.starterData[species].eggMoves & (1 << i)) { @@ -2057,17 +1897,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ public getLearnableLevelMoves(): Moves[] { let levelMoves = this.getLevelMoves(1, true, false, true).map(lm => lm[1]); - if ( - this.metBiome === -1 && - !globalScene.gameMode.isFreshStartChallenge() && - !globalScene.gameMode.isDaily - ) { + if (this.metBiome === -1 && !globalScene.gameMode.isFreshStartChallenge() && !globalScene.gameMode.isDaily) { levelMoves = this.getUnlockedEggMoves().concat(levelMoves); } if (Array.isArray(this.usedTMs) && this.usedTMs.length > 0) { - levelMoves = this.usedTMs - .filter(m => !levelMoves.includes(m)) - .concat(levelMoves); + levelMoves = this.usedTMs.filter(m => !levelMoves.includes(m)).concat(levelMoves); } levelMoves = levelMoves.filter(lm => !this.moveset.some(m => m.moveId === lm)); return levelMoves; @@ -2083,9 +1917,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ public getTypes( includeTeraType = false, - forDefend: boolean = false, - ignoreOverride: boolean = false, - useIllusion: boolean = false + forDefend = false, + ignoreOverride = false, + useIllusion = false, ): PokemonType[] { const types: PokemonType[] = []; @@ -2100,7 +1934,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } if (!types.length || !includeTeraType) { - if ( !ignoreOverride && this.summonData.types && @@ -2145,10 +1978,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { secondType = fusionType1; } - if ( - secondType === PokemonType.UNKNOWN && - isNullOrUndefined(fusionType2) - ) { + if (secondType === PokemonType.UNKNOWN && isNullOrUndefined(fusionType2)) { // If second pokemon was monotype and shared its primary type secondType = customTypes && @@ -2187,11 +2017,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } // check type added to Pokemon from moves like Forest's Curse or Trick Or Treat - if ( - !ignoreOverride && - this.summonData.addedType && - !types.includes(this.summonData.addedType) - ) { + if (!ignoreOverride && this.summonData.addedType && !types.includes(this.summonData.addedType)) { types.push(this.summonData.addedType); } @@ -2211,15 +2037,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param ignoreOverride - If `true`, ignore ability changing effects; Default: `false` * @returns `true` if the Pokemon's type matches */ - public isOfType( - type: PokemonType, - includeTeraType = true, - forDefend = false, - ignoreOverride = false, - ): boolean { - return this.getTypes(includeTeraType, forDefend, ignoreOverride).some( - t => t === type, - ); + public isOfType(type: PokemonType, includeTeraType = true, forDefend = false, ignoreOverride = false): boolean { + return this.getTypes(includeTeraType, forDefend, ignoreOverride).some(t => t === type); } /** @@ -2241,27 +2060,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return allAbilities[Overrides.OPP_ABILITY_OVERRIDE]; } if (this.isFusion()) { - if ( - !isNullOrUndefined(this.fusionCustomPokemonData?.ability) && - this.fusionCustomPokemonData.ability !== -1 - ) { + if (!isNullOrUndefined(this.fusionCustomPokemonData?.ability) && this.fusionCustomPokemonData.ability !== -1) { return allAbilities[this.fusionCustomPokemonData.ability]; } - return allAbilities[ - this.getFusionSpeciesForm(ignoreOverride).getAbility( - this.fusionAbilityIndex, - ) - ]; + return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)]; } - if ( - !isNullOrUndefined(this.customPokemonData.ability) && - this.customPokemonData.ability !== -1 - ) { + if (!isNullOrUndefined(this.customPokemonData.ability) && this.customPokemonData.ability !== -1) { return allAbilities[this.customPokemonData.ability]; } - let abilityId = this.getSpeciesForm(ignoreOverride).getAbility( - this.abilityIndex, - ); + let abilityId = this.getSpeciesForm(ignoreOverride).getAbility(this.abilityIndex); if (abilityId === Abilities.NONE) { abilityId = this.species.ability1; } @@ -2282,10 +2089,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) { return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE]; } - if ( - !isNullOrUndefined(this.customPokemonData.passive) && - this.customPokemonData.passive !== -1 - ) { + if (!isNullOrUndefined(this.customPokemonData.passive) && this.customPokemonData.passive !== -1) { return allAbilities[this.customPokemonData.passive]; } @@ -2309,9 +2113,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const abilityAttrs: T[] = []; if (!canApply || this.canApplyAbility()) { - abilityAttrs.push( - ...this.getAbility(ignoreOverride).getAttrs(attrType), - ); + abilityAttrs.push(...this.getAbility(ignoreOverride).getAttrs(attrType)); } if (!canApply || this.canApplyAbility(true)) { @@ -2361,11 +2163,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return false; } if ( - ((Overrides.PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE || - Overrides.HAS_PASSIVE_ABILITY_OVERRIDE) && + ((Overrides.PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE || Overrides.HAS_PASSIVE_ABILITY_OVERRIDE) && this.isPlayer()) || - ((Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE || - Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE) && + ((Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE || Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE) && !this.isPlayer()) ) { return true; @@ -2402,35 +2202,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return false; } const arena = globalScene?.arena; - if ( - arena.ignoreAbilities && - arena.ignoringEffectSource !== this.getBattlerIndex() && - ability.isIgnorable - ) { + if (arena.ignoreAbilities && arena.ignoringEffectSource !== this.getBattlerIndex() && ability.isIgnorable) { return false; } - if ( - this.summonData.abilitySuppressed && - ability.isSuppressable - ) { + if (this.summonData.abilitySuppressed && ability.isSuppressable) { return false; } - const suppressAbilitiesTag = arena.getTag( - ArenaTagType.NEUTRALIZING_GAS, - ) as SuppressAbilitiesTag; + const suppressAbilitiesTag = arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag; const suppressOffField = ability.hasAttr(PreSummonAbAttr); - if ( - (this.isOnField() || suppressOffField) && - suppressAbilitiesTag && - !suppressAbilitiesTag.isBeingRemoved() - ) { - const thisAbilitySuppressing = ability.hasAttr( - PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, - ); - const hasSuppressingAbility = this.hasAbilityWithAttr( - PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, - false, - ); + if ((this.isOnField() || suppressOffField) && suppressAbilitiesTag && !suppressAbilitiesTag.isBeingRemoved()) { + 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) @@ -2442,10 +2224,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return false; } } - return ( - (this.hp > 0 || ability.isBypassFaint) && - !ability.conditions.find(condition => !condition(this)) - ); + return (this.hp > 0 || ability.isBypassFaint) && !ability.conditions.find(condition => !condition(this)); } /** @@ -2457,22 +2236,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param ignoreOverride Whether to ignore ability changing effects; default `false` * @returns `true` if the ability is present and active */ - public hasAbility( - ability: Abilities, - canApply = true, - ignoreOverride = false, - ): boolean { - if ( - this.getAbility(ignoreOverride).id === ability && - (!canApply || this.canApplyAbility()) - ) { + public hasAbility(ability: Abilities, canApply = true, ignoreOverride = false): boolean { + if (this.getAbility(ignoreOverride).id === ability && (!canApply || this.canApplyAbility())) { return true; } - if ( - this.getPassiveAbility().id === ability && - this.hasPassive() && - (!canApply || this.canApplyAbility(true)) - ) { + if (this.getPassiveAbility().id === ability && this.hasPassive() && (!canApply || this.canApplyAbility(true))) { return true; } return false; @@ -2488,22 +2256,11 @@ 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 { - if ( - (!canApply || this.canApplyAbility()) && - this.getAbility(ignoreOverride).hasAttr(attrType) - ) { + public hasAbilityWithAttr(attrType: Constructor, canApply = true, ignoreOverride = false): boolean { + if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).hasAttr(attrType)) { return true; } - if ( - this.hasPassive() && - (!canApply || this.canApplyAbility(true)) && - this.getPassiveAbility().hasAttr(attrType) - ) { + if (this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().hasAttr(attrType)) { return true; } return false; @@ -2536,10 +2293,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return PokemonType.STELLAR; } if (this.hasSpecies(Species.OGERPON)) { - const ogerponForm = - this.species.speciesId === Species.OGERPON - ? this.formIndex - : this.fusionFormIndex; + const ogerponForm = this.species.speciesId === Species.OGERPON ? this.formIndex : this.fusionFormIndex; switch (ogerponForm) { case 0: case 4: @@ -2579,10 +2333,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param simulated - If `true`, applies abilities via simulated calls. * @returns `true` if the pokemon is trapped */ - public isTrapped( - trappedAbMessages: string[] = [], - simulated = true, - ): boolean { + public isTrapped(trappedAbMessages: string[] = [], simulated = true): boolean { const commandedTag = this.getTag(BattlerTagType.COMMANDED); if (commandedTag?.getSourcePokemon()?.isActive(true)) { return true; @@ -2597,22 +2348,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * Contains opposing Pokemon (Enemy/Player Pokemon) depending on perspective * Afterwards, it filters out Pokemon that have been switched out of the field so trapped abilities/moves do not trigger */ - const opposingFieldUnfiltered = this.isPlayer() - ? globalScene.getEnemyField() - : globalScene.getPlayerField(); - const opposingField = opposingFieldUnfiltered.filter( - enemyPkm => enemyPkm.switchOutStatus === false, - ); + const opposingFieldUnfiltered = this.isPlayer() ? globalScene.getEnemyField() : globalScene.getPlayerField(); + 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; @@ -2634,26 +2374,19 @@ 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 - if (this.isTerastallized && (move.id === Moves.TERA_BLAST || move.id === Moves.TERA_STARSTORM && moveTypeHolder.value === PokemonType.STELLAR)) { + if ( + this.isTerastallized && + (move.id === Moves.TERA_BLAST || + (move.id === Moves.TERA_STARSTORM && moveTypeHolder.value === PokemonType.STELLAR)) + ) { return moveTypeHolder.value as PokemonType; } - globalScene.arena.applyTags( - ArenaTagType.ION_DELUGE, - simulated, - moveTypeHolder, - ); + globalScene.arena.applyTags(ArenaTagType.ION_DELUGE, simulated, moveTypeHolder); if (this.getTag(BattlerTagType.ELECTRIFIED)) { moveTypeHolder.value = PokemonType.ELECTRIC; } @@ -2678,7 +2411,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ignoreAbility = false, simulated = true, cancelled?: BooleanHolder, - useIllusion: boolean = false + useIllusion = false, ): TypeDamageMultiplier { if (!isNullOrUndefined(this.turnData?.moveEffectiveness)) { return this.turnData?.moveEffectiveness; @@ -2690,28 +2423,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const moveType = source.getMoveType(move); const typeMultiplier = new NumberHolder( - move.category !== MoveCategory.STATUS || - move.hasAttr(RespectAttackTypeImmunityAttr) - ? this.getAttackTypeEffectiveness( - moveType, - source, - false, - simulated, - move, - useIllusion - ) - : 1); - - applyMoveAttrs( - VariableMoveTypeMultiplierAttr, - source, - this, - move, - typeMultiplier, + move.category !== MoveCategory.STATUS || move.hasAttr(RespectAttackTypeImmunityAttr) + ? this.getAttackTypeEffectiveness(moveType, source, false, simulated, move, useIllusion) + : 1, ); - if ( - this.getTypes(true, true).find(t => move.isTypeImmune(source, this, t)) - ) { + + applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); + if (this.getTypes(true, true).find(t => move.isTypeImmune(source, this, t))) { typeMultiplier.value = 0; } @@ -2721,52 +2439,23 @@ 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(); + const defendingSidePlayField = this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); defendingSidePlayField.forEach(p => - applyPreDefendAbAttrs( - FieldPriorityMoveImmunityAbAttr, - p, - source, - move, - cancelledHolder, - ), + applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelledHolder), ); } } - const immuneTags = this.findTags( - tag => tag instanceof TypeImmuneTag && tag.immuneType === moveType, - ); + 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; } @@ -2774,27 +2463,14 @@ 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) - ) { + if (move.category === MoveCategory.STATUS && move.hitsSubstitute(source, this)) { typeMultiplier.value = 0; } - return ( - !cancelledHolder.value ? typeMultiplier.value : 0 - ) as TypeDamageMultiplier; + return (!cancelledHolder.value ? typeMultiplier.value : 0) as TypeDamageMultiplier; } /** @@ -2810,10 +2486,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getAttackTypeEffectiveness( moveType: PokemonType, source?: Pokemon, - ignoreStrongWinds: boolean = false, - simulated: boolean = true, + ignoreStrongWinds = false, + simulated = true, move?: Move, - useIllusion: boolean = false + useIllusion = false, ): TypeDamageMultiplier { if (moveType === PokemonType.STELLAR) { return this.isTerastallized ? 2 : 1; @@ -2823,10 +2499,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Handle flying v ground type immunity without removing flying type so effective types are still effective // Related to https://github.com/pagefaultgames/pokerogue/issues/524 - if ( - moveType === PokemonType.GROUND && - (this.isGrounded() || arena.hasTag(ArenaTagType.GRAVITY)) - ) { + if (moveType === PokemonType.GROUND && (this.isGrounded() || arena.hasTag(ArenaTagType.GRAVITY))) { const flyingIndex = types.indexOf(PokemonType.FLYING); if (flyingIndex > -1) { types.splice(flyingIndex, 1); @@ -2835,37 +2508,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let multiplier = types .map(defType => { - const multiplier = new NumberHolder( - getTypeDamageMultiplier(moveType, defType), - ); - applyChallenges( - ChallengeType.TYPE_EFFECTIVENESS, - multiplier, - ); + 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); - 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) { @@ -2873,9 +2524,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - const exposedTags = this.findTags( - tag => tag instanceof ExposedTag, - ) as ExposedTag[]; + const exposedTags = this.findTags(tag => tag instanceof ExposedTag) as ExposedTag[]; if (exposedTags.some(t => t.ignoreImmunity(defType, moveType))) { if (multiplier.value === 0) { return 1; @@ -2886,13 +2535,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }) .reduce((acc, cur) => acc * cur, 1) as TypeDamageMultiplier; - const typeMultiplierAgainstFlying = new NumberHolder( - getTypeDamageMultiplier(moveType, PokemonType.FLYING), - ); - applyChallenges( - ChallengeType.TYPE_EFFECTIVENESS, - typeMultiplierAgainstFlying, - ); + const typeMultiplierAgainstFlying = new NumberHolder(getTypeDamageMultiplier(moveType, PokemonType.FLYING)); + applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, typeMultiplierAgainstFlying); // Handle strong winds lowering effectiveness of types super effective against pure flying if ( !ignoreStrongWinds && @@ -2921,27 +2565,25 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const enemyTypes = opponent.getTypes(true, true, false, true); /** Is this Pokemon faster than the opponent? */ const outspeed = - (this.isActive(true) - ? this.getEffectiveStat(Stat.SPD, opponent) - : this.getStat(Stat.SPD, false)) >= + (this.isActive(true) ? this.getEffectiveStat(Stat.SPD, opponent) : this.getStat(Stat.SPD, false)) >= opponent.getEffectiveStat(Stat.SPD, this); /** * Based on how effective this Pokemon's types are offensively against the opponent's types. * This score is increased by 25 percent if this Pokemon is faster than the opponent. */ - let atkScore = opponent.getAttackTypeEffectiveness(types[0], this, false, true, undefined, true) * (outspeed ? 1.25 : 1); + let atkScore = + opponent.getAttackTypeEffectiveness(types[0], this, false, true, undefined, true) * (outspeed ? 1.25 : 1); /** * Based on how effectively this Pokemon defends against the opponent's types. * This score cannot be higher than 4. */ - let defScore = - 1 / - Math.max(this.getAttackTypeEffectiveness(enemyTypes[0], opponent), 0.25); + let defScore = 1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[0], opponent), 0.25); if (types.length > 1) { atkScore *= opponent.getAttackTypeEffectiveness(types[1], this); } if (enemyTypes.length > 1) { - defScore *= (1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[1], opponent, false, false, undefined, true), 0.25)); + defScore *= + 1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[1], opponent, false, false, undefined, true), 0.25); } /** * Based on this Pokemon's HP ratio compared to that of the opponent. @@ -2962,38 +2604,26 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if ( !e.item && this.level >= e.level && - (isNullOrUndefined(e.preFormKey) || - this.getFormKey() === e.preFormKey) + (isNullOrUndefined(e.preFormKey) || this.getFormKey() === e.preFormKey) ) { - if ( - e.condition === null || - (e.condition as SpeciesEvolutionCondition).predicate(this) - ) { + if (e.condition === null || (e.condition as SpeciesEvolutionCondition).predicate(this)) { return e; } } } } - if ( - this.isFusion() && - this.fusionSpecies && - pokemonEvolutions.hasOwnProperty(this.fusionSpecies.speciesId) - ) { - const fusionEvolutions = pokemonEvolutions[ - this.fusionSpecies.speciesId - ].map(e => new FusionSpeciesFormEvolution(this.species.speciesId, e)); + if (this.isFusion() && this.fusionSpecies && pokemonEvolutions.hasOwnProperty(this.fusionSpecies.speciesId)) { + const fusionEvolutions = pokemonEvolutions[this.fusionSpecies.speciesId].map( + 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) + (isNullOrUndefined(fe.preFormKey) || this.getFusionFormKey() === fe.preFormKey) ) { - if ( - fe.condition === null || - (fe.condition as SpeciesEvolutionCondition).predicate(this) - ) { + if (fe.condition === null || (fe.condition as SpeciesEvolutionCondition).predicate(this)) { return fe; } } @@ -3023,10 +2653,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!startingLevel) { startingLevel = this.level; } - if ( - learnSituation === LearnMoveSituation.EVOLUTION_FUSED && - this.fusionSpecies - ) { + if (learnSituation === LearnMoveSituation.EVOLUTION_FUSED && this.fusionSpecies) { // For fusion evolutions, get ONLY the moves of the component mon that evolved levelMoves = this.getFusionSpeciesForm(true) .getLevelMoves() @@ -3046,10 +2673,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ); for (let e = 0; e < evolutionChain.length; e++) { // TODO: Might need to pass specific form index in simulated evolution chain - const speciesLevelMoves = getPokemonSpeciesForm( - evolutionChain[e][0], - this.formIndex, - ).getLevelMoves(); + const speciesLevelMoves = getPokemonSpeciesForm(evolutionChain[e][0], this.formIndex).getLevelMoves(); if (includeRelearnerMoves) { levelMoves.push(...speciesLevelMoves); } else { @@ -3057,9 +2681,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ...speciesLevelMoves.filter( lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || - ((!e || lm[0] > 1) && - (e === evolutionChain.length - 1 || - lm[0] <= evolutionChain[e + 1][1])), + ((!e || lm[0] > 1) && (e === evolutionChain.length - 1 || lm[0] <= evolutionChain[e + 1][1])), ), ); } @@ -3074,19 +2696,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { lm[0] > 0, ); } - if ( - this.fusionSpecies && - learnSituation !== LearnMoveSituation.EVOLUTION_FUSED_BASE - ) { + if (this.fusionSpecies && learnSituation !== LearnMoveSituation.EVOLUTION_FUSED_BASE) { // For fusion evolutions, get ONLY the moves of the component mon that evolved if (simulateEvolutionChain) { - const fusionEvolutionChain = - this.fusionSpecies.getSimulatedEvolutionChain( - this.level, - this.hasTrainer(), - this.isBoss(), - this.isPlayer(), - ); + const fusionEvolutionChain = this.fusionSpecies.getSimulatedEvolutionChain( + this.level, + this.hasTrainer(), + this.isBoss(), + this.isPlayer(), + ); for (let e = 0; e < fusionEvolutionChain.length; e++) { // TODO: Might need to pass specific form index in simulated evolution chain const speciesLevelMoves = getPokemonSpeciesForm( @@ -3096,9 +2714,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (includeRelearnerMoves) { levelMoves.push( ...speciesLevelMoves.filter( - lm => - (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || - lm[0] !== EVOLVE_MOVE, + lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || lm[0] !== EVOLVE_MOVE, ), ); } else { @@ -3107,8 +2723,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || ((!e || lm[0] > 1) && - (e === fusionEvolutionChain.length - 1 || - lm[0] <= fusionEvolutionChain[e + 1][1])), + (e === fusionEvolutionChain.length - 1 || lm[0] <= fusionEvolutionChain[e + 1][1])), ), ); } @@ -3127,9 +2742,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } } - levelMoves.sort((lma: [number, number], lmb: [number, number]) => - lma[0] > lmb[0] ? 1 : lma[0] < lmb[0] ? -1 : 0, - ); + levelMoves.sort((lma: [number, number], lmb: [number, number]) => (lma[0] > lmb[0] ? 1 : lma[0] < lmb[0] ? -1 : 0)); /** * Filter out moves not within the correct level range(s) @@ -3141,10 +2754,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const isRelearner = level < startingLevel; const allowedEvolutionMove = level === 0 && includeEvolutionMoves; - return ( - !(level > this.level) && - (includeRelearnerMoves || !isRelearner || allowedEvolutionMove) - ); + return !(level > this.level) && (includeRelearnerMoves || !isRelearner || allowedEvolutionMove); }); /** @@ -3210,10 +2820,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ trySetShiny(thresholdOverride?: number): boolean { // Shiny Pokemon should not spawn in the end biome in endless - if ( - globalScene.gameMode.isEndless && - globalScene.arena.biomeType === Biome.END - ) { + if (globalScene.gameMode.isEndless && globalScene.arena.biomeType === Biome.END) { return false; } @@ -3233,11 +2840,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } if (!this.hasTrainer()) { - globalScene.applyModifiers( - ShinyRateBoosterModifier, - true, - shinyThreshold, - ); + globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); } } else { shinyThreshold.value = thresholdOverride; @@ -3262,10 +2865,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param applyModifiersToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Shiny Charm and event modifiers to {@linkcode thresholdOverride} * @returns `true` if the Pokemon has been set as a shiny, `false` otherwise */ - public trySetShinySeed( - thresholdOverride?: number, - applyModifiersToOverride?: boolean, - ): boolean { + public trySetShinySeed(thresholdOverride?: number, applyModifiersToOverride?: boolean): boolean { if (!this.shiny) { const shinyThreshold = new NumberHolder(BASE_SHINY_CHANCE); if (thresholdOverride === undefined || applyModifiersToOverride) { @@ -3275,13 +2875,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (timedEventManager.isEventActive()) { shinyThreshold.value *= timedEventManager.getShinyMultiplier(); } - globalScene.applyModifiers( - ShinyRateBoosterModifier, - true, - shinyThreshold, - ); - } - else { + globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); + } else { shinyThreshold.value = thresholdOverride; } @@ -3291,8 +2886,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.shiny) { this.variant = this.variant ?? 0; this.variant = Math.max(this.generateShinyVariant(), this.variant) as Variant; // Don't set a variant lower than the current one - this.luck = - this.variant + 1 + (this.fusionShiny ? this.fusionVariant + 1 : 0); + this.luck = this.variant + 1 + (this.fusionShiny ? this.fusionVariant + 1 : 0); this.initShinySparkle(); } @@ -3318,8 +2912,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Checks if there is no variant data for both the index or index with form if ( !this.shiny || - (!variantData.hasOwnProperty(variantDataIndex) && - !variantData.hasOwnProperty(this.species.speciesId)) + (!variantData.hasOwnProperty(variantDataIndex) && !variantData.hasOwnProperty(this.species.speciesId)) ) { return 0; } @@ -3349,10 +2942,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param applyModifiersToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Ability Charm to {@linkcode thresholdOverride} * @returns `true` if the Pokemon has been set to have its hidden ability, `false` otherwise */ - public tryRerollHiddenAbilitySeed( - thresholdOverride?: number, - applyModifiersToOverride?: boolean, - ): boolean { + public tryRerollHiddenAbilitySeed(thresholdOverride?: number, applyModifiersToOverride?: boolean): boolean { if (!this.species.abilityHidden) { return false; } @@ -3362,11 +2952,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { haThreshold.value = thresholdOverride; } if (!this.hasTrainer()) { - globalScene.applyModifiers( - HiddenAbilityRateBoosterModifier, - true, - haThreshold, - ); + globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, haThreshold); } } else { haThreshold.value = thresholdOverride; @@ -3380,15 +2966,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } public generateFusionSpecies(forStarter?: boolean): void { - const hiddenAbilityChance = new NumberHolder( - BASE_HIDDEN_ABILITY_CHANCE, - ); + const hiddenAbilityChance = new NumberHolder(BASE_HIDDEN_ABILITY_CHANCE); if (!this.hasTrainer()) { - globalScene.applyModifiers( - HiddenAbilityRateBoosterModifier, - true, - hiddenAbilityChance, - ); + globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); } const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value); @@ -3411,30 +2991,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let fusionOverride: PokemonSpecies | undefined = undefined; - if ( - forStarter && - this instanceof PlayerPokemon && - Overrides.STARTER_FUSION_SPECIES_OVERRIDE - ) { - fusionOverride = getPokemonSpecies( - Overrides.STARTER_FUSION_SPECIES_OVERRIDE, - ); - } else if ( - this instanceof EnemyPokemon && - Overrides.OPP_FUSION_SPECIES_OVERRIDE - ) { + if (forStarter && this instanceof PlayerPokemon && Overrides.STARTER_FUSION_SPECIES_OVERRIDE) { + fusionOverride = getPokemonSpecies(Overrides.STARTER_FUSION_SPECIES_OVERRIDE); + } else if (this instanceof EnemyPokemon && Overrides.OPP_FUSION_SPECIES_OVERRIDE) { fusionOverride = getPokemonSpecies(Overrides.OPP_FUSION_SPECIES_OVERRIDE); } this.fusionSpecies = fusionOverride ?? - globalScene.randomSpecies( - globalScene.currentBattle?.waveIndex || 0, - this.level, - false, - filter, - true, - ); + globalScene.randomSpecies(globalScene.currentBattle?.waveIndex || 0, this.level, false, filter, true); this.fusionAbilityIndex = this.fusionSpecies.abilityHidden && hasHiddenAbility ? 2 @@ -3486,10 +3051,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let movePool: [Moves, number][] = []; const allLevelMoves = this.getLevelMoves(1, true, true); if (!allLevelMoves) { - console.warn( - "Error encountered trying to generate moveset for:", - this.species.name, - ); + console.warn("Error encountered trying to generate moveset for:", this.species.name); return; } @@ -3504,13 +3066,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { weight = 50; } // Assume level 1 moves with 80+ BP are "move reminder" moves and bump their weight. Trainers use actual relearn moves. - if (weight === 1 && allMoves[levelMove[1]].power >= 80 || weight === RELEARN_MOVE && this.hasTrainer()) { + if ((weight === 1 && allMoves[levelMove[1]].power >= 80) || (weight === RELEARN_MOVE && this.hasTrainer())) { weight = 40; } - if ( - !movePool.some(m => m[0] === levelMove[1]) && - !allMoves[levelMove[1]].name.endsWith(" (N)") - ) { + if (!movePool.some(m => m[0] === levelMove[1]) && !allMoves[levelMove[1]].name.endsWith(" (N)")) { movePool.push([levelMove[1], weight]); } } @@ -3531,30 +3090,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { compatible = true; break; } - } else if ( - p === this.species.speciesId || - (this.fusionSpecies && p === this.fusionSpecies.speciesId) - ) { + } else if (p === this.species.speciesId || (this.fusionSpecies && p === this.fusionSpecies.speciesId)) { compatible = true; break; } } - if ( - compatible && - !movePool.some(m => m[0] === moveId) && - !allMoves[moveId].name.endsWith(" (N)") - ) { + if (compatible && !movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) { if (tmPoolTiers[moveId] === ModifierTier.COMMON && this.level >= 15) { movePool.push([moveId, 4]); - } else if ( - tmPoolTiers[moveId] === ModifierTier.GREAT && - this.level >= 30 - ) { + } else if (tmPoolTiers[moveId] === ModifierTier.GREAT && this.level >= 30) { movePool.push([moveId, 8]); - } else if ( - tmPoolTiers[moveId] === ModifierTier.ULTRA && - this.level >= 50 - ) { + } else if (tmPoolTiers[moveId] === ModifierTier.ULTRA && this.level >= 50) { movePool.push([moveId, 14]); } } @@ -3564,10 +3110,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.level >= 60) { for (let i = 0; i < 3; i++) { const moveId = speciesEggMoves[this.species.getRootSpeciesId()][i]; - if ( - !movePool.some(m => m[0] === moveId) && - !allMoves[moveId].name.endsWith(" (N)") - ) { + if (!movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) { movePool.push([moveId, 40]); } } @@ -3583,17 +3126,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (this.fusionSpecies) { for (let i = 0; i < 3; i++) { - const moveId = - speciesEggMoves[this.fusionSpecies.getRootSpeciesId()][i]; - if ( - !movePool.some(m => m[0] === moveId) && - !allMoves[moveId].name.endsWith(" (N)") - ) { + const moveId = speciesEggMoves[this.fusionSpecies.getRootSpeciesId()][i]; + if (!movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) { movePool.push([moveId, 40]); } } - const moveId = - speciesEggMoves[this.fusionSpecies.getRootSpeciesId()][3]; + const moveId = speciesEggMoves[this.fusionSpecies.getRootSpeciesId()][3]; // No rare egg moves before e4 if ( this.level >= 170 && @@ -3612,35 +3150,21 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { 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)); // 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), ]); } @@ -3648,10 +3172,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Caps max power at 90 to avoid something like hyper beam ruining the stats. // This is a pretty soft weighting factor, although it is scaled with the weight multiplier. const maxPower = Math.min( - movePool.reduce( - (v, m) => Math.max(allMoves[m[0]].calculateEffectivePower(), v), - 40, - ), + movePool.reduce((v, m) => Math.max(allMoves[m[0]].calculateEffectivePower(), v), 40), 90, ); movePool = movePool.map(m => [ @@ -3659,10 +3180,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { m[1] * (allMoves[m[0]].category === MoveCategory.STATUS ? 1 - : Math.max( - Math.min(allMoves[m[0]].calculateEffectivePower() / maxPower, 1), - 0.5, - )), + : Math.max(Math.min(allMoves[m[0]].calculateEffectivePower() / maxPower, 1), 0.5)), ]); // Weight damaging moves against the lower stat. This uses a non-linear relationship. @@ -3670,16 +3188,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // One third weight at ~1.58x higher, one quarter weight at ~1.73x higher, one fifth at ~1.87x, and one tenth at ~2.35x higher. const atk = this.getStat(Stat.ATK); const spAtk = this.getStat(Stat.SPATK); - const worseCategory: MoveCategory = - atk > spAtk ? MoveCategory.SPECIAL : MoveCategory.PHYSICAL; - const statRatio = - worseCategory === MoveCategory.PHYSICAL ? atk / spAtk : spAtk / atk; + const worseCategory: MoveCategory = atk > spAtk ? MoveCategory.SPECIAL : MoveCategory.PHYSICAL; + const statRatio = worseCategory === MoveCategory.PHYSICAL ? atk / spAtk : spAtk / atk; movePool = movePool.map(m => [ m[0], - m[1] * - (allMoves[m[0]].category === worseCategory - ? Math.min(Math.pow(statRatio, 3) * 1.3, 1) - : 1), + m[1] * (allMoves[m[0]].category === worseCategory ? Math.min(Math.pow(statRatio, 3) * 1.3, 1) : 1), ]); /** The higher this is the more the game weights towards higher level moves. At `0` all moves are equal weight. */ @@ -3687,16 +3200,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.isBoss()) { weightMultiplier += 0.4; } - const baseWeights: [Moves, number][] = movePool.map(m => [ - m[0], - Math.ceil(Math.pow(m[1], weightMultiplier) * 100), - ]); + const baseWeights: [Moves, number][] = movePool.map(m => [m[0], Math.ceil(Math.pow(m[1], weightMultiplier) * 100)]); // All Pokemon force a STAB move first const stabMovePool = baseWeights.filter( - m => - allMoves[m[0]].category !== MoveCategory.STATUS && - this.isOfType(allMoves[m[0]].type), + m => allMoves[m[0]].category !== MoveCategory.STATUS && this.isOfType(allMoves[m[0]].type), ); if (stabMovePool.length) { @@ -3709,41 +3217,32 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.moveset.push(new PokemonMove(stabMovePool[index][0], 0, 0)); } - while ( - baseWeights.length > this.moveset.length && - this.moveset.length < 4 - ) { + while (baseWeights.length > this.moveset.length && this.moveset.length < 4) { if (this.hasTrainer()) { // Sqrt the weight of any damaging moves with overlapping types. This is about a 0.05 - 0.1 multiplier. // Other damaging moves 2x weight if 0-1 damaging moves, 0.5x if 2, 0.125x if 3. These weights get 20x if STAB. // Status moves remain unchanged on weight, this encourages 1-2 movePool = baseWeights - .filter(m => !this.moveset.some( - mo => - m[0] === mo.moveId || - (allMoves[m[0]].hasAttr(SacrificialAttr) && mo.getMove().hasAttr(SacrificialAttr)) // Only one self-KO move allowed - )) + .filter( + m => + !this.moveset.some( + mo => + m[0] === mo.moveId || + (allMoves[m[0]].hasAttr(SacrificialAttr) && mo.getMove().hasAttr(SacrificialAttr)), // Only one self-KO move allowed + ), + ) .map(m => { let ret: number; if ( this.moveset.some( - mo => - mo.getMove().category !== MoveCategory.STATUS && - mo.getMove().type === allMoves[m[0]].type, + mo => mo.getMove().category !== MoveCategory.STATUS && mo.getMove().type === allMoves[m[0]].type, ) ) { ret = Math.ceil(Math.sqrt(m[1])); } else if (allMoves[m[0]].category !== MoveCategory.STATUS) { ret = Math.ceil( (m[1] / - Math.max( - Math.pow( - 4, - this.moveset.filter(mo => (mo.getMove().power ?? 0) > 1) - .length, - ) / 8, - 0.5, - )) * + Math.max(Math.pow(4, this.moveset.filter(mo => (mo.getMove().power ?? 0) > 1).length) / 8, 0.5)) * (this.isOfType(allMoves[m[0]].type) ? 20 : 1), ); } else { @@ -3753,11 +3252,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }); } else { // Non-trainer pokemon just use normal weights - movePool = baseWeights.filter(m => !this.moveset.some( - mo => - m[0] === mo.moveId || - (allMoves[m[0]].hasAttr(SacrificialAttr) && mo.getMove().hasAttr(SacrificialAttr)) // Only one self-KO move allowed - )); + movePool = baseWeights.filter( + m => + !this.moveset.some( + mo => + m[0] === mo.moveId || + (allMoves[m[0]].hasAttr(SacrificialAttr) && mo.getMove().hasAttr(SacrificialAttr)), // Only one self-KO move allowed + ), + ); } const totalWeight = movePool.reduce((v, m) => v + m[1], 0); let rand = randSeedInt(totalWeight); @@ -3774,18 +3276,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { !globalScene.currentBattle?.isBattleMysteryEncounter() || !globalScene.currentBattle?.mysteryEncounter ) { - globalScene.triggerPokemonFormChange( - this, - SpeciesFormChangeMoveLearnedTrigger, - ); + globalScene.triggerPokemonFormChange(this, SpeciesFormChangeMoveLearnedTrigger); } } public trySelectMove(moveIndex: number, ignorePp?: boolean): boolean { - const move = - this.getMoveset().length > moveIndex - ? this.getMoveset()[moveIndex] - : null; + const move = this.getMoveset().length > moveIndex ? this.getMoveset()[moveIndex] : null; return move?.isUsable(this, ignorePp) ?? false; } @@ -3794,11 +3290,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const otherBattleInfo = globalScene.fieldUI .getAll() .slice(0, 4) - .filter( - ui => - ui instanceof BattleInfo && - (ui as BattleInfo) instanceof PlayerBattleInfo === this.isPlayer(), - ) + .filter(ui => ui instanceof BattleInfo && (ui as BattleInfo) instanceof PlayerBattleInfo === this.isPlayer()) .find(() => true); if (!otherBattleInfo || !this.getFieldIndex()) { globalScene.fieldUI.sendToBack(this.battleInfo); @@ -3806,10 +3298,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } else { globalScene.fieldUI.moveAbove(this.battleInfo, otherBattleInfo); } - this.battleInfo.setX( - this.battleInfo.x + - (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198), - ); + this.battleInfo.setX(this.battleInfo.x + (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198)); this.battleInfo.setVisible(true); if (this.isPlayer()) { this.battleInfo.expMaskRect.x += 150; @@ -3825,7 +3314,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { hideInfo(): Promise { return new Promise(resolve => { - if (this.battleInfo && this.battleInfo.visible) { + if (this.battleInfo?.visible) { globalScene.tweens.add({ targets: [this.battleInfo, this.battleInfo.expMaskRect], x: this.isPlayer() ? "+=150" : `-=${!this.isBoss() ? 150 : 246}`, @@ -3836,10 +3325,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.battleInfo.expMaskRect.x -= 150; } this.battleInfo.setVisible(false); - this.battleInfo.setX( - this.battleInfo.x - - (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198), - ); + this.battleInfo.setX(this.battleInfo.x - (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198)); resolve(); }, }); @@ -3886,25 +3372,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const maxExpLevel = globalScene.getMaxExpLevel(ignoreLevelCap); const initialExp = this.exp; this.exp += exp; - while ( - this.level < maxExpLevel && - this.exp >= getLevelTotalExp(this.level + 1, this.species.growthRate) - ) { + while (this.level < maxExpLevel && this.exp >= getLevelTotalExp(this.level + 1, this.species.growthRate)) { this.level++; } if (this.level >= maxExpLevel) { - console.log( - initialExp, - this.exp, - getLevelTotalExp(this.level, this.species.growthRate), - ); - this.exp = Math.max( - getLevelTotalExp(this.level, this.species.growthRate), - initialExp, - ); + console.log(initialExp, this.exp, getLevelTotalExp(this.level, this.species.growthRate)); + this.exp = Math.max(getLevelTotalExp(this.level, this.species.growthRate), initialExp); } - this.levelExp = - this.exp - getLevelTotalExp(this.level, this.species.growthRate); + this.levelExp = this.exp - getLevelTotalExp(this.level, this.species.growthRate); } /** @@ -3918,7 +3393,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getOpponent(targetIndex: number): Pokemon | null { const ret = this.getOpponents()[targetIndex]; - if (ret.summonData) { // TODO: why does this check for summonData and can we remove it? + // TODO: why does this check for summonData and can we remove it? + if (ret.summonData) { return ret; } return null; @@ -3930,11 +3406,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param onField - whether to also check if the pokemon is currently on the field (defaults to true) */ getOpponents(onField = true): Pokemon[] { - return ( - (this.isPlayer() - ? globalScene.getEnemyField() - : globalScene.getPlayerField()) as Pokemon[] - ).filter(p => p.isActive(onField)); + return ((this.isPlayer() ? globalScene.getEnemyField() : globalScene.getPlayerField()) as Pokemon[]).filter(p => + p.isActive(onField), + ); } getOpponentDescriptor(): string { @@ -3942,17 +3416,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (opponents.length === 1) { return opponents[0].name; } - return this.isPlayer() - ? i18next.t("arenaTag:opposingTeam") - : i18next.t("arenaTag:yourTeam"); + return this.isPlayer() ? i18next.t("arenaTag:opposingTeam") : i18next.t("arenaTag:yourTeam"); } getAlly(): Pokemon | undefined { - return ( - this.isPlayer() - ? globalScene.getPlayerField() - : globalScene.getEnemyField() - )[this.getFieldIndex() ? 0 : 1]; + return (this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField())[this.getFieldIndex() ? 0 : 1]; } /** @@ -3961,9 +3429,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 instanceof PlayerPokemon ? globalScene.getPlayerField() : globalScene.getEnemyField(); } /** @@ -4006,37 +3472,17 @@ 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, - ); + applyMoveAttrs(IgnoreOpponentStatStagesAttr, this, opponent, move, ignoreStatStage); } } if (!ignoreStatStage.value) { - const statStageMultiplier = new NumberHolder( - Math.max(2, 2 + statStage.value) / Math.max(2, 2 - statStage.value), - ); + const statStageMultiplier = new NumberHolder(Math.max(2, 2 + statStage.value) / Math.max(2, 2 - statStage.value)); if (!ignoreHeldItems) { - globalScene.applyModifiers( - TempStatStageBoosterModifier, - this.isPlayer(), - stat, - statStageMultiplier, - ); + globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), stat, statStageMultiplier); } return Math.min(statStageMultiplier.value, 4); } @@ -4060,47 +3506,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } const userAccStage = new NumberHolder(this.getStatStage(Stat.ACC)); - const targetEvaStage = new NumberHolder( - target.getStatStage(Stat.EVA), - ); + const targetEvaStage = new NumberHolder(target.getStatStage(Stat.EVA)); 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, - ); - applyMoveAttrs( - IgnoreOpponentStatStagesAttr, - this, - target, - sourceMove, - 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, - ); + globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage); - userAccStage.value = ignoreAccStatStage.value - ? 0 - : Math.min(userAccStage.value, 6); + userAccStage.value = ignoreAccStatStage.value ? 0 : Math.min(userAccStage.value, 6); targetEvaStage.value = ignoreEvaStatStage.value ? 0 : targetEvaStage.value; if (target.findTag(t => t instanceof ExposedTag)) { @@ -4115,22 +3532,10 @@ 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)) { @@ -4156,8 +3561,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param simulated - if `true`, suppresses changes to game state during calculation (defaults to `true`). * @returns The move's base damage against this Pokemon when used by the source Pokemon. */ - getBaseDamage( - { + getBaseDamage({ source, move, moveCategory, @@ -4166,8 +3570,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ignoreAllyAbility = false, ignoreSourceAllyAbility = false, isCritical = false, - simulated = true}: getBaseDamageParams - ): number { + simulated = true, + }: getBaseDamageParams): number { const isPhysical = moveCategory === MoveCategory.PHYSICAL; /** A base damage multiplier based on the source's level */ @@ -4216,25 +3620,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * The attack's base damage, as determined by the source's level, move power * and Attack stat as well as this Pokemon's Defense stat */ - const baseDamage = - (levelMultiplier * power * sourceAtk.value) / targetDef.value / 50 + 2; + const baseDamage = (levelMultiplier * power * sourceAtk.value) / targetDef.value / 50 + 2; /** Debug message for non-simulated calls (i.e. when damage is actually dealt) */ if (!simulated) { - console.log( - "base damage", - baseDamage, - move.name, - power, - sourceAtk.value, - targetDef.value, - ); + console.log("base damage", baseDamage, move.name, power, sourceAtk.value, targetDef.value); } return baseDamage; } - /** Determine the STAB multiplier for a move used against this pokemon. * * @param source - The attacking {@linkcode Pokemon} @@ -4258,31 +3653,20 @@ 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); } - if ( - source.isTerastallized && - sourceTeraType === moveType && - moveType !== PokemonType.STELLAR - ) { + if (source.isTerastallized && sourceTeraType === moveType && moveType !== PokemonType.STELLAR) { stabMultiplier.value += 0.5; } if ( source.isTerastallized && source.getTeraType() === PokemonType.STELLAR && - (!source.stellarTypesBoosted.includes(moveType) || - source.hasSpecies(Species.TERAPAGOS)) + (!source.stellarTypesBoosted.includes(moveType) || source.hasSpecies(Species.TERAPAGOS)) ) { stabMultiplier.value += matchesSourceType ? 0.5 : 0.2; } @@ -4303,31 +3687,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param effectiveness If defined, used in place of calculated effectiveness values * @returns The {@linkcode DamageCalculationResult} */ - getAttackDamage( - { - source, - move, - ignoreAbility = false, - ignoreSourceAbility = false, - ignoreAllyAbility = false, - ignoreSourceAllyAbility = false, - isCritical = false, - simulated = true, - effectiveness}: getAttackDamageParams, - ): DamageCalculationResult { + getAttackDamage({ + source, + move, + ignoreAbility = false, + ignoreSourceAbility = false, + ignoreAllyAbility = false, + ignoreSourceAllyAbility = false, + isCritical = false, + simulated = true, + effectiveness, + }: getAttackDamageParams): DamageCalculationResult { const damage = new NumberHolder(0); - const defendingSide = this.isPlayer() - ? ArenaTagSide.PLAYER - : ArenaTagSide.ENEMY; + 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 */ @@ -4343,13 +3718,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * * Note that the source's abilities are not ignored here */ - const typeMultiplier = effectiveness ?? this.getMoveEffectiveness( - source, - move, - ignoreAbility, - simulated, - cancelled, - ); + const typeMultiplier = + effectiveness ?? this.getMoveEffectiveness(source, move, ignoreAbility, simulated, cancelled); const isPhysical = moveCategory === MoveCategory.PHYSICAL; @@ -4357,21 +3727,14 @@ 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; if (cancelled.value || isTypeImmune) { return { cancelled: cancelled.value, - result: - move.id === Moves.SHEER_COLD ? HitResult.IMMUNE : HitResult.NO_EFFECT, + result: move.id === Moves.SHEER_COLD ? HitResult.IMMUNE : HitResult.NO_EFFECT, damage: 0, }; } @@ -4389,9 +3752,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { null, multiLensMultiplier, ); - fixedDamage.value = toDmgValue( - fixedDamage.value * multiLensMultiplier.value, - ); + fixedDamage.value = toDmgValue(fixedDamage.value * multiLensMultiplier.value); return { cancelled: false, @@ -4468,10 +3829,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * A multiplier for random damage spread in the range [0.85, 1] * This is always 1 for simulated calls. */ - const randomMultiplier = simulated - ? 1 - : this.randBattleSeedIntRange(85, 100) / 100; - + const randomMultiplier = simulated ? 1 : this.randBattleSeedIntRange(85, 100) / 100; /** A damage multiplier for when the attack is of the attacker's type and/or Tera type. */ const stabMultiplier = this.calculateStabMultiplier(source, move, ignoreSourceAbility, simulated); @@ -4486,12 +3844,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; @@ -4504,13 +3857,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Critical hits should bypass screens if (!isCritical) { globalScene.arena.applyTagsForSide( - WeakenMoveScreenTag, - defendingSide, - simulated, - source, - moveCategory, - screenMultiplier, - ); + WeakenMoveScreenTag, + defendingSide, + simulated, + source, + moveCategory, + screenMultiplier, + ); } /** @@ -4555,14 +3908,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 */ @@ -4575,28 +3921,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); } } @@ -4604,15 +3934,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) @@ -4641,8 +3963,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @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 - */ - getCriticalHitResult(source: Pokemon, move: Move, simulated: boolean = true): boolean { + */ + 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)) { @@ -4656,16 +3978,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { 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)) - ]; + 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); return isCritical.value; - } /** @@ -4676,12 +3995,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param ignoreFaintPhas flag on whether to add FaintPhase if pokemon after applying damage faints * @returns integer representing damage dealt */ - damage( - damage: number, - _ignoreSegments = false, - preventEndure = false, - ignoreFaintPhase = false, - ): number { + damage(damage: number, _ignoreSegments = false, preventEndure = false, ignoreFaintPhase = false): number { if (this.isFainted()) { return 0; } @@ -4697,12 +4011,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { surviveDamage.value = this.lapseTag(BattlerTagType.ENDURE_TOKEN); } if (!surviveDamage.value) { - globalScene.applyModifiers( - SurviveDamageModifier, - this.isPlayer(), - this, - surviveDamage, - ); + globalScene.applyModifiers(SurviveDamageModifier, this.isPlayer(), this, surviveDamage); } if (surviveDamage.value) { damage = this.hp - 1; @@ -4720,9 +4029,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.setPhaseQueueSplice(); - globalScene.unshiftPhase( - new FaintPhase(this.getBattlerIndex(), preventEndure), - ); + globalScene.unshiftPhase(new FaintPhase(this.getBattlerIndex(), preventEndure)); this.destroySubstitute(); this.lapseTag(BattlerTagType.COMMANDED); } @@ -4741,39 +4048,29 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param ignoreFaintPhase boolean to ignore adding a FaintPhase, passsed to damage() * @returns integer of damage done */ - damageAndUpdate(damage: number, + damageAndUpdate( + damage: number, { result = HitResult.EFFECTIVE, isCritical = false, ignoreSegments = false, ignoreFaintPhase = false, source = undefined, - }: - { - result?: DamageResult, - isCritical?: boolean, - ignoreSegments?: boolean, - ignoreFaintPhase?: boolean, - source?: Pokemon, - } = {} + }: { + result?: DamageResult; + isCritical?: boolean; + ignoreSegments?: boolean; + ignoreFaintPhase?: boolean; + source?: Pokemon; + } = {}, ): number { - const isIndirectDamage = [ HitResult.INDIRECT, HitResult.INDIRECT_KO ].includes(result); - const damagePhase = new DamageAnimPhase( - this.getBattlerIndex(), - damage, - result as DamageResult, - isCritical - ); + const isIndirectDamage = [HitResult.INDIRECT, HitResult.INDIRECT_KO].includes(result); + const damagePhase = new DamageAnimPhase(this.getBattlerIndex(), damage, result as DamageResult, isCritical); globalScene.unshiftPhase(damagePhase); if (this.switchOutStatus && source) { damage = 0; } - damage = this.damage( - damage, - ignoreSegments, - isIndirectDamage, - ignoreFaintPhase, - ); + damage = this.damage(damage, ignoreSegments, isIndirectDamage, ignoreFaintPhase); // Ensure the battle-info bar's HP is updated, though only if the battle info is visible // TODO: When battle-info UI is refactored, make this only update the HP bar if (this.battleInfo.visible) { @@ -4786,15 +4083,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; } @@ -4810,13 +4099,28 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } isMax(): boolean { - const maxForms = [ SpeciesFormKey.GIGANTAMAX, SpeciesFormKey.GIGANTAMAX_RAPID, SpeciesFormKey.GIGANTAMAX_SINGLE, SpeciesFormKey.ETERNAMAX ] as string[]; - return maxForms.includes(this.getFormKey()) || (!!this.getFusionFormKey() && maxForms.includes(this.getFusionFormKey()!)); + const maxForms = [ + SpeciesFormKey.GIGANTAMAX, + SpeciesFormKey.GIGANTAMAX_RAPID, + SpeciesFormKey.GIGANTAMAX_SINGLE, + SpeciesFormKey.ETERNAMAX, + ] as string[]; + return ( + maxForms.includes(this.getFormKey()) || (!!this.getFusionFormKey() && maxForms.includes(this.getFusionFormKey()!)) + ); } isMega(): boolean { - const megaForms = [ SpeciesFormKey.MEGA, SpeciesFormKey.MEGA_X, SpeciesFormKey.MEGA_Y, SpeciesFormKey.PRIMAL ] as string[]; - return megaForms.includes(this.getFormKey()) || (!!this.getFusionFormKey() && megaForms.includes(this.getFusionFormKey()!)); + const megaForms = [ + SpeciesFormKey.MEGA, + SpeciesFormKey.MEGA_X, + SpeciesFormKey.MEGA_Y, + SpeciesFormKey.PRIMAL, + ] as string[]; + return ( + megaForms.includes(this.getFormKey()) || + (!!this.getFusionFormKey() && megaForms.includes(this.getFusionFormKey()!)) + ); } canAddTag(tagType: BattlerTagType): boolean { @@ -4827,35 +4131,17 @@ 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; } - addTag( - tagType: BattlerTagType, - turnCount = 0, - sourceMove?: Moves, - sourceId?: number, - ): boolean { + addTag(tagType: BattlerTagType, turnCount = 0, sourceMove?: Moves, sourceId?: number): boolean { const existingTag = this.getTag(tagType); if (existingTag) { existingTag.onOverlap(this); @@ -4865,25 +4151,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; } @@ -4931,14 +4205,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const tags = this.summonData.tags; const tag = tags.find(t => t.tagType === tagType); if (!tag) { - return false + return false; } if (!tag.lapse(this, BattlerTagLapseType.CUSTOM)) { tag.onRemove(this); tags.splice(tags.indexOf(tag), 1); } - return true + return true; } /** @@ -4952,8 +4226,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { .filter( t => lapseType === BattlerTagLapseType.FAINT || - (t.lapseTypes.some(lType => lType === lapseType) && - !t.lapse(this, lapseType)), + (t.lapseTypes.some(lType => lType === lapseType) && !t.lapse(this, lapseType)), ) .forEach(t => { t.onRemove(this); @@ -4997,7 +4270,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (t.sourceId === sourceId) { t.sourceId = newSourceId; } - }) + }); } /** @@ -5057,21 +4330,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * * @see {@linkcode MoveRestrictionBattlerTag} */ - isMoveTargetRestricted( - moveId: Moves, - user: Pokemon, - target: Pokemon, - ): boolean { - for (const tag of this.findTags( - t => t instanceof MoveRestrictionBattlerTag, - )) { - if ( - (tag as MoveRestrictionBattlerTag).isMoveTargetRestricted( - moveId, - user, - target, - ) - ) { + isMoveTargetRestricted(moveId: Moves, user: Pokemon, target: Pokemon): boolean { + for (const tag of this.findTags(t => t instanceof MoveRestrictionBattlerTag)) { + if ((tag as MoveRestrictionBattlerTag).isMoveTargetRestricted(moveId, user, target)) { return (tag as MoveRestrictionBattlerTag) !== null; } } @@ -5086,26 +4347,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param target - {@linkcode Pokemon} the target of the move, optional and used when the target is a factor in the move's restricted status * @returns The first tag on this Pokemon that restricts the move, or `null` if the move is not restricted. */ - getRestrictingTag( - moveId: Moves, - user?: Pokemon, - target?: Pokemon, - ): MoveRestrictionBattlerTag | null { - for (const tag of this.findTags( - t => t instanceof MoveRestrictionBattlerTag, - )) { + getRestrictingTag(moveId: Moves, user?: Pokemon, target?: Pokemon): MoveRestrictionBattlerTag | null { + for (const tag of this.findTags(t => t instanceof MoveRestrictionBattlerTag)) { if ((tag as MoveRestrictionBattlerTag).isMoveRestricted(moveId, user)) { return tag as MoveRestrictionBattlerTag; } - if ( - user && - target && - (tag as MoveRestrictionBattlerTag).isMoveTargetRestricted( - moveId, - user, - target, - ) - ) { + if (user && target && (tag as MoveRestrictionBattlerTag).isMoveTargetRestricted(moveId, user, target)) { return tag as MoveRestrictionBattlerTag; } } @@ -5135,9 +4382,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getLastXMoves(moveCount = 1): TurnMove[] { const moveHistory = this.getMoveHistory(); if (moveCount >= 0) { - return moveHistory - .slice(Math.max(moveHistory.length - moveCount, 0)) - .reverse(); + return moveHistory.slice(Math.max(moveHistory.length - moveCount, 0)).reverse(); } return moveHistory.slice(0).reverse(); } @@ -5163,39 +4408,24 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.loadAssets().then(() => { this.calculateStats(); globalScene.updateModifiers(this.isPlayer(), true); - Promise.all([this.updateInfo(), globalScene.updateFieldScale()]).then( - () => resolve(), - ); + Promise.all([this.updateInfo(), globalScene.updateFieldScale()]).then(() => resolve()); }); }); } - cry( - soundConfig?: Phaser.Types.Sound.SoundConfig, - sceneOverride?: BattleScene, - ): AnySound { + cry(soundConfig?: Phaser.Types.Sound.SoundConfig, sceneOverride?: BattleScene): AnySound { const scene = sceneOverride ?? globalScene; // TODO: is `sceneOverride` needed? const cry = this.getSpeciesForm(undefined, true).cry(soundConfig); let duration = cry.totalDuration * 1000; - if ( - this.fusionSpecies && - this.getSpeciesForm(undefined, true) !== this.getFusionSpeciesForm(undefined, true) - ) { + if (this.fusionSpecies && this.getSpeciesForm(undefined, true) !== this.getFusionSpeciesForm(undefined, true)) { let fusionCry = this.getFusionSpeciesForm(undefined, true).cry(soundConfig, true); duration = Math.min(duration, fusionCry.totalDuration * 1000); fusionCry.destroy(); scene.time.delayedCall(fixedInt(Math.ceil(duration * 0.4)), () => { try { - SoundFade.fadeOut( - scene, - cry, - fixedInt(Math.ceil(duration * 0.2)), - ); + SoundFade.fadeOut(scene, cry, fixedInt(Math.ceil(duration * 0.2))); fusionCry = this.getFusionSpeciesForm(undefined, true).cry( - Object.assign( - { seek: Math.max(fusionCry.totalDuration * 0.4, 0) }, - soundConfig, - ), + Object.assign({ seek: Math.max(fusionCry.totalDuration * 0.4, 0) }, soundConfig), ); SoundFade.fadeIn( scene, @@ -5215,10 +4445,7 @@ 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() - ) { + if (this.fusionSpecies && this.getSpeciesForm() !== this.getFusionSpeciesForm()) { return this.fusionFaintCry(callback); } @@ -5238,32 +4465,31 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { sprite.anims.pause(); tintSprite?.anims.pause(); - let faintCryTimer: Phaser.Time.TimerEvent | null = - globalScene.time.addEvent({ - delay: fixedInt(delay), - repeat: -1, - callback: () => { - frameThreshold = sprite.anims.msPerFrame / rate; - frameProgress += delay; - while (frameProgress > frameThreshold) { - if (sprite.anims.duration) { - sprite.anims.nextFrame(); - tintSprite?.anims.nextFrame(); - } - frameProgress -= frameThreshold; + let faintCryTimer: Phaser.Time.TimerEvent | null = globalScene.time.addEvent({ + delay: fixedInt(delay), + repeat: -1, + callback: () => { + frameThreshold = sprite.anims.msPerFrame / rate; + frameProgress += delay; + while (frameProgress > frameThreshold) { + if (sprite.anims.duration) { + sprite.anims.nextFrame(); + tintSprite?.anims.nextFrame(); } - if (cry && !cry.pendingRemove) { - rate *= 0.99; - cry.setRate(rate); - } else { - faintCryTimer?.destroy(); - faintCryTimer = null; - if (callback) { - callback(); - } + frameProgress -= frameThreshold; + } + if (cry && !cry.pendingRemove) { + rate *= 0.99; + cry.setRate(rate); + } else { + faintCryTimer?.destroy(); + faintCryTimer = null; + if (callback) { + callback(); } - }, - }); + } + }, + }); // Failsafe globalScene.time.delayedCall(fixedInt(3000), () => { @@ -5323,61 +4549,53 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { sprite.anims.pause(); tintSprite?.anims.pause(); - let faintCryTimer: Phaser.Time.TimerEvent | null = - globalScene.time.addEvent({ - delay: fixedInt(delay), - repeat: -1, - callback: () => { - ++i; - frameThreshold = sprite.anims.msPerFrame / rate; - frameProgress += delay; - while (frameProgress > frameThreshold) { - if (sprite.anims.duration) { - sprite.anims.nextFrame(); - tintSprite?.anims.nextFrame(); - } - frameProgress -= frameThreshold; + let faintCryTimer: Phaser.Time.TimerEvent | null = globalScene.time.addEvent({ + delay: fixedInt(delay), + repeat: -1, + callback: () => { + ++i; + frameThreshold = sprite.anims.msPerFrame / rate; + frameProgress += delay; + while (frameProgress > frameThreshold) { + if (sprite.anims.duration) { + sprite.anims.nextFrame(); + tintSprite?.anims.nextFrame(); } - if (i === transitionIndex && fusionCryKey) { - SoundFade.fadeOut( - globalScene, - cry, - fixedInt(Math.ceil((duration / rate) * 0.2)), - ); - fusionCry = globalScene.playSound( - fusionCryKey, - Object.assign({ - seek: Math.max(fusionCry.totalDuration * 0.4, 0), - rate: rate, - }), - ); - SoundFade.fadeIn( - globalScene, - fusionCry, - fixedInt(Math.ceil((duration / rate) * 0.2)), - globalScene.masterVolume * globalScene.fieldVolume, - 0, - ); + frameProgress -= frameThreshold; + } + if (i === transitionIndex && fusionCryKey) { + SoundFade.fadeOut(globalScene, cry, fixedInt(Math.ceil((duration / rate) * 0.2))); + fusionCry = globalScene.playSound( + fusionCryKey, + Object.assign({ + seek: Math.max(fusionCry.totalDuration * 0.4, 0), + rate: rate, + }), + ); + SoundFade.fadeIn( + globalScene, + fusionCry, + fixedInt(Math.ceil((duration / rate) * 0.2)), + globalScene.masterVolume * globalScene.fieldVolume, + 0, + ); + } + rate *= 0.99; + if (cry && !cry.pendingRemove) { + cry.setRate(rate); + } + if (fusionCry && !fusionCry.pendingRemove) { + fusionCry.setRate(rate); + } + if ((!cry || cry.pendingRemove) && (!fusionCry || fusionCry.pendingRemove)) { + faintCryTimer?.destroy(); + faintCryTimer = null; + if (callback) { + callback(); } - rate *= 0.99; - if (cry && !cry.pendingRemove) { - cry.setRate(rate); - } - if (fusionCry && !fusionCry.pendingRemove) { - fusionCry.setRate(rate); - } - if ( - (!cry || cry.pendingRemove) && - (!fusionCry || fusionCry.pendingRemove) - ) { - faintCryTimer?.destroy(); - faintCryTimer = null; - if (callback) { - callback(); - } - } - }, - }); + } + }, + }); // Failsafe globalScene.time.delayedCall(fixedInt(3000), () => { @@ -5400,8 +4618,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { isOppositeGender(pokemon: Pokemon): boolean { return ( this.gender !== Gender.GENDERLESS && - pokemon.gender === - (this.gender === Gender.MALE ? Gender.FEMALE : Gender.MALE) + pokemon.gender === (this.gender === Gender.MALE ? Gender.FEMALE : Gender.MALE) ); } @@ -5409,11 +4626,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!effect || quiet) { return; } - const message = effect && this.status?.effect === effect - ? getStatusEffectOverlapText(effect ?? StatusEffect.NONE, getPokemonNameWithAffix(this)) - : i18next.t("abilityTriggers:moveImmunity", { - pokemonNameWithAffix: getPokemonNameWithAffix(this), - }); + const message = + effect && this.status?.effect === effect + ? getStatusEffectOverlapText(effect ?? StatusEffect.NONE, getPokemonNameWithAffix(this)) + : i18next.t("abilityTriggers:moveImmunity", { + pokemonNameWithAffix: getPokemonNameWithAffix(this), + }); globalScene.queueMessage(message); } @@ -5438,11 +4656,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.queueImmuneMessage(quiet, effect); return false; } - if ( - this.isGrounded() && - !ignoreField && - globalScene.arena.terrain?.terrainType === TerrainType.MISTY - ) { + if (this.isGrounded() && !ignoreField && globalScene.arena.terrain?.terrainType === TerrainType.MISTY) { this.queueImmuneMessage(quiet, effect); return false; } @@ -5452,7 +4666,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { switch (effect) { case StatusEffect.POISON: - case StatusEffect.TOXIC: + case StatusEffect.TOXIC: { // Check if the Pokemon is immune to Poison/Toxic or if the source pokemon is canceling the immunity const poisonImmunity = types.map(defType => { // Check if the Pokemon is not immune to Poison/Toxic @@ -5463,20 +4677,13 @@ 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; } } - return true; + return true; }); if (this.isOfType(PokemonType.POISON) || this.isOfType(PokemonType.STEEL)) { @@ -5486,6 +4693,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } break; + } case StatusEffect.PARALYSIS: if (this.isOfType(PokemonType.ELECTRIC)) { this.queueImmuneMessage(quiet, effect); @@ -5493,10 +4701,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } break; case StatusEffect.SLEEP: - if ( - this.isGrounded() && - globalScene.arena.terrain?.terrainType === TerrainType.ELECTRIC - ) { + if (this.isGrounded() && globalScene.arena.terrain?.terrainType === TerrainType.ELECTRIC) { this.queueImmuneMessage(quiet, effect); return false; } @@ -5506,9 +4711,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.isOfType(PokemonType.ICE) || (!ignoreField && globalScene?.arena?.weather?.weatherType && - [WeatherType.SUNNY, WeatherType.HARSH_SUN].includes( - globalScene.arena.weather.weatherType, - )) + [WeatherType.SUNNY, WeatherType.HARSH_SUN].includes(globalScene.arena.weather.weatherType)) ) { this.queueImmuneMessage(quiet, effect); return false; @@ -5523,13 +4726,7 @@ 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; } @@ -5540,8 +4737,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { pokemon, effect, cancelled, - quiet, this, sourcePokemon, - ) + quiet, + this, + sourcePokemon, + ); if (cancelled.value) { break; } @@ -5551,15 +4750,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return false; } - if ( - sourcePokemon && - sourcePokemon !== this && - this.isSafeguarded(sourcePokemon) - ) { - if(!quiet){ - globalScene.queueMessage( - i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(this) - })); + if (sourcePokemon && sourcePokemon !== this && this.isSafeguarded(sourcePokemon)) { + if (!quiet) { + globalScene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(this) })); } return false; } @@ -5600,13 +4793,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.resetStatus(false); } globalScene.unshiftPhase( - new ObtainStatusEffectPhase( - this.getBattlerIndex(), - effect, - turnsRemaining, - sourceText, - sourcePokemon, - ), + new ObtainStatusEffectPhase(this.getBattlerIndex(), effect, turnsRemaining, sourceText, sourcePokemon), ); return true; } @@ -5692,9 +4879,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns `true` if this Pokemon is protected by Safeguard; `false` otherwise. */ isSafeguarded(attacker: Pokemon): boolean { - const defendingSide = this.isPlayer() - ? ArenaTagSide.PLAYER - : ArenaTagSide.ENEMY; + const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (globalScene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) { const bypassed = new BooleanHolder(false); if (attacker) { @@ -5712,18 +4897,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public fieldSetup(resetSummonData?: boolean): void { this.setSwitchOutStatus(false); if (globalScene) { - globalScene.triggerPokemonFormChange( - this, - SpeciesFormChangePostMoveTrigger, - true, - ); + globalScene.triggerPokemonFormChange(this, SpeciesFormChangePostMoveTrigger, true); } // If this Pokemon has a Substitute when loading in, play an animation to add its sprite if (this.getTag(SubstituteTag)) { - globalScene.triggerPokemonBattleAnim( - this, - PokemonAnimType.SUBSTITUTE_ADD, - ); + globalScene.triggerPokemonBattleAnim(this, PokemonAnimType.SUBSTITUTE_ADD); this.getTag(SubstituteTag)!.sourceInFocus = false; } @@ -5753,7 +4931,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } this.summonData = new PokemonSummonData(); this.tempSummonData = new PokemonTempSummonData(); - this.summonData.illusion = illusion + this.summonData.illusion = illusion; this.updateInfo(); } @@ -5761,7 +4939,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * Reset a {@linkcode Pokemon}'s per-battle {@linkcode PokemonBattleData | battleData}, * as well as any transient {@linkcode PokemonWaveData | waveData} for the current wave. * Should be called once per arena transition (new biome/trainer battle/Mystery Encounter). - */ + */ resetBattleAndWaveData(): void { this.battleData = new PokemonBattleData(); this.resetWaveData(); @@ -5782,10 +4960,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.stellarTypesBoosted = []; if (wasTerastallized) { this.updateSpritePipelineData(); - globalScene.triggerPokemonFormChange( - this, - SpeciesFormChangeLapseTeraTrigger, - ); + globalScene.triggerPokemonFormChange(this, SpeciesFormChangeLapseTeraTrigger); } } @@ -5803,18 +4978,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { try { this.getSprite().play(this.getBattleSpriteKey()); } catch (err: unknown) { - console.error( - `Failed to play animation for ${this.getBattleSpriteKey()}`, - err, - ); + console.error(`Failed to play animation for ${this.getBattleSpriteKey()}`, err); } try { this.getTintSprite()?.play(this.getBattleSpriteKey()); } catch (err: unknown) { - console.error( - `Failed to play animation for ${this.getBattleSpriteKey()}`, - err, - ); + console.error(`Failed to play animation for ${this.getBattleSpriteKey()}`, err); } } @@ -5865,9 +5034,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.x * this.parentContainer.scale + this.parentContainer.x, this.y * this.parentContainer.scale + this.parentContainer.y, ); - this.maskSprite?.setScale( - this.getSpriteScale() * this.parentContainer.scale, - ); + this.maskSprite?.setScale(this.getSpriteScale() * this.parentContainer.scale); this.maskEnabled = true; } } @@ -5893,12 +5060,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { [this.getSprite(), this.getTintSprite()] .filter(s => !!s) .map(s => { - s.pipelineData[ - `spriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}` - ] = []; - s.pipelineData[ - `fusionSpriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}` - ] = []; + s.pipelineData[`spriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}`] = []; + s.pipelineData[`fusionSpriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}`] = []; }); return; } @@ -5913,12 +5076,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.variant, ); const backSpriteKey = speciesForm - .getSpriteKey( - this.getGender(ignoreOveride) === Gender.FEMALE, - speciesForm.formIndex, - this.shiny, - this.variant, - ) + .getSpriteKey(this.getGender(ignoreOveride) === Gender.FEMALE, speciesForm.formIndex, this.shiny, this.variant) .replace("pkmn__", "pkmn__back__"); const fusionSpriteKey = fusionSpeciesForm.getSpriteKey( this.getFusionGender(ignoreOveride) === Gender.FEMALE, @@ -5961,40 +5119,28 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const spriteColors: number[][] = []; const pixelData: Uint8ClampedArray[] = []; - [canvas, backCanvas, fusionCanvas, fusionBackCanvas].forEach( - (canv: HTMLCanvasElement, c: number) => { - const context = canv.getContext("2d"); - const frame = [ - sourceFrame, - sourceBackFrame, - fusionFrame, - fusionBackFrame, - ][c]; - canv.width = frame.width; - canv.height = frame.height; + [canvas, backCanvas, fusionCanvas, fusionBackCanvas].forEach((canv: HTMLCanvasElement, c: number) => { + const context = canv.getContext("2d"); + const frame = [sourceFrame, sourceBackFrame, fusionFrame, fusionBackFrame][c]; + canv.width = frame.width; + canv.height = frame.height; - if (context) { - context.drawImage( - [sourceImage, sourceBackImage, fusionImage, fusionBackImage][c], - frame.cutX, - frame.cutY, - frame.width, - frame.height, - 0, - 0, - frame.width, - frame.height, - ); - const imageData = context.getImageData( - frame.cutX, - frame.cutY, - frame.width, - frame.height, - ); - pixelData.push(imageData.data); - } - }, - ); + if (context) { + context.drawImage( + [sourceImage, sourceBackImage, fusionImage, fusionBackImage][c], + frame.cutX, + frame.cutY, + frame.width, + frame.height, + 0, + 0, + frame.width, + frame.height, + ); + const imageData = context.getImageData(frame.cutX, frame.cutY, frame.width, frame.height); + pixelData.push(imageData.data); + } + }); for (let f = 0; f < 2; f++) { const variantColors = variantColorCache[!f ? spriteKey : backSpriteKey]; @@ -6003,9 +5149,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { Object.keys(variantColors[this.variant]).forEach(k => { variantColorSet.set( rgbaToInt(Array.from(Object.values(rgbHexToRgba(k)))), - Array.from( - Object.values(rgbHexToRgba(variantColors[this.variant][k])), - ), + Array.from(Object.values(rgbHexToRgba(variantColors[this.variant][k]))), ); }); } @@ -6035,9 +5179,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const pixelColors: number[] = []; for (let f = 0; f < 2; f++) { for (let i = 0; i < pixelData[f].length; i += 4) { - const total = pixelData[f] - .slice(i, i + 3) - .reduce((total: number, value: number) => total + value, 0); + const total = pixelData[f].slice(i, i + 3).reduce((total: number, value: number) => total + value, 0); if (!total) { continue; } @@ -6054,29 +5196,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const fusionPixelColors: number[] = []; for (let f = 0; f < 2; f++) { - const variantColors = - variantColorCache[!f ? fusionSpriteKey : fusionBackSpriteKey]; + const variantColors = variantColorCache[!f ? fusionSpriteKey : fusionBackSpriteKey]; const variantColorSet = new Map(); - if ( - this.fusionShiny && - variantColors && - variantColors[this.fusionVariant] - ) { + if (this.fusionShiny && variantColors && variantColors[this.fusionVariant]) { for (const k of Object.keys(variantColors[this.fusionVariant])) { variantColorSet.set( rgbaToInt(Array.from(Object.values(rgbHexToRgba(k)))), - Array.from( - Object.values( - rgbHexToRgba(variantColors[this.fusionVariant][k]), - ), - ), + Array.from(Object.values(rgbHexToRgba(variantColors[this.fusionVariant][k]))), ); } } for (let i = 0; i < pixelData[2 + f].length; i += 4) { - const total = pixelData[2 + f] - .slice(i, i + 3) - .reduce((total: number, value: number) => total + value, 0); + const total = pixelData[2 + f].slice(i, i + 3).reduce((total: number, value: number) => total + value, 0); if (!total) { continue; } @@ -6124,101 +5255,94 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { paletteColors = paletteColors!; // erroneously tell TS compiler that paletteColors is defined! fusionPaletteColors = fusionPaletteColors!; // mischievously misinform TS compiler that fusionPaletteColors is defined! - const [palette, fusionPalette] = [paletteColors, fusionPaletteColors].map( - paletteColors => { - let keys = Array.from(paletteColors.keys()).sort( - (a: number, b: number) => - paletteColors.get(a)! < paletteColors.get(b)! ? 1 : -1, - ); - let rgbaColors: Map; - let hsvColors: Map; + const [palette, fusionPalette] = [paletteColors, fusionPaletteColors].map(paletteColors => { + let keys = Array.from(paletteColors.keys()).sort((a: number, b: number) => + paletteColors.get(a)! < paletteColors.get(b)! ? 1 : -1, + ); + let rgbaColors: Map; + let hsvColors: Map; - const mappedColors = new Map(); + const mappedColors = new Map(); - do { - mappedColors.clear(); + do { + mappedColors.clear(); - rgbaColors = keys.reduce((map: Map, k: number) => { - map.set(k, Object.values(rgbaFromArgb(k))); - return map; - }, new Map()); - hsvColors = Array.from(rgbaColors.keys()).reduce( - (map: Map, k: number) => { - const rgb = rgbaColors.get(k)!.slice(0, 3); - map.set(k, rgbToHsv(rgb[0], rgb[1], rgb[2])); - return map; - }, - new Map(), - ); + rgbaColors = keys.reduce((map: Map, k: number) => { + map.set(k, Object.values(rgbaFromArgb(k))); + return map; + }, new Map()); + hsvColors = Array.from(rgbaColors.keys()).reduce((map: Map, k: number) => { + const rgb = rgbaColors.get(k)!.slice(0, 3); + map.set(k, rgbToHsv(rgb[0], rgb[1], rgb[2])); + return map; + }, new Map()); - for (let c = keys.length - 1; c >= 0; c--) { - const hsv = hsvColors.get(keys[c])!; - for (let c2 = 0; c2 < c; c2++) { - const hsv2 = hsvColors.get(keys[c2])!; - const diff = Math.abs(hsv[0] - hsv2[0]); - if (diff < 30 || diff >= 330) { - if (mappedColors.has(keys[c])) { - mappedColors.get(keys[c])!.push(keys[c2]); - } else { - mappedColors.set(keys[c], [keys[c2]]); - } - break; + for (let c = keys.length - 1; c >= 0; c--) { + const hsv = hsvColors.get(keys[c])!; + for (let c2 = 0; c2 < c; c2++) { + const hsv2 = hsvColors.get(keys[c2])!; + const diff = Math.abs(hsv[0] - hsv2[0]); + if (diff < 30 || diff >= 330) { + if (mappedColors.has(keys[c])) { + mappedColors.get(keys[c])!.push(keys[c2]); + } else { + mappedColors.set(keys[c], [keys[c2]]); } + break; + } + } + } + + mappedColors.forEach((values: number[], key: number) => { + const keyColor = rgbaColors.get(key)!; + const valueColors = values.map(v => rgbaColors.get(v)!); + const color = keyColor.slice(0); + let count = paletteColors.get(key)!; + for (const value of values) { + const valueCount = paletteColors.get(value); + if (!valueCount) { + continue; + } + count += valueCount; + } + + for (let c = 0; c < 3; c++) { + color[c] *= paletteColors.get(key)! / count; + values.forEach((value: number, i: number) => { + if (paletteColors.has(value)) { + const valueCount = paletteColors.get(value)!; + color[c] += valueColors[i][c] * (valueCount / count); + } + }); + color[c] = Math.round(color[c]); + } + + paletteColors.delete(key); + for (const value of values) { + paletteColors.delete(value); + if (mappedColors.has(value)) { + mappedColors.delete(value); } } - mappedColors.forEach((values: number[], key: number) => { - const keyColor = rgbaColors.get(key)!; - const valueColors = values.map(v => rgbaColors.get(v)!); - const color = keyColor.slice(0); - let count = paletteColors.get(key)!; - for (const value of values) { - const valueCount = paletteColors.get(value); - if (!valueCount) { - continue; - } - count += valueCount; - } - - for (let c = 0; c < 3; c++) { - color[c] *= paletteColors.get(key)! / count; - values.forEach((value: number, i: number) => { - if (paletteColors.has(value)) { - const valueCount = paletteColors.get(value)!; - color[c] += valueColors[i][c] * (valueCount / count); - } - }); - color[c] = Math.round(color[c]); - } - - paletteColors.delete(key); - for (const value of values) { - paletteColors.delete(value); - if (mappedColors.has(value)) { - mappedColors.delete(value); - } - } - - paletteColors.set( - argbFromRgba({ - r: color[0], - g: color[1], - b: color[2], - a: color[3], - }), - count, - ); - }); - - keys = Array.from(paletteColors.keys()).sort( - (a: number, b: number) => - paletteColors.get(a)! < paletteColors.get(b)! ? 1 : -1, + paletteColors.set( + argbFromRgba({ + r: color[0], + g: color[1], + b: color[2], + a: color[3], + }), + count, ); - } while (mappedColors.size); + }); - return keys.map(c => Object.values(rgbaFromArgb(c))); - }, - ); + keys = Array.from(paletteColors.keys()).sort((a: number, b: number) => + paletteColors.get(a)! < paletteColors.get(b)! ? 1 : -1, + ); + } while (mappedColors.size); + + return keys.map(c => Object.values(rgbaFromArgb(c))); + }); const paletteDeltas: number[][] = []; @@ -6241,10 +5365,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const ratio = easeFunc(delta / 255); const color = [0, 0, 0, fusionSpriteColors[sc][3]]; for (let c = 0; c < 3; c++) { - color[c] = Math.round( - fusionSpriteColors[sc][c] * ratio + - fusionPalette[paletteIndex][c] * (1 - ratio), - ); + color[c] = Math.round(fusionSpriteColors[sc][c] * ratio + fusionPalette[paletteIndex][c] * (1 - ratio)); } fusionSpriteColors[sc] = color; } @@ -6253,12 +5374,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { [this.getSprite(), this.getTintSprite()] .filter(s => !!s) .map(s => { - s.pipelineData[ - `spriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}` - ] = spriteColors; - s.pipelineData[ - `fusionSpriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}` - ] = fusionSpriteColors; + s.pipelineData[`spriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}`] = spriteColors; + s.pipelineData[`fusionSpriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}`] = + fusionSpriteColors; }); canvas.remove(); @@ -6278,9 +5396,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1) */ randBattleSeedInt(range: number, min = 0): number { - return globalScene.currentBattle - ? globalScene.randBattleSeedInt(range, min) - : randSeedInt(range, min); + return globalScene.currentBattle ? globalScene.randBattleSeedInt(range, min) : randSeedInt(range, min); } /** @@ -6290,9 +5406,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns a random integer between {@linkcode min} and {@linkcode max} inclusive */ randBattleSeedIntRange(min: number, max: number): number { - return globalScene.currentBattle - ? globalScene.randBattleSeedInt(max - min + 1, min) - : randSeedIntRange(min, max); + return globalScene.currentBattle ? globalScene.randBattleSeedInt(max - min + 1, min) : randSeedIntRange(min, max); } /** @@ -6320,11 +5434,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Trigger abilities that activate upon leaving the field applyPreLeaveFieldAbAttrs(PreLeaveFieldAbAttr, this); this.setSwitchOutStatus(true); - globalScene.triggerPokemonFormChange( - this, - SpeciesFormChangeActiveTrigger, - true, - ); + globalScene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true); globalScene.field.remove(this, destroy); } @@ -6346,10 +5456,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { hasSameAbilityInRootForm(abilityIndex: number): boolean { const currentAbilityIndex = this.abilityIndex; const rootForm = getPokemonSpecies(this.species.getRootSpeciesId()); - return ( - rootForm.getAbility(abilityIndex) === - rootForm.getAbility(currentAbilityIndex) - ); + return rootForm.getAbility(abilityIndex) === rootForm.getAbility(currentAbilityIndex); } /** @@ -6378,23 +5485,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param forBattle If `false`, do not trigger in-battle effects (such as Unburden) from losing the item. For example, set this to `false` if the Pokemon is giving away the held item for a Mystery Encounter. Default is `true`. * @returns `true` if the item was removed successfully, `false` otherwise. */ - public loseHeldItem( - heldItem: PokemonHeldItemModifier, - forBattle = true, - ): boolean { + public loseHeldItem(heldItem: PokemonHeldItemModifier, forBattle = true): boolean { if (heldItem.pokemonId !== -1 && heldItem.pokemonId !== this.id) { return false; } - heldItem.stackCount--; - if (heldItem.stackCount <= 0) { - globalScene.removeModifier(heldItem, !this.isPlayer()); - } - if (forBattle) { - applyPostItemLostAbAttrs(PostItemLostAbAttr, this, false); - } + heldItem.stackCount--; + if (heldItem.stackCount <= 0) { + globalScene.removeModifier(heldItem, !this.isPlayer()); + } + if (forBattle) { + applyPostItemLostAbAttrs(PostItemLostAbAttr, this, false); + } - return true; + return true; } /** @@ -6403,11 +5507,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param berryType The type of berry being eaten. * @param updateHarvest Whether to track the berry for harvest; default `true`. */ - public recordEatenBerry(berryType: BerryType, updateHarvest: boolean = true) { + public recordEatenBerry(berryType: BerryType, updateHarvest = true) { this.battleData.hasEatenBerry = true; if (updateHarvest) { // Only track for harvest if we actually consumed the berry - this.battleData.berriesEaten.push(berryType) + this.battleData.berriesEaten.push(berryType); } this.turnData.berriesEaten.push(berryType); } @@ -6428,20 +5532,7 @@ export class PlayerPokemon extends Pokemon { nature?: Nature, dataSource?: Pokemon | PokemonData, ) { - super( - 106, - 148, - species, - level, - abilityIndex, - formIndex, - gender, - shiny, - variant, - ivs, - nature, - dataSource, - ); + super(106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource); if (Overrides.STATUS_OVERRIDE) { this.status = new Status(Overrides.STATUS_OVERRIDE, 0, 4); @@ -6504,17 +5595,13 @@ export class PlayerPokemon extends Pokemon { if (Array.isArray(p)) { const [pkm, form] = p; if ( - (pkm === this.species.speciesId || - (this.fusionSpecies && pkm === this.fusionSpecies.speciesId)) && + (pkm === this.species.speciesId || (this.fusionSpecies && pkm === this.fusionSpecies.speciesId)) && form === this.getFormKey() ) { compatible = true; break; } - } else if ( - p === this.species.speciesId || - (this.fusionSpecies && p === this.fusionSpecies.speciesId) - ) { + } else if (p === this.species.speciesId || (this.fusionSpecies && p === this.fusionSpecies.speciesId)) { compatible = true; break; } @@ -6532,8 +5619,7 @@ export class PlayerPokemon extends Pokemon { if ( !this.getSpeciesForm().validateStarterMoveset( moveset, - globalScene.gameData.starterData[this.species.getRootSpeciesId()] - .eggMoves, + globalScene.gameData.starterData[this.species.getRootSpeciesId()].eggMoves, ) ) { return false; @@ -6559,18 +5645,10 @@ export class PlayerPokemon extends Pokemon { UiMode.PARTY, PartyUiMode.FAINT_SWITCH, this.getFieldIndex(), - (slotIndex: number, option: PartyOption) => { - if ( - slotIndex >= globalScene.currentBattle.getBattlerCount() && - slotIndex < 6 - ) { + (slotIndex: number, _option: PartyOption) => { + if (slotIndex >= globalScene.currentBattle.getBattlerCount() && slotIndex < 6) { globalScene.prependToPhase( - new SwitchSummonPhase( - switchType, - this.getFieldIndex(), - slotIndex, - false, - ), + new SwitchSummonPhase(switchType, this.getFieldIndex(), slotIndex, false), MoveEndPhase, ); } @@ -6584,23 +5662,13 @@ export class PlayerPokemon extends Pokemon { addFriendship(friendship: number): void { if (friendship > 0) { const starterSpeciesId = this.species.getRootSpeciesId(); - const fusionStarterSpeciesId = - this.isFusion() && this.fusionSpecies - ? this.fusionSpecies.getRootSpeciesId() - : 0; + const fusionStarterSpeciesId = this.isFusion() && this.fusionSpecies ? this.fusionSpecies.getRootSpeciesId() : 0; const starterData = [ globalScene.gameData.starterData[starterSpeciesId], - fusionStarterSpeciesId - ? globalScene.gameData.starterData[fusionStarterSpeciesId] - : null, + fusionStarterSpeciesId ? globalScene.gameData.starterData[fusionStarterSpeciesId] : null, ].filter(d => !!d); const amount = new NumberHolder(friendship); - globalScene.applyModifier( - PokemonFriendshipBoosterModifier, - true, - this, - amount, - ); + globalScene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount); const candyFriendshipMultiplier = globalScene.gameMode.isClassic ? timedEventManager.getClassicFriendshipMultiplier() : 1; @@ -6609,11 +5677,7 @@ export class PlayerPokemon extends Pokemon { ? 1.5 // Divide candy gain for fusions by 1.5 during events : 2 // 2 for fusions outside events : 1; // 1 for non-fused mons - const starterAmount = new NumberHolder( - Math.floor( - (amount.value * candyFriendshipMultiplier) / fusionReduction, - ), - ); + const starterAmount = new NumberHolder(Math.floor((amount.value * candyFriendshipMultiplier) / fusionReduction)); // Add friendship to this PlayerPokemon this.friendship = Math.min(this.friendship + amount.value, 255); @@ -6622,14 +5686,9 @@ export class PlayerPokemon extends Pokemon { } // Add to candy progress for this mon's starter species and its fused species (if it has one) starterData.forEach((sd: StarterDataEntry, i: number) => { - const speciesId = !i - ? starterSpeciesId - : (fusionStarterSpeciesId as Species); + const speciesId = !i ? starterSpeciesId : (fusionStarterSpeciesId as Species); sd.friendship = (sd.friendship || 0) + starterAmount.value; - if ( - sd.friendship >= - getStarterValueFriendshipCap(speciesStarterCosts[speciesId]) - ) { + if (sd.friendship >= getStarterValueFriendshipCap(speciesStarterCosts[speciesId])) { globalScene.gameData.addStarterCandy(getPokemonSpecies(speciesId), 1); sd.friendship = 0; } @@ -6640,9 +5699,7 @@ export class PlayerPokemon extends Pokemon { } } - getPossibleEvolution( - evolution: SpeciesFormEvolution | null, - ): Promise { + getPossibleEvolution(evolution: SpeciesFormEvolution | null): Promise { if (!evolution) { return new Promise(resolve => resolve(this)); } @@ -6657,9 +5714,7 @@ export class PlayerPokemon extends Pokemon { this.fusionFormIndex = evolution.evoFormKey !== null ? Math.max( - evolutionSpecies.forms.findIndex( - f => f.formKey === evolution.evoFormKey, - ), + evolutionSpecies.forms.findIndex(f => f.formKey === evolution.evoFormKey), 0, ) : this.fusionFormIndex; @@ -6681,9 +5736,7 @@ export class PlayerPokemon extends Pokemon { const formIndex = evolution.evoFormKey !== null && !isFusion ? Math.max( - evolutionSpecies.forms.findIndex( - f => f.formKey === evolution.evoFormKey, - ), + evolutionSpecies.forms.findIndex(f => f.formKey === evolution.evoFormKey), 0, ) : this.formIndex; @@ -6704,10 +5757,7 @@ export class PlayerPokemon extends Pokemon { }); } - evolve( - evolution: SpeciesFormEvolution | null, - preEvolution: PokemonSpeciesForm, - ): Promise { + evolve(evolution: SpeciesFormEvolution | null, preEvolution: PokemonSpeciesForm): Promise { if (!evolution) { return new Promise(resolve => resolve()); } @@ -6723,10 +5773,9 @@ export class PlayerPokemon extends Pokemon { } if (evolution.preFormKey !== null) { const formIndex = Math.max( - (!isFusion || !this.fusionSpecies - ? this.species - : this.fusionSpecies - ).forms.findIndex(f => f.formKey === evolution.evoFormKey), + (!isFusion || !this.fusionSpecies ? this.species : this.fusionSpecies).forms.findIndex( + f => f.formKey === evolution.evoFormKey, + ), 0, ); if (!isFusion) { @@ -6741,18 +5790,12 @@ export class PlayerPokemon extends Pokemon { const preEvoAbilityCount = preEvolution.getAbilityCount(); if ([0, 1, 2].includes(this.abilityIndex)) { // Handles cases where a Pokemon with 3 abilities evolves into a Pokemon with 2 abilities (ie: Eevee -> any Eeveelution) - if ( - this.abilityIndex === 2 && - preEvoAbilityCount === 3 && - abilityCount === 2 - ) { + if (this.abilityIndex === 2 && preEvoAbilityCount === 3 && abilityCount === 2) { this.abilityIndex = 1; } } else { // Prevent pokemon with an illegal ability value from breaking things - console.warn( - "this.abilityIndex is somehow an illegal value, please report this", - ); + console.warn("this.abilityIndex is somehow an illegal value, please report this"); console.warn(this.abilityIndex); this.abilityIndex = 0; } @@ -6761,17 +5804,11 @@ export class PlayerPokemon extends Pokemon { const abilityCount = this.getFusionSpeciesForm().getAbilityCount(); const preEvoAbilityCount = preEvolution.getAbilityCount(); if ([0, 1, 2].includes(this.fusionAbilityIndex)) { - if ( - this.fusionAbilityIndex === 2 && - preEvoAbilityCount === 3 && - abilityCount === 2 - ) { + if (this.fusionAbilityIndex === 2 && preEvoAbilityCount === 3 && abilityCount === 2) { this.fusionAbilityIndex = 1; } } else { - console.warn( - "this.fusionAbilityIndex is somehow an illegal value, please report this", - ); + console.warn("this.fusionAbilityIndex is somehow an illegal value, please report this"); console.warn(this.fusionAbilityIndex); this.fusionAbilityIndex = 0; } @@ -6785,22 +5822,15 @@ export class PlayerPokemon extends Pokemon { }); }; if (preEvolution.speciesId === Species.GIMMIGHOUL) { - const evotracker = - this.getHeldItems().filter(m => m instanceof EvoTrackerModifier)[0] ?? - null; + const evotracker = this.getHeldItems().filter(m => m instanceof EvoTrackerModifier)[0] ?? null; if (evotracker) { globalScene.removeModifier(evotracker); } } if (!globalScene.gameMode.isDaily || this.metBiome > -1) { - globalScene.gameData.updateSpeciesDexIvs( - this.species.speciesId, - this.ivs, - ); + globalScene.gameData.updateSpeciesDexIvs(this.species.speciesId, this.ivs); globalScene.gameData.setPokemonSeen(this, false); - globalScene.gameData - .setPokemonCaught(this, false) - .then(() => updateAndResolve()); + globalScene.gameData.setPokemonCaught(this, false).then(() => updateAndResolve()); } else { updateAndResolve(); } @@ -6811,10 +5841,7 @@ export class PlayerPokemon extends Pokemon { const isFusion = evolution instanceof FusionSpeciesFormEvolution; const evoSpecies = !isFusion ? this.species : this.fusionSpecies; - if ( - evoSpecies?.speciesId === Species.NINCADA && - evolution.speciesId === Species.NINJASK - ) { + if (evoSpecies?.speciesId === Species.NINCADA && evolution.speciesId === Species.NINJASK) { const newEvolution = pokemonEvolutions[evoSpecies.speciesId][1]; if (newEvolution.condition?.predicate(this)) { @@ -6850,12 +5877,7 @@ export class PlayerPokemon extends Pokemon { newPokemon.evoCounter = this.evoCounter; globalScene.getPlayerParty().push(newPokemon); - newPokemon.evolve( - !isFusion - ? newEvolution - : new FusionSpeciesFormEvolution(this.id, newEvolution), - evoSpecies, - ); + newPokemon.evolve(!isFusion ? newEvolution : new FusionSpeciesFormEvolution(this.id, newEvolution), evoSpecies); const modifiers = globalScene.findModifiers( m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.id, true, @@ -6916,9 +5938,7 @@ export class PlayerPokemon extends Pokemon { }; if (!globalScene.gameMode.isDaily || this.metBiome > -1) { globalScene.gameData.setPokemonSeen(this, false); - globalScene.gameData - .setPokemonCaught(this, false) - .then(() => updateAndResolve()); + globalScene.gameData.setPokemonCaught(this, false).then(() => updateAndResolve()); } else { updateAndResolve(); } @@ -6953,8 +5973,7 @@ export class PlayerPokemon extends Pokemon { // Store the average HP% that each Pokemon has const maxHp = this.getMaxHp(); - const newHpPercent = - (pokemon.hp / pokemon.getMaxHp() + this.hp / maxHp) / 2; + const newHpPercent = (pokemon.hp / pokemon.getMaxHp() + this.hp / maxHp) / 2; this.generateName(); this.calculateStats(); @@ -6985,15 +6004,7 @@ export class PlayerPokemon extends Pokemon { true, ) as PokemonHeldItemModifier[]; for (const modifier of fusedPartyMemberHeldModifiers) { - globalScene.tryTransferHeldItemModifier( - modifier, - this, - false, - modifier.getStackCount(), - true, - true, - false, - ); + globalScene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true, false); } globalScene.updateModifiers(true, true); globalScene.removePartyMemberModifiers(fusedPartyMemberIndex); @@ -7001,11 +6012,7 @@ 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.unshiftPhase(new LearnMovePhase(newPartyMemberIndex, m.getMove().id))); pokemon.destroy(); this.updateFusionPalette(); } @@ -7107,17 +6114,13 @@ export class EnemyPokemon extends Pokemon { } } - this.luck = - (this.shiny ? this.variant + 1 : 0) + - (this.fusionShiny ? this.fusionVariant + 1 : 0); + this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0); let prevolution: Species; let speciesId = species.speciesId; while ((prevolution = pokemonPrevolutions[speciesId])) { const evolution = pokemonEvolutions[prevolution].find( - pe => - pe.speciesId === speciesId && - (!pe.evoFormKey || pe.evoFormKey === this.getFormKey()), + pe => pe.speciesId === speciesId && (!pe.evoFormKey || pe.evoFormKey === this.getFormKey()), ); if (evolution?.condition?.enforceFunc) { evolution.condition.enforceFunc(this); @@ -7135,8 +6138,7 @@ export class EnemyPokemon extends Pokemon { } } - this.aiType = - boss || this.hasTrainer() ? AiType.SMART : AiType.SMART_RANDOM; + this.aiType = boss || this.hasTrainer() ? AiType.SMART : AiType.SMART_RANDOM; } initBattleInfo(): void { @@ -7160,12 +6162,7 @@ export class EnemyPokemon extends Pokemon { if (boss) { this.bossSegments = bossSegments || - globalScene.getEncounterBossSegments( - globalScene.currentBattle.waveIndex, - this.level, - this.species, - true, - ); + globalScene.getEncounterBossSegments(globalScene.currentBattle.waveIndex, this.level, this.species, true); this.bossSegmentIndex = this.bossSegments - 1; } else { this.bossSegments = 0; @@ -7219,12 +6216,14 @@ export class EnemyPokemon extends Pokemon { 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) { + if ( + (moveIndex > -1 && this.getMoveset()[moveIndex].isUsable(this, queuedMove.ignorePP)) || + queuedMove.virtual + ) { return queuedMove; - } else { - this.getMoveQueue().shift(); - return this.getNextMove(); } + this.getMoveQueue().shift(); + return this.getNextMove(); } } @@ -7248,11 +6247,13 @@ export class EnemyPokemon extends Pokemon { } } switch (this.aiType) { - case AiType.RANDOM: // No enemy should spawn with this AI type in-game + // 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) }; + } case AiType.SMART_RANDOM: - case AiType.SMART: + case AiType.SMART: { /** * Search this Pokemon's move pool for moves that will KO an opposing target. * If there are any moves that can KO an opponent (i.e. a player Pokemon), @@ -7273,20 +6274,14 @@ 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 && moveTargets.some(p => { const doesNotFail = move.applyConditions(this, p, move) || - [ - Moves.SUCKER_PUNCH, - Moves.UPPER_HAND, - Moves.THUNDERCLAP, - ].includes(move.id); + [Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP].includes(move.id); return ( doesNotFail && p.getAttackDamage({ @@ -7297,8 +6292,7 @@ export class EnemyPokemon extends Pokemon { ignoreAllyAbility: !p.getAlly()?.waveData.abilityRevealed, ignoreSourceAllyAbility: false, isCritical, - } - ).damage >= p.hp + }).damage >= p.hp ); }) ); @@ -7314,7 +6308,7 @@ export class EnemyPokemon extends Pokemon { * For more information on how benefit scores are calculated, see `docs/enemy-ai.md`. */ const moveScores = movePool.map(() => 0); - const moveTargets = Object.fromEntries(movePool.map(m => [ m.moveId, this.getNextTargets(m.moveId) ])); + const moveTargets = Object.fromEntries(movePool.map(m => [m.moveId, this.getNextTargets(m.moveId)])); for (const m in movePool) { const pokemonMove = movePool[m]; const move = pokemonMove.getMove(); @@ -7335,8 +6329,7 @@ export class EnemyPokemon extends Pokemon { */ let targetScore = move.getUserBenefitScore(this, target, move) + - move.getTargetBenefitScore(this, target, move) * - (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1); + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1); if (Number.isNaN(targetScore)) { console.error(`Move ${move.name} returned score of NaN`); targetScore = 0; @@ -7346,27 +6339,23 @@ export class EnemyPokemon extends Pokemon { * target score to -20 */ if ( - (move.name.endsWith(" (N)") || - !move.applyConditions(this, target, move)) && - ![ - Moves.SUCKER_PUNCH, - Moves.UPPER_HAND, - Moves.THUNDERCLAP, - ].includes(move.id) + (move.name.endsWith(" (N)") || !move.applyConditions(this, target, move)) && + ![Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP].includes(move.id) ) { targetScore = -20; } else if (move instanceof 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. - */ + */ const effectiveness = target.getMoveEffectiveness( this, move, !target.waveData.abilityRevealed, undefined, undefined, - true); + true, + ); if (target.isPlayer() !== this.isPlayer()) { targetScore *= effectiveness; @@ -7405,18 +6394,14 @@ export class EnemyPokemon extends Pokemon { let r = 0; if (this.aiType === AiType.SMART_RANDOM) { // Has a 5/8 chance to select the best move, and a 3/8 chance to advance to the next best move (and repeat this roll) - while ( - r < sortedMovePool.length - 1 && - globalScene.randBattleSeedInt(8) >= 5 - ) { + while (r < sortedMovePool.length - 1 && globalScene.randBattleSeedInt(8) >= 5) { r++; } } else if (this.aiType === AiType.SMART) { // The chance to advance to the next best move increases when the compared moves' scores are closer to each other. while ( r < sortedMovePool.length - 1 && - moveScores[movePool.indexOf(sortedMovePool[r + 1])] / - moveScores[movePool.indexOf(sortedMovePool[r])] >= + moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])] >= 0 && globalScene.randBattleSeedInt(100) < Math.round( @@ -7428,8 +6413,14 @@ export class EnemyPokemon extends Pokemon { r++; } } - console.log(movePool.map(m => m.getName()), moveScores, r, sortedMovePool.map(m => m.getName())); + console.log( + movePool.map(m => m.getName()), + moveScores, + r, + sortedMovePool.map(m => m.getName()), + ); return { move: sortedMovePool[r]!.moveId, targets: moveTargets[sortedMovePool[r]!.moveId] }; + } } } @@ -7446,9 +6437,7 @@ export class EnemyPokemon extends Pokemon { */ getNextTargets(moveId: Moves): BattlerIndex[] { const moveTargets = getMoveTargets(this, moveId); - const targets = globalScene - .getField(true) - .filter(p => moveTargets.targets.indexOf(p.getBattlerIndex()) > -1); + const targets = globalScene.getField(true).filter(p => moveTargets.targets.indexOf(p.getBattlerIndex()) > -1); // If the move is multi-target, return all targets' indexes if (moveTargets.multiple) { return targets.map(p => p.getBattlerIndex()); @@ -7462,8 +6451,7 @@ export class EnemyPokemon extends Pokemon { */ const benefitScores = targets.map(p => [ p.getBattlerIndex(), - move.getTargetBenefitScore(this, p, move) * - (p.isPlayer() === this.isPlayer() ? 1 : -1), + move.getTargetBenefitScore(this, p, move) * (p.isPlayer() === this.isPlayer() ? 1 : -1), ]); const sortedBenefitScores = benefitScores.slice(0); @@ -7494,9 +6482,7 @@ export class EnemyPokemon extends Pokemon { } // Remove any targets whose weights are less than half the max of the target weights from consideration - const benefitCutoffIndex = targetWeights.findIndex( - s => s < targetWeights[0] / 2, - ); + const benefitCutoffIndex = targetWeights.findIndex(s => s < targetWeights[0] / 2); if (benefitCutoffIndex > -1) { targetWeights = targetWeights.slice(0, benefitCutoffIndex); } @@ -7555,12 +6541,7 @@ export class EnemyPokemon extends Pokemon { return 0; } - damage( - damage: number, - ignoreSegments = false, - preventEndure = false, - ignoreFaintPhase = false, - ): number { + damage(damage: number, ignoreSegments = false, preventEndure = false, ignoreFaintPhase = false): number { if (this.isFainted()) { return 0; } @@ -7579,16 +6560,13 @@ export class EnemyPokemon extends Pokemon { while ( segmentsBypassed < this.bossSegmentIndex && this.canBypassBossSegments(segmentsBypassed + 1) && - damage - hpRemainder >= - Math.round(segmentSize * Math.pow(2, segmentsBypassed + 1)) + damage - hpRemainder >= Math.round(segmentSize * Math.pow(2, segmentsBypassed + 1)) ) { segmentsBypassed++; //console.log('damage', damage, 'segment', segmentsBypassed + 1, 'segment size', segmentSize, 'damage needed', Math.round(segmentSize * Math.pow(2, segmentsBypassed + 1))); } - damage = toDmgValue( - this.hp - hpThreshold + segmentSize * segmentsBypassed, - ); + damage = toDmgValue(this.hp - hpThreshold + segmentSize * segmentsBypassed); clearedBossSegmentIndex = s - segmentsBypassed; } break; @@ -7603,12 +6581,7 @@ export class EnemyPokemon extends Pokemon { } } - const ret = super.damage( - damage, - ignoreSegments, - preventEndure, - ignoreFaintPhase, - ); + const ret = super.damage(damage, ignoreSegments, preventEndure, ignoreFaintPhase); if (this.isBoss()) { if (ignoreSegments) { @@ -7642,17 +6615,10 @@ export class EnemyPokemon extends Pokemon { * @param segmentIndex index of the segment to get down to (0 = no shield left, 1 = 1 shield left, etc.) */ handleBossSegmentCleared(segmentIndex: number): void { - while ( - this.bossSegmentIndex > 0 && - segmentIndex - 1 < this.bossSegmentIndex - ) { + while (this.bossSegmentIndex > 0 && segmentIndex - 1 < this.bossSegmentIndex) { // Filter out already maxed out stat stages and weigh the rest based on existing stats - const leftoverStats = EFFECTIVE_STATS.filter( - (s: EffectiveStat) => this.getStatStage(s) < 6, - ); - const statWeights = leftoverStats.map((s: EffectiveStat) => - this.getStat(s, false), - ); + const leftoverStats = EFFECTIVE_STATS.filter((s: EffectiveStat) => this.getStatStage(s) < 6); + const statWeights = leftoverStats.map((s: EffectiveStat) => this.getStat(s, false)); let boostedStat: EffectiveStat; const statThresholds: number[] = []; @@ -7684,14 +6650,7 @@ export class EnemyPokemon extends Pokemon { } globalScene.unshiftPhase( - new StatStageChangePhase( - this.getBattlerIndex(), - true, - [boostedStat!], - stages, - true, - true, - ), + new StatStageChangePhase(this.getBattlerIndex(), true, [boostedStat!], stages, true, true), ); this.bossSegmentIndex--; } @@ -7745,11 +6704,7 @@ export class EnemyPokemon extends Pokemon { newPokemon.setVisible(false); ret = newPokemon; - globalScene.triggerPokemonFormChange( - newPokemon, - SpeciesFormChangeActiveTrigger, - true, - ); + globalScene.triggerPokemonFormChange(newPokemon, SpeciesFormChangeActiveTrigger, true); } return ret; @@ -7789,7 +6744,7 @@ interface IllusionData { /** The fusionGender of the illusion if it's a fusion */ fusionGender?: Gender; /** The level of the illusion (not used currently) */ - level?: number + level?: number; } export interface TurnMove { @@ -7838,7 +6793,7 @@ export class PokemonSummonData { /** Data pertaining to this pokemon's illusion. */ public illusion: IllusionData | null = null; - public illusionBroken: boolean = false; + public illusionBroken = false; /** Array containing all berries eaten in the last turn; used by {@linkcode Abilities.CUD_CHEW} */ public berriesEatenLast: BerryType[] = []; @@ -7875,15 +6830,15 @@ export class PokemonSummonData { } } - // TODO: Merge this inside `summmonData` but exclude from save if/when a save data serializer is added +// TODO: Merge this inside `summmonData` but exclude from save if/when a save data serializer is added export class PokemonTempSummonData { /** * The number of turns this pokemon has spent without switching out. * Only currently used for positioning the battle cursor. */ - turnCount: number = 1; + turnCount = 1; - /** + /** * The number of turns this pokemon has spent in the active position since the start of the wave * without switching out. * Reset on switch and new wave, but not stored in `SummonData` to avoid being written to the save file. @@ -7892,7 +6847,6 @@ export class PokemonTempSummonData { * {@linkcode Moves.FAKE_OUT | Fake Out} and {@linkcode Moves.FIRST_IMPRESSION | First Impression}). */ waveTurnCount = 1; - } /** @@ -7903,7 +6857,7 @@ export class PokemonBattleData { /** Counter tracking direct hits this Pokemon has received during this battle; used for {@linkcode Moves.RAGE_FIST} */ public hitCount = 0; /** Whether this Pokemon has eaten a berry this battle; used for {@linkcode Moves.BELCH} */ - public hasEatenBerry: boolean = false; + public hasEatenBerry = false; /** Array containing all berries eaten and not yet recovered during this current battle; used by {@linkcode Abilities.HARVEST} */ public berriesEaten: BerryType[] = []; @@ -7927,7 +6881,7 @@ export class PokemonWaveData { * A set of all the abilities this {@linkcode Pokemon} has used in this wave. * Used to track once per battle conditions, as well as (hopefully) by the updated AI for move effectiveness. */ - public abilitiesApplied: Set = new Set; + public abilitiesApplied: Set = new Set(); /** Whether the pokemon's ability has been revealed or not */ public abilityRevealed = false; } @@ -7967,8 +6921,8 @@ export class PokemonTurnData { * All berries eaten by this pokemon in this turn. * Saved into {@linkcode PokemonSummonData | SummonData} by {@linkcode Abilities.CUD_CHEW} on turn end. * @see {@linkcode PokemonSummonData.berriesEatenLast} - */ - public berriesEaten: BerryType[] = [] + */ + public berriesEaten: BerryType[] = []; } export enum AiType { @@ -8045,13 +6999,7 @@ export class PokemonMove { */ public maxPpOverride?: number; - constructor( - moveId: Moves, - ppUsed = 0, - ppUp = 0, - virtual = false, - maxPpOverride?: number, - ) { + constructor(moveId: Moves, ppUsed = 0, ppUp = 0, virtual = false, maxPpOverride?: number) { this.moveId = moveId; this.ppUsed = ppUsed; this.ppUp = ppUp; @@ -8063,21 +7011,13 @@ export class PokemonMove { * 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} pokemon {@linkcode Pokemon} that would be using this move - * @param {boolean} ignorePp If `true`, skips the PP check - * @param {boolean} ignoreRestrictionTags If `true`, skips the check for move restriction tags (see {@link MoveRestrictionBattlerTag}) + * @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) - ) { + isUsable(pokemon: Pokemon, ignorePp = false, ignoreRestrictionTags = false): boolean { + if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon)) { return false; } @@ -8085,9 +7025,7 @@ export class PokemonMove { return false; } - return ( - ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1 - ); + return ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1; } getMove(): Move { @@ -8098,15 +7036,12 @@ export class PokemonMove { * Sets {@link ppUsed} for this move and ensures the value does not exceed {@link getMovePp} * @param count Amount of PP to use */ - usePp(count: number = 1) { + 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) - ); + return this.maxPpOverride || this.getMove().pp + this.ppUp * toDmgValue(this.getMove().pp / 5); } getPpRatio(): number { @@ -8123,12 +7058,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.virtual, source.maxPpOverride); } } diff --git a/src/phases/revival-blessing-phase.ts b/src/phases/revival-blessing-phase.ts index 598d9109abc..428acaf9ed4 100644 --- a/src/phases/revival-blessing-phase.ts +++ b/src/phases/revival-blessing-phase.ts @@ -24,7 +24,7 @@ export class RevivalBlessingPhase extends BattlePhase { UiMode.PARTY, PartyUiMode.REVIVAL_BLESSING, this.user.getFieldIndex(), - (slotIndex: integer, _option: PartyOption) => { + (slotIndex: number, _option: PartyOption) => { if (slotIndex >= 0 && slotIndex < 6) { const pokemon = globalScene.getPlayerParty()[slotIndex]; if (!pokemon || !pokemon.isFainted()) { diff --git a/src/ui/login-form-ui-handler.ts b/src/ui/login-form-ui-handler.ts index 714a9b39771..5c48cf55753 100644 --- a/src/ui/login-form-ui-handler.ts +++ b/src/ui/login-form-ui-handler.ts @@ -151,9 +151,9 @@ export default class LoginFormUiHandler extends FormModalUiHandler { // Prevent overlapping overrides on action modification this.submitAction = originalLoginAction; this.sanitizeInputs(); - globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] }); + globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] }); const onFail = error => { - globalScene.ui.setMode(UiMode.LOGIN_FORM, Object.assign(config, { errorMessage: error?.trim() })); + globalScene.ui.setMode(UiMode.LOGIN_FORM, Object.assign(config, { errorMessage: error?.trim() })); globalScene.ui.playError(); }; if (!this.inputs[0].text) { @@ -243,7 +243,7 @@ export default class LoginFormUiHandler extends FormModalUiHandler { }, }); } - globalScene.ui.setOverlayMode(UiMode.OPTION_SELECT, { + globalScene.ui.setOverlayMode(UiMode.OPTION_SELECT, { options: options, delay: 1000, }); From 6c676f1f11215a23826432bd552be5cd22c1195e Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Tue, 27 May 2025 15:41:06 -0500 Subject: [PATCH 08/14] [Misc] Add decrypt-save.js utility script (#5731) * Add decrypt-save.js * Update scripts/decrypt-save.js Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com> --------- Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com> --- scripts/decrypt-save.js | 149 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 scripts/decrypt-save.js diff --git a/scripts/decrypt-save.js b/scripts/decrypt-save.js new file mode 100644 index 00000000000..a7239a40df6 --- /dev/null +++ b/scripts/decrypt-save.js @@ -0,0 +1,149 @@ +import pkg from "crypto-js"; +const { AES, enc } = pkg; +// biome-ignore lint: This is how you import fs from node +import * as fs from "node:fs"; + +const SAVE_KEY = "x0i2O7WRiANTqPmZ"; + +/** + * A map of condensed keynames to their associated full names + * NOTE: Update this if `src/system/game-data#systemShortKeys` ever changes! + */ +const systemShortKeys = { + seenAttr: "$sa", + caughtAttr: "$ca", + natureAttr: "$na", + seenCount: "$s", + caughtCount: "$c", + hatchedCount: "$hc", + ivs: "$i", + moveset: "$m", + eggMoves: "$em", + candyCount: "$x", + friendship: "$f", + abilityAttr: "$a", + passiveAttr: "$pa", + valueReduction: "$vr", + classicWinCount: "$wc", +}; + +/** + * Replace the shortened key names with their full names + * @param {string} dataStr - The string to convert + * @returns {string} The string with shortened keynames replaced with full names + */ +function convertSystemDataStr(dataStr) { + const fromKeys = Object.values(systemShortKeys); + const toKeys = Object.keys(systemShortKeys); + for (const k in fromKeys) { + dataStr = dataStr.replace(new RegExp(`${fromKeys[k].replace("$", "\\$")}`, "g"), toKeys[k]); + } + + return dataStr; +} + +/** + * Decrypt a save + * @param {string} path - The path to the encrypted save file + * @returns {string} The decrypted save data + */ +function decryptSave(path) { + // Check if the file exists + if (!fs.existsSync(path)) { + console.error(`File not found: ${path}`); + process.exit(1); + } + let fileData; + try { + fileData = fs.readFileSync(path, "utf8"); + } catch (e) { + switch (e.code) { + case "ENOENT": + console.error(`File not found: ${path}`); + break; + case "EACCES": + console.error(`Could not open ${path}: Permission denied`); + break; + case "EISDIR": + console.error(`Unable to read ${path} as it is a directory`); + break; + default: + console.error(`Error reading file: ${e.message}`); + } + process.exit(1); + } + return convertSystemDataStr(AES.decrypt(fileData, SAVE_KEY).toString(enc.Utf8)); +} + +/* Print the usage message and exits */ +function printUsage() { + console.log(` +Usage: node decrypt-save.js [save-file] + +Arguments: + file-path Path to the encrypted save file to decrypt. + save-file Path to where the decrypted data should be written. If not provided, the decrypted data will be printed to the console. + +Options: + -h, --help Show this help message and exit. + +Description: + This script decrypts an encrypted pokerogue save file +`); +} + +/** + * Write `data` to `filePath`, gracefully communicating errors that arise + * @param {string} filePath + * @param {string} data + */ +function writeToFile(filePath, data) { + try { + fs.writeFileSync(filePath, data); + } catch (e) { + switch (e.code) { + case "EACCES": + console.error(`Could not open ${filePath}: Permission denied`); + break; + case "EISDIR": + console.error(`Unable to write to ${filePath} as it is a directory`); + break; + default: + console.error(`Error writing file: ${e.message}`); + } + process.exit(1); + } +} + +function main() { + let args = process.argv.slice(2); + // Get options + const options = args.filter(arg => arg.startsWith("-")); + // get args + args = args.filter(arg => !arg.startsWith("-")); + + if (args.length === 0 || options.includes("-h") || options.includes("--help") || args.length > 2) { + printUsage(); + process.exit(0); + } + // If the user provided a second argument, check if the file exists already and refuse to write to it. + if (args.length === 2) { + const destPath = args[1]; + if (fs.existsSync(destPath)) { + console.error(`Refusing to overwrite ${destPath}`); + process.exit(1); + } + } + + // Otherwise, commence decryption. + const decrypt = decryptSave(args[0]); + + if (args.length === 1) { + process.stdout.write(decrypt); + process.exit(0); + } + + writeToFile(destPath, decrypt); +} + +main(); From 2deced5565c92e5f9d99a6cd8fbb683d767ce105 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Tue, 27 May 2025 16:46:56 -0500 Subject: [PATCH 09/14] [Bug][Move] Allow gastro acid to suppress passives if main ability is unsuppressable (#5854) * Allow gastro acid to suppress passives if main ability is unsuppressable * Update gastro_acid.test.ts * Update src/data/moves/move.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Add test to ensure unsuppressable main ability is not suppressed --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/moves/move.ts | 2 +- test/moves/gastro_acid.test.ts | 29 ++++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 31ad3337926..8a0da5f35c2 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -7521,7 +7521,7 @@ export class SuppressAbilitiesAttr extends MoveEffectAttr { /** Causes the effect to fail when the target's ability is unsupressable or already suppressed. */ getCondition(): MoveConditionFunc { - return (user, target, move) => target.getAbility().isSuppressable && !target.summonData.abilitySuppressed; + return (_user, target, _move) => !target.summonData.abilitySuppressed && (target.getAbility().isSuppressable || (target.hasPassive() && target.getPassiveAbility().isSuppressable)); } } diff --git a/test/moves/gastro_acid.test.ts b/test/moves/gastro_acid.test.ts index 8247d29c0a0..333619d16db 100644 --- a/test/moves/gastro_acid.test.ts +++ b/test/moves/gastro_acid.test.ts @@ -25,7 +25,7 @@ describe("Moves - Gastro Acid", () => { game.override.battleStyle("double"); game.override.startingLevel(1); game.override.enemyLevel(100); - game.override.ability(Abilities.NONE); + game.override.ability(Abilities.BALL_FETCH); game.override.moveset([Moves.GASTRO_ACID, Moves.WATER_GUN, Moves.SPLASH, Moves.CORE_ENFORCER]); game.override.enemySpecies(Species.BIDOOF); game.override.enemyMoveset(Moves.SPLASH); @@ -40,7 +40,7 @@ describe("Moves - Gastro Acid", () => { * - player mon 1 should have dealt damage, player mon 2 should have not */ - await game.startBattle(); + await game.classicMode.startBattle(); game.move.select(Moves.GASTRO_ACID, 0, BattlerIndex.ENEMY); game.move.select(Moves.SPLASH, 1); @@ -63,7 +63,7 @@ describe("Moves - Gastro Acid", () => { it("fails if used on an enemy with an already-suppressed ability", async () => { game.override.battleStyle("single"); - await game.startBattle(); + await game.classicMode.startBattle(); game.move.select(Moves.CORE_ENFORCER); // Force player to be slower to enable Core Enforcer to proc its suppression effect @@ -77,4 +77,27 @@ describe("Moves - Gastro Acid", () => { expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL); }); + + it("should suppress the passive of a target even if its main ability is unsuppressable and not suppress main abli", async () => { + game.override + .enemyAbility(Abilities.COMATOSE) + .enemyPassiveAbility(Abilities.WATER_ABSORB) + .moveset([Moves.SPLASH, Moves.GASTRO_ACID, Moves.WATER_GUN]); + await game.classicMode.startBattle([Species.MAGIKARP]); + + const enemyPokemon = game.scene.getEnemyPokemon(); + + game.move.select(Moves.GASTRO_ACID); + await game.toNextTurn(); + expect(enemyPokemon?.summonData.abilitySuppressed).toBe(true); + + game.move.select(Moves.WATER_GUN); + await game.toNextTurn(); + expect(enemyPokemon?.getHpRatio()).toBeLessThan(1); + + game.move.select(Moves.SPORE); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.status?.effect).toBeFalsy(); + }); }); From c236996a02f581f9fd54c873d4aba5cfd4836eeb Mon Sep 17 00:00:00 2001 From: Lugiad <2070109+Adri1@users.noreply.github.com> Date: Wed, 28 May 2025 00:08:49 +0200 Subject: [PATCH 10/14] [UI/UX] [Localization] starterInfoText adjustments and clean up (#5859) * starterInfoText adjustments and clean up * starterInfoText adjustments and clean up * Update starter-select-ui-handler.ts * Update starter-select-ui-handler.ts --------- Co-authored-by: Wlowscha <54003515+Wlowscha@users.noreply.github.com> --- src/ui/starter-select-ui-handler.ts | 41 +++++++++++++++-------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 80acac6a6b4..a971ba86479 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -108,17 +108,21 @@ const languageSettings: { [key: string]: LanguageSetting } = { instructionTextSize: "38px", }, de: { - starterInfoTextSize: "48px", + starterInfoTextSize: "54px", instructionTextSize: "35px", - starterInfoXPos: 33, + starterInfoXPos: 35, }, "es-ES": { - starterInfoTextSize: "52px", - instructionTextSize: "35px", + starterInfoTextSize: "50px", + instructionTextSize: "38px", + starterInfoYOffset: 0.5, + starterInfoXPos: 38, }, "es-MX": { - starterInfoTextSize: "52px", - instructionTextSize: "35px", + starterInfoTextSize: "50px", + instructionTextSize: "38px", + starterInfoYOffset: 0.5, + starterInfoXPos: 38, }, fr: { starterInfoTextSize: "54px", @@ -128,21 +132,16 @@ const languageSettings: { [key: string]: LanguageSetting } = { starterInfoTextSize: "56px", instructionTextSize: "38px", }, - pt_BR: { - starterInfoTextSize: "47px", - instructionTextSize: "38px", + "pt-BR": { + starterInfoTextSize: "48px", + instructionTextSize: "42px", + starterInfoYOffset: 0.5, starterInfoXPos: 33, }, zh: { - starterInfoTextSize: "47px", - instructionTextSize: "38px", - starterInfoYOffset: 1, - starterInfoXPos: 24, - }, - pt: { - starterInfoTextSize: "48px", - instructionTextSize: "42px", - starterInfoXPos: 33, + starterInfoTextSize: "56px", + instructionTextSize: "36px", + starterInfoXPos: 26, }, ko: { starterInfoTextSize: "60px", @@ -156,9 +155,11 @@ const languageSettings: { [key: string]: LanguageSetting } = { starterInfoYOffset: 0.5, starterInfoXPos: 33, }, - "ca-ES": { - starterInfoTextSize: "52px", + ca: { + starterInfoTextSize: "48px", instructionTextSize: "38px", + starterInfoYOffset: 0.5, + starterInfoXPos: 29, }, }; From d5789105f344abd6b6234d2b14410d03ae91e4bf Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Wed, 28 May 2025 12:29:03 -0500 Subject: [PATCH 11/14] [Refactor][UI/UX] Cleanup battle-info ui code (#5696) * Create battle-info directory and move battle-info.ts to it * Move player and enemy battle info to their own files * Move subclass specific parts of constructor to subclass constructor * Fixup mock gameobject methods to match phaser gameobject returns * Make statOrder specific to subclass * Create getShinyDescriptor function in utils * Move icon construction to its own function * Cleanup enemybattleinfo constructor to use chaining * Make flyout exclusive to EnemyBattleInfo * Move EnemyPokemon specific init Logic to its class * Break up initInfo into different methods * Remove hp bar segment dividers from base battle info * Move setMini to pokemoninfo * Breakup updateInfo into smaller parts * Remove hp info handling from base updateInfo * Use phaser object chaining methods * Add some docs * Add missing chain usage * Use getShinyDescriptor in pokemon-info-container * Minor cleanup of updatePokemonExp * Fixup setSizeToFrame mock * Ensure pokemon hp numbers are not visible during stat display * Update src/utils/common.ts Co-authored-by: Wlowscha <54003515+Wlowscha@users.noreply.github.com> * Make summary-ui-handler use new shinyDescriptor method * Remove `undefined` parameter pass Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com> * Address kev's review comments Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Ensure hp number display fades in/out * Ensure ribbon and caught indicator fade with stat display * Update src/ui/battle-info/battle-info.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Move construction of stats and type icons to their own methods * Make setPositionRelative return this * Improve doc comment on paddingX param * Fix mock sprite's setPositionRelative --------- Co-authored-by: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/field/pokemon.ts | 31 +- src/main.ts | 2 +- src/typings/phaser/index.d.ts | 12 +- src/ui-inputs.ts | 2 +- src/ui/battle-flyout.ts | 4 +- src/ui/battle-info.ts | 986 ------------------ src/ui/battle-info/battle-info.ts | 688 ++++++++++++ src/ui/battle-info/enemy-battle-info.ts | 235 +++++ src/ui/battle-info/player-battle-info.ts | 242 +++++ src/ui/fight-ui-handler.ts | 6 +- src/ui/pokemon-info-container.ts | 27 +- src/ui/summary-ui-handler.ts | 26 +- src/utils/common.ts | 16 + test/testUtils/mocks/mockGameObject.ts | 1 + .../mocks/mocksContainer/mockContainer.ts | 187 ++-- .../mocks/mocksContainer/mockGraphics.ts | 53 +- .../mocks/mocksContainer/mockRectangle.ts | 42 +- .../mocks/mocksContainer/mockSprite.ts | 153 +-- .../mocks/mocksContainer/mockText.ts | 104 +- test/testUtils/testFileInitialization.ts | 4 +- 20 files changed, 1602 insertions(+), 1219 deletions(-) delete mode 100644 src/ui/battle-info.ts create mode 100644 src/ui/battle-info/battle-info.ts create mode 100644 src/ui/battle-info/enemy-battle-info.ts create mode 100644 src/ui/battle-info/player-battle-info.ts diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 85b003517a6..329ba06fd09 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -5,7 +5,9 @@ import { globalScene } from "#app/global-scene"; import type { Variant } from "#app/sprites/variant"; import { populateVariantColors, variantColorCache } from "#app/sprites/variant"; import { variantData } from "#app/sprites/variant"; -import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "#app/ui/battle-info"; +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, @@ -3347,22 +3349,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.battleInfo.updateInfo(this, instant); } - /** - * Show or hide the type effectiveness multiplier window - * Passing undefined will hide the window - */ - updateEffectiveness(effectiveness?: string) { - this.battleInfo.updateEffectiveness(effectiveness); - } - toggleStats(visible: boolean): void { this.battleInfo.toggleStats(visible); } - toggleFlyout(visible: boolean): void { - this.battleInfo.toggleFlyout(visible); - } - /** * Adds experience to this PlayerPokemon, subject to wave based level caps. * @param exp The amount of experience to add @@ -5518,6 +5508,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } export class PlayerPokemon extends Pokemon { + protected battleInfo: PlayerBattleInfo; public compatibleTms: Moves[]; constructor( @@ -6038,6 +6029,7 @@ export class PlayerPokemon extends Pokemon { } export class EnemyPokemon extends Pokemon { + protected battleInfo: EnemyBattleInfo; public trainerSlot: TrainerSlot; public aiType: AiType; public bossSegments: number; @@ -6709,6 +6701,19 @@ export class EnemyPokemon extends Pokemon { return ret; } + + + /** + * Show or hide the type effectiveness multiplier window + * Passing undefined will hide the window + */ + updateEffectiveness(effectiveness?: string) { + this.battleInfo.updateEffectiveness(effectiveness); + } + + toggleFlyout(visible: boolean): void { + this.battleInfo.toggleFlyout(visible); + } } /** diff --git a/src/main.ts b/src/main.ts index 7db663d14c7..38bfcbe5636 100644 --- a/src/main.ts +++ b/src/main.ts @@ -29,7 +29,7 @@ window.addEventListener("unhandledrejection", event => { const setPositionRelative = function (guideObject: Phaser.GameObjects.GameObject, x: number, y: number) { const offsetX = guideObject.width * (-0.5 + (0.5 - guideObject.originX)); const offsetY = guideObject.height * (-0.5 + (0.5 - guideObject.originY)); - this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y); + return this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y); }; Phaser.GameObjects.Container.prototype.setPositionRelative = setPositionRelative; diff --git a/src/typings/phaser/index.d.ts b/src/typings/phaser/index.d.ts index f3665768cec..26fbcff75bd 100644 --- a/src/typings/phaser/index.d.ts +++ b/src/typings/phaser/index.d.ts @@ -20,37 +20,37 @@ declare module "phaser" { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): this; } interface Sprite { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): this; } interface Image { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): this; } interface NineSlice { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): this; } interface Text { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): this; } interface Rectangle { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): this; } } diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index 0c13cdb9512..e4f11e1c93c 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -161,7 +161,7 @@ export class UiInputs { buttonInfo(pressed = true): void { if (globalScene.showMovesetFlyout) { - for (const p of globalScene.getField().filter(p => p?.isActive(true))) { + for (const p of globalScene.getEnemyField().filter(p => p?.isActive(true))) { p.toggleFlyout(pressed); } } diff --git a/src/ui/battle-flyout.ts b/src/ui/battle-flyout.ts index e590bebcf5a..f8ef5fc1ec4 100644 --- a/src/ui/battle-flyout.ts +++ b/src/ui/battle-flyout.ts @@ -1,4 +1,4 @@ -import type { default as Pokemon } from "../field/pokemon"; +import type { EnemyPokemon, default as Pokemon } from "../field/pokemon"; import { addTextObject, TextStyle } from "./text"; import { fixedInt } from "#app/utils/common"; import { globalScene } from "#app/global-scene"; @@ -126,7 +126,7 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { * Links the given {@linkcode Pokemon} and subscribes to the {@linkcode BattleSceneEventType.MOVE_USED} event * @param pokemon {@linkcode Pokemon} to link to this flyout */ - initInfo(pokemon: Pokemon) { + initInfo(pokemon: EnemyPokemon) { this.pokemon = pokemon; this.name = `Flyout ${getPokemonNameWithAffix(this.pokemon)}`; diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts deleted file mode 100644 index 2822e8364ec..00000000000 --- a/src/ui/battle-info.ts +++ /dev/null @@ -1,986 +0,0 @@ -import type { EnemyPokemon, default as Pokemon } from "../field/pokemon"; -import { getLevelTotalExp, getLevelRelExp } from "../data/exp"; -import { getLocalizedSpriteKey, fixedInt } from "#app/utils/common"; -import { addTextObject, TextStyle } from "./text"; -import { getGenderSymbol, getGenderColor, Gender } from "../data/gender"; -import { StatusEffect } from "#enums/status-effect"; -import { globalScene } from "#app/global-scene"; -import { getTypeRgb } from "#app/data/type"; -import { PokemonType } from "#enums/pokemon-type"; -import { getVariantTint } from "#app/sprites/variant"; -import { Stat } from "#enums/stat"; -import BattleFlyout from "./battle-flyout"; -import { WindowVariant, addWindow } from "./ui-theme"; -import i18next from "i18next"; -import { ExpGainsSpeed } from "#app/enums/exp-gains-speed"; - -export default class BattleInfo extends Phaser.GameObjects.Container { - public static readonly EXP_GAINS_DURATION_BASE = 1650; - - private baseY: number; - - private player: boolean; - private mini: boolean; - private boss: boolean; - private bossSegments: number; - private offset: boolean; - private lastName: string | null; - private lastTeraType: PokemonType; - private lastStatus: StatusEffect; - private lastHp: number; - private lastMaxHp: number; - private lastHpFrame: string | null; - private lastExp: number; - private lastLevelExp: number; - private lastLevel: number; - private lastLevelCapped: boolean; - private lastStats: string; - - private box: Phaser.GameObjects.Sprite; - private nameText: Phaser.GameObjects.Text; - private genderText: Phaser.GameObjects.Text; - private ownedIcon: Phaser.GameObjects.Sprite; - private championRibbon: Phaser.GameObjects.Sprite; - private teraIcon: Phaser.GameObjects.Sprite; - private shinyIcon: Phaser.GameObjects.Sprite; - private fusionShinyIcon: Phaser.GameObjects.Sprite; - private splicedIcon: Phaser.GameObjects.Sprite; - private statusIndicator: Phaser.GameObjects.Sprite; - private levelContainer: Phaser.GameObjects.Container; - private hpBar: Phaser.GameObjects.Image; - private hpBarSegmentDividers: Phaser.GameObjects.Rectangle[]; - private levelNumbersContainer: Phaser.GameObjects.Container; - private hpNumbersContainer: Phaser.GameObjects.Container; - private type1Icon: Phaser.GameObjects.Sprite; - private type2Icon: Phaser.GameObjects.Sprite; - private type3Icon: Phaser.GameObjects.Sprite; - private expBar: Phaser.GameObjects.Image; - - // #region Type effectiveness hint objects - private effectivenessContainer: Phaser.GameObjects.Container; - private effectivenessWindow: Phaser.GameObjects.NineSlice; - private effectivenessText: Phaser.GameObjects.Text; - private currentEffectiveness?: string; - // #endregion - - public expMaskRect: Phaser.GameObjects.Graphics; - - private statsContainer: Phaser.GameObjects.Container; - private statsBox: Phaser.GameObjects.Sprite; - private statValuesContainer: Phaser.GameObjects.Container; - private statNumbers: Phaser.GameObjects.Sprite[]; - - public flyoutMenu?: BattleFlyout; - - private statOrder: Stat[]; - private readonly statOrderPlayer = [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD]; - private readonly statOrderEnemy = [Stat.HP, Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD]; - - constructor(x: number, y: number, player: boolean) { - super(globalScene, x, y); - this.baseY = y; - this.player = player; - this.mini = !player; - this.boss = false; - this.offset = false; - this.lastName = null; - this.lastTeraType = PokemonType.UNKNOWN; - this.lastStatus = StatusEffect.NONE; - this.lastHp = -1; - this.lastMaxHp = -1; - this.lastHpFrame = null; - this.lastExp = -1; - this.lastLevelExp = -1; - this.lastLevel = -1; - - // Initially invisible and shown via Pokemon.showInfo - this.setVisible(false); - - this.box = globalScene.add.sprite(0, 0, this.getTextureName()); - this.box.setName("box"); - this.box.setOrigin(1, 0.5); - this.add(this.box); - - this.nameText = addTextObject(player ? -115 : -124, player ? -15.2 : -11.2, "", TextStyle.BATTLE_INFO); - this.nameText.setName("text_name"); - this.nameText.setOrigin(0, 0); - this.add(this.nameText); - - this.genderText = addTextObject(0, 0, "", TextStyle.BATTLE_INFO); - this.genderText.setName("text_gender"); - this.genderText.setOrigin(0, 0); - this.genderText.setPositionRelative(this.nameText, 0, 2); - this.add(this.genderText); - - if (!this.player) { - this.ownedIcon = globalScene.add.sprite(0, 0, "icon_owned"); - this.ownedIcon.setName("icon_owned"); - this.ownedIcon.setVisible(false); - this.ownedIcon.setOrigin(0, 0); - this.ownedIcon.setPositionRelative(this.nameText, 0, 11.75); - this.add(this.ownedIcon); - - this.championRibbon = globalScene.add.sprite(0, 0, "champion_ribbon"); - this.championRibbon.setName("icon_champion_ribbon"); - this.championRibbon.setVisible(false); - this.championRibbon.setOrigin(0, 0); - this.championRibbon.setPositionRelative(this.nameText, 8, 11.75); - this.add(this.championRibbon); - } - - this.teraIcon = globalScene.add.sprite(0, 0, "icon_tera"); - this.teraIcon.setName("icon_tera"); - this.teraIcon.setVisible(false); - this.teraIcon.setOrigin(0, 0); - this.teraIcon.setScale(0.5); - this.teraIcon.setPositionRelative(this.nameText, 0, 2); - this.teraIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains); - this.add(this.teraIcon); - - this.shinyIcon = globalScene.add.sprite(0, 0, "shiny_star"); - this.shinyIcon.setName("icon_shiny"); - this.shinyIcon.setVisible(false); - this.shinyIcon.setOrigin(0, 0); - this.shinyIcon.setScale(0.5); - this.shinyIcon.setPositionRelative(this.nameText, 0, 2); - this.shinyIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains); - this.add(this.shinyIcon); - - this.fusionShinyIcon = globalScene.add.sprite(0, 0, "shiny_star_2"); - this.fusionShinyIcon.setName("icon_fusion_shiny"); - this.fusionShinyIcon.setVisible(false); - this.fusionShinyIcon.setOrigin(0, 0); - this.fusionShinyIcon.setScale(0.5); - this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y); - this.add(this.fusionShinyIcon); - - this.splicedIcon = globalScene.add.sprite(0, 0, "icon_spliced"); - this.splicedIcon.setName("icon_spliced"); - this.splicedIcon.setVisible(false); - this.splicedIcon.setOrigin(0, 0); - this.splicedIcon.setScale(0.5); - this.splicedIcon.setPositionRelative(this.nameText, 0, 2); - this.splicedIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains); - this.add(this.splicedIcon); - - this.statusIndicator = globalScene.add.sprite(0, 0, getLocalizedSpriteKey("statuses")); - this.statusIndicator.setName("icon_status"); - this.statusIndicator.setVisible(false); - this.statusIndicator.setOrigin(0, 0); - this.statusIndicator.setPositionRelative(this.nameText, 0, 11.5); - this.add(this.statusIndicator); - - this.levelContainer = globalScene.add.container(player ? -41 : -50, player ? -10 : -5); - this.levelContainer.setName("container_level"); - this.add(this.levelContainer); - - const levelOverlay = globalScene.add.image(0, 0, "overlay_lv"); - this.levelContainer.add(levelOverlay); - - this.hpBar = globalScene.add.image(player ? -61 : -71, player ? -1 : 4.5, "overlay_hp"); - this.hpBar.setName("hp_bar"); - this.hpBar.setOrigin(0); - this.add(this.hpBar); - - this.hpBarSegmentDividers = []; - - this.levelNumbersContainer = globalScene.add.container(9.5, globalScene.uiTheme ? 0 : -0.5); - this.levelNumbersContainer.setName("container_level"); - this.levelContainer.add(this.levelNumbersContainer); - - if (this.player) { - this.hpNumbersContainer = globalScene.add.container(-15, 10); - this.hpNumbersContainer.setName("container_hp"); - this.add(this.hpNumbersContainer); - - const expBar = globalScene.add.image(-98, 18, "overlay_exp"); - expBar.setName("overlay_exp"); - expBar.setOrigin(0); - this.add(expBar); - - const expMaskRect = globalScene.make.graphics({}); - expMaskRect.setScale(6); - expMaskRect.fillStyle(0xffffff); - expMaskRect.beginPath(); - expMaskRect.fillRect(127, 126, 85, 2); - - const expMask = expMaskRect.createGeometryMask(); - - expBar.setMask(expMask); - - this.expBar = expBar; - this.expMaskRect = expMaskRect; - } - - this.statsContainer = globalScene.add.container(0, 0); - this.statsContainer.setName("container_stats"); - this.statsContainer.setAlpha(0); - this.add(this.statsContainer); - - this.statsBox = globalScene.add.sprite(0, 0, `${this.getTextureName()}_stats`); - this.statsBox.setName("box_stats"); - this.statsBox.setOrigin(1, 0.5); - this.statsContainer.add(this.statsBox); - - const statLabels: Phaser.GameObjects.Sprite[] = []; - this.statNumbers = []; - - this.statValuesContainer = globalScene.add.container(0, 0); - this.statsContainer.add(this.statValuesContainer); - - // this gives us a different starting location from the left of the label and padding between stats for a player vs enemy - // since the player won't have HP to show, it doesn't need to change from the current version - const startingX = this.player ? -this.statsBox.width + 8 : -this.statsBox.width + 5; - const paddingX = this.player ? 4 : 2; - const statOverflow = this.player ? 1 : 0; - this.statOrder = this.player ? this.statOrderPlayer : this.statOrderEnemy; // this tells us whether or not to use the player or enemy battle stat order - - this.statOrder.map((s, i) => { - // we do a check for i > statOverflow to see when the stat labels go onto the next column - // For enemies, we have HP (i=0) by itself then a new column, so we check for i > 0 - // For players, we don't have HP, so we start with i = 0 and i = 1 for our first column, and so need to check for i > 1 - const statX = - i > statOverflow - ? this.statNumbers[Math.max(i - 2, 0)].x + this.statNumbers[Math.max(i - 2, 0)].width + paddingX - : startingX; // we have the Math.max(i - 2, 0) in there so for i===1 to not return a negative number; since this is now based on anything >0 instead of >1, we need to allow for i-2 < 0 - - const baseY = -this.statsBox.height / 2 + 4; // this is the baseline for the y-axis - let statY: number; // this will be the y-axis placement for the labels - if (this.statOrder[i] === Stat.SPD || this.statOrder[i] === Stat.HP) { - statY = baseY + 5; - } else { - statY = baseY + (!!(i % 2) === this.player ? 10 : 0); // we compare i % 2 against this.player to tell us where to place the label; because this.battleStatOrder for enemies has HP, this.battleStatOrder[1]=ATK, but for players this.battleStatOrder[0]=ATK, so this comparing i % 2 to this.player fixes this issue for us - } - - const statLabel = globalScene.add.sprite(statX, statY, "pbinfo_stat", Stat[s]); - statLabel.setName("icon_stat_label_" + i.toString()); - statLabel.setOrigin(0, 0); - statLabels.push(statLabel); - this.statValuesContainer.add(statLabel); - - const statNumber = globalScene.add.sprite( - statX + statLabel.width, - statY, - "pbinfo_stat_numbers", - this.statOrder[i] !== Stat.HP ? "3" : "empty", - ); - statNumber.setName("icon_stat_number_" + i.toString()); - statNumber.setOrigin(0, 0); - this.statNumbers.push(statNumber); - this.statValuesContainer.add(statNumber); - - if (this.statOrder[i] === Stat.HP) { - statLabel.setVisible(false); - statNumber.setVisible(false); - } - }); - - if (!this.player) { - this.flyoutMenu = new BattleFlyout(this.player); - this.add(this.flyoutMenu); - - this.moveBelow(this.flyoutMenu, this.box); - } - - this.type1Icon = globalScene.add.sprite( - player ? -139 : -15, - player ? -17 : -15.5, - `pbinfo_${player ? "player" : "enemy"}_type1`, - ); - this.type1Icon.setName("icon_type_1"); - this.type1Icon.setOrigin(0, 0); - this.add(this.type1Icon); - - this.type2Icon = globalScene.add.sprite( - player ? -139 : -15, - player ? -1 : -2.5, - `pbinfo_${player ? "player" : "enemy"}_type2`, - ); - this.type2Icon.setName("icon_type_2"); - this.type2Icon.setOrigin(0, 0); - this.add(this.type2Icon); - - this.type3Icon = globalScene.add.sprite( - player ? -154 : 0, - player ? -17 : -15.5, - `pbinfo_${player ? "player" : "enemy"}_type`, - ); - this.type3Icon.setName("icon_type_3"); - this.type3Icon.setOrigin(0, 0); - this.add(this.type3Icon); - - if (!this.player) { - this.effectivenessContainer = globalScene.add.container(0, 0); - this.effectivenessContainer.setPositionRelative(this.type1Icon, 22, 4); - this.effectivenessContainer.setVisible(false); - this.add(this.effectivenessContainer); - - this.effectivenessText = addTextObject(5, 4.5, "", TextStyle.BATTLE_INFO); - this.effectivenessWindow = addWindow(0, 0, 0, 20, undefined, false, undefined, undefined, WindowVariant.XTHIN); - - this.effectivenessContainer.add(this.effectivenessWindow); - this.effectivenessContainer.add(this.effectivenessText); - } - } - - getStatsValueContainer(): Phaser.GameObjects.Container { - return this.statValuesContainer; - } - - initInfo(pokemon: Pokemon) { - this.updateNameText(pokemon); - const nameTextWidth = this.nameText.displayWidth; - - this.name = pokemon.getNameToRender(); - this.box.name = pokemon.getNameToRender(); - - this.flyoutMenu?.initInfo(pokemon); - - this.genderText.setText(getGenderSymbol(pokemon.gender)); - this.genderText.setColor(getGenderColor(pokemon.gender)); - this.genderText.setPositionRelative(this.nameText, nameTextWidth, 0); - - this.lastTeraType = pokemon.getTeraType(); - - this.teraIcon.setPositionRelative(this.nameText, nameTextWidth + this.genderText.displayWidth + 1, 2); - this.teraIcon.setVisible(pokemon.isTerastallized); - this.teraIcon.on("pointerover", () => { - if (pokemon.isTerastallized) { - globalScene.ui.showTooltip( - "", - i18next.t("fightUiHandler:teraHover", { - type: i18next.t(`pokemonInfo:Type.${PokemonType[this.lastTeraType]}`), - }), - ); - } - }); - this.teraIcon.on("pointerout", () => globalScene.ui.hideTooltip()); - - const isFusion = pokemon.isFusion(true); - - this.splicedIcon.setPositionRelative( - this.nameText, - nameTextWidth + this.genderText.displayWidth + 1 + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0), - 2.5, - ); - this.splicedIcon.setVisible(isFusion); - if (this.splicedIcon.visible) { - this.splicedIcon.on("pointerover", () => - globalScene.ui.showTooltip( - "", - `${pokemon.species.getName(pokemon.formIndex)}/${pokemon.fusionSpecies?.getName(pokemon.fusionFormIndex)}`, - ), - ); - this.splicedIcon.on("pointerout", () => globalScene.ui.hideTooltip()); - } - - const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny; - const baseVariant = !doubleShiny ? pokemon.getVariant(true) : pokemon.variant; - - this.shinyIcon.setPositionRelative( - this.nameText, - nameTextWidth + - this.genderText.displayWidth + - 1 + - (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0) + - (this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0), - 2.5, - ); - this.shinyIcon.setTexture(`shiny_star${doubleShiny ? "_1" : ""}`); - this.shinyIcon.setVisible(pokemon.isShiny()); - this.shinyIcon.setTint(getVariantTint(baseVariant)); - if (this.shinyIcon.visible) { - const shinyDescriptor = - doubleShiny || baseVariant - ? `${baseVariant === 2 ? i18next.t("common:epicShiny") : baseVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}${doubleShiny ? `/${pokemon.fusionVariant === 2 ? i18next.t("common:epicShiny") : pokemon.fusionVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}` : ""}` - : ""; - this.shinyIcon.on("pointerover", () => - globalScene.ui.showTooltip( - "", - `${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`, - ), - ); - this.shinyIcon.on("pointerout", () => globalScene.ui.hideTooltip()); - } - - this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y); - this.fusionShinyIcon.setVisible(doubleShiny); - if (isFusion) { - this.fusionShinyIcon.setTint(getVariantTint(pokemon.fusionVariant)); - } - - if (!this.player) { - if (this.nameText.visible) { - this.nameText.on("pointerover", () => - globalScene.ui.showTooltip( - "", - i18next.t("battleInfo:generation", { - generation: i18next.t(`starterSelectUiHandler:gen${pokemon.species.generation}`), - }), - ), - ); - this.nameText.on("pointerout", () => globalScene.ui.hideTooltip()); - } - - const dexEntry = globalScene.gameData.dexData[pokemon.species.speciesId]; - this.ownedIcon.setVisible(!!dexEntry.caughtAttr); - const opponentPokemonDexAttr = pokemon.getDexAttr(); - if (globalScene.gameMode.isClassic) { - if ( - globalScene.gameData.starterData[pokemon.species.getRootSpeciesId()].classicWinCount > 0 && - globalScene.gameData.starterData[pokemon.species.getRootSpeciesId(true)].classicWinCount > 0 - ) { - this.championRibbon.setVisible(true); - } - } - - // Check if Player owns all genders and forms of the Pokemon - const missingDexAttrs = (dexEntry.caughtAttr & opponentPokemonDexAttr) < opponentPokemonDexAttr; - - const ownedAbilityAttrs = globalScene.gameData.starterData[pokemon.species.getRootSpeciesId()].abilityAttr; - - // Check if the player owns ability for the root form - const playerOwnsThisAbility = pokemon.checkIfPlayerHasAbilityOfStarter(ownedAbilityAttrs); - - if (missingDexAttrs || !playerOwnsThisAbility) { - this.ownedIcon.setTint(0x808080); - } - - if (this.boss) { - this.updateBossSegmentDividers(pokemon as EnemyPokemon); - } - } - - this.hpBar.setScale(pokemon.getHpRatio(true), 1); - this.lastHpFrame = this.hpBar.scaleX > 0.5 ? "high" : this.hpBar.scaleX > 0.25 ? "medium" : "low"; - this.hpBar.setFrame(this.lastHpFrame); - if (this.player) { - this.setHpNumbers(pokemon.hp, pokemon.getMaxHp()); - } - this.lastHp = pokemon.hp; - this.lastMaxHp = pokemon.getMaxHp(); - - this.setLevel(pokemon.level); - this.lastLevel = pokemon.level; - - this.shinyIcon.setVisible(pokemon.isShiny()); - - const types = pokemon.getTypes(true, false, undefined, true); - this.type1Icon.setTexture(`pbinfo_${this.player ? "player" : "enemy"}_type${types.length > 1 ? "1" : ""}`); - this.type1Icon.setFrame(PokemonType[types[0]].toLowerCase()); - this.type2Icon.setVisible(types.length > 1); - this.type3Icon.setVisible(types.length > 2); - if (types.length > 1) { - this.type2Icon.setFrame(PokemonType[types[1]].toLowerCase()); - } - if (types.length > 2) { - this.type3Icon.setFrame(PokemonType[types[2]].toLowerCase()); - } - - if (this.player) { - this.expMaskRect.x = (pokemon.levelExp / getLevelTotalExp(pokemon.level, pokemon.species.growthRate)) * 510; - this.lastExp = pokemon.exp; - this.lastLevelExp = pokemon.levelExp; - - this.statValuesContainer.setPosition(8, 7); - } - - const stats = this.statOrder.map(() => 0); - - this.lastStats = stats.join(""); - this.updateStats(stats); - } - - getTextureName(): string { - return `pbinfo_${this.player ? "player" : "enemy"}${!this.player && this.boss ? "_boss" : this.mini ? "_mini" : ""}`; - } - - setMini(mini: boolean): void { - if (this.mini === mini) { - return; - } - - this.mini = mini; - - this.box.setTexture(this.getTextureName()); - this.statsBox.setTexture(`${this.getTextureName()}_stats`); - - if (this.player) { - this.y -= 12 * (mini ? 1 : -1); - this.baseY = this.y; - } - - const offsetElements = [ - this.nameText, - this.genderText, - this.teraIcon, - this.splicedIcon, - this.shinyIcon, - this.statusIndicator, - this.levelContainer, - ]; - offsetElements.forEach(el => (el.y += 1.5 * (mini ? -1 : 1))); - - [this.type1Icon, this.type2Icon, this.type3Icon].forEach(el => { - el.x += 4 * (mini ? 1 : -1); - el.y += -8 * (mini ? 1 : -1); - }); - - this.statValuesContainer.x += 2 * (mini ? 1 : -1); - this.statValuesContainer.y += -7 * (mini ? 1 : -1); - - const toggledElements = [this.hpNumbersContainer, this.expBar]; - toggledElements.forEach(el => el.setVisible(!mini)); - } - - toggleStats(visible: boolean): void { - globalScene.tweens.add({ - targets: this.statsContainer, - duration: fixedInt(125), - ease: "Sine.easeInOut", - alpha: visible ? 1 : 0, - }); - } - - updateBossSegments(pokemon: EnemyPokemon): void { - const boss = !!pokemon.bossSegments; - - if (boss !== this.boss) { - this.boss = boss; - - [ - this.nameText, - this.genderText, - this.teraIcon, - this.splicedIcon, - this.shinyIcon, - this.ownedIcon, - this.championRibbon, - this.statusIndicator, - this.statValuesContainer, - ].map(e => (e.x += 48 * (boss ? -1 : 1))); - this.hpBar.x += 38 * (boss ? -1 : 1); - this.hpBar.y += 2 * (this.boss ? -1 : 1); - this.levelContainer.x += 2 * (boss ? -1 : 1); - this.hpBar.setTexture(`overlay_hp${boss ? "_boss" : ""}`); - this.box.setTexture(this.getTextureName()); - this.statsBox.setTexture(`${this.getTextureName()}_stats`); - } - - this.bossSegments = boss ? pokemon.bossSegments : 0; - this.updateBossSegmentDividers(pokemon); - } - - updateBossSegmentDividers(pokemon: EnemyPokemon): void { - while (this.hpBarSegmentDividers.length) { - this.hpBarSegmentDividers.pop()?.destroy(); - } - - if (this.boss && this.bossSegments > 1) { - const uiTheme = globalScene.uiTheme; - const maxHp = pokemon.getMaxHp(); - for (let s = 1; s < this.bossSegments; s++) { - const dividerX = (Math.round((maxHp / this.bossSegments) * s) / maxHp) * this.hpBar.width; - const divider = globalScene.add.rectangle( - 0, - 0, - 1, - this.hpBar.height - (uiTheme ? 0 : 1), - pokemon.bossSegmentIndex >= s ? 0xffffff : 0x404040, - ); - divider.setOrigin(0.5, 0); - divider.setName("hpBar_divider_" + s.toString()); - this.add(divider); - this.moveBelow(divider as Phaser.GameObjects.GameObject, this.statsContainer); - - divider.setPositionRelative(this.hpBar, dividerX, uiTheme ? 0 : 1); - this.hpBarSegmentDividers.push(divider); - } - } - } - - setOffset(offset: boolean): void { - if (this.offset === offset) { - return; - } - - this.offset = offset; - - this.x += 10 * (this.offset === this.player ? 1 : -1); - this.y += 27 * (this.offset ? 1 : -1); - this.baseY = this.y; - } - - updateInfo(pokemon: Pokemon, instant?: boolean): Promise { - return new Promise(resolve => { - if (!globalScene) { - return resolve(); - } - - const gender = pokemon.summonData.illusion?.gender ?? pokemon.gender; - - this.genderText.setText(getGenderSymbol(gender)); - this.genderText.setColor(getGenderColor(gender)); - - const nameUpdated = this.lastName !== pokemon.getNameToRender(); - - if (nameUpdated) { - this.updateNameText(pokemon); - this.genderText.setPositionRelative(this.nameText, this.nameText.displayWidth, 0); - } - - const teraType = pokemon.isTerastallized ? pokemon.getTeraType() : PokemonType.UNKNOWN; - const teraTypeUpdated = this.lastTeraType !== teraType; - - if (teraTypeUpdated) { - this.teraIcon.setVisible(teraType !== PokemonType.UNKNOWN); - this.teraIcon.setPositionRelative( - this.nameText, - this.nameText.displayWidth + this.genderText.displayWidth + 1, - 2, - ); - this.teraIcon.setTintFill(Phaser.Display.Color.GetColor(...getTypeRgb(teraType))); - this.lastTeraType = teraType; - } - - const isFusion = pokemon.isFusion(true); - - if (nameUpdated || teraTypeUpdated) { - this.splicedIcon.setVisible(isFusion); - - this.teraIcon.setPositionRelative( - this.nameText, - this.nameText.displayWidth + this.genderText.displayWidth + 1, - 2, - ); - this.splicedIcon.setPositionRelative( - this.nameText, - this.nameText.displayWidth + - this.genderText.displayWidth + - 1 + - (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0), - 1.5, - ); - this.shinyIcon.setPositionRelative( - this.nameText, - this.nameText.displayWidth + - this.genderText.displayWidth + - 1 + - (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0) + - (this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0), - 2.5, - ); - } - - if (this.lastStatus !== (pokemon.status?.effect || StatusEffect.NONE)) { - this.lastStatus = pokemon.status?.effect || StatusEffect.NONE; - - if (this.lastStatus !== StatusEffect.NONE) { - this.statusIndicator.setFrame(StatusEffect[this.lastStatus].toLowerCase()); - } - - const offsetX = !this.player ? (this.ownedIcon.visible ? 8 : 0) + (this.championRibbon.visible ? 8 : 0) : 0; - this.statusIndicator.setPositionRelative(this.nameText, offsetX, 11.5); - - this.statusIndicator.setVisible(!!this.lastStatus); - } - - const types = pokemon.getTypes(true, false, undefined, true); - this.type1Icon.setTexture(`pbinfo_${this.player ? "player" : "enemy"}_type${types.length > 1 ? "1" : ""}`); - this.type1Icon.setFrame(PokemonType[types[0]].toLowerCase()); - this.type2Icon.setVisible(types.length > 1); - this.type3Icon.setVisible(types.length > 2); - if (types.length > 1) { - this.type2Icon.setFrame(PokemonType[types[1]].toLowerCase()); - } - if (types.length > 2) { - this.type3Icon.setFrame(PokemonType[types[2]].toLowerCase()); - } - - const updateHpFrame = () => { - const hpFrame = this.hpBar.scaleX > 0.5 ? "high" : this.hpBar.scaleX > 0.25 ? "medium" : "low"; - if (hpFrame !== this.lastHpFrame) { - this.hpBar.setFrame(hpFrame); - this.lastHpFrame = hpFrame; - } - }; - - const updatePokemonHp = () => { - let duration = !instant ? Phaser.Math.Clamp(Math.abs(this.lastHp - pokemon.hp) * 5, 250, 5000) : 0; - const speed = globalScene.hpBarSpeed; - if (speed) { - duration = speed >= 3 ? 0 : duration / Math.pow(2, speed); - } - globalScene.tweens.add({ - targets: this.hpBar, - ease: "Sine.easeOut", - scaleX: pokemon.getHpRatio(true), - duration: duration, - onUpdate: () => { - if (this.player && this.lastHp !== pokemon.hp) { - const tweenHp = Math.ceil(this.hpBar.scaleX * pokemon.getMaxHp()); - this.setHpNumbers(tweenHp, pokemon.getMaxHp()); - this.lastHp = tweenHp; - } - - updateHpFrame(); - }, - onComplete: () => { - updateHpFrame(); - // If, after tweening, the hp is different from the original (due to rounding), force the hp number display - // to update to the correct value. - if (this.player && this.lastHp !== pokemon.hp) { - this.setHpNumbers(pokemon.hp, pokemon.getMaxHp()); - this.lastHp = pokemon.hp; - } - resolve(); - }, - }); - if (!this.player) { - this.lastHp = pokemon.hp; - } - this.lastMaxHp = pokemon.getMaxHp(); - }; - - if (this.player) { - const isLevelCapped = pokemon.level >= globalScene.getMaxExpLevel(); - - if (this.lastExp !== pokemon.exp || this.lastLevel !== pokemon.level) { - const originalResolve = resolve; - const durationMultipler = Math.max( - Phaser.Tweens.Builders.GetEaseFunction("Cubic.easeIn")( - 1 - Math.min(pokemon.level - this.lastLevel, 10) / 10, - ), - 0.1, - ); - resolve = () => this.updatePokemonExp(pokemon, false, durationMultipler).then(() => originalResolve()); - } else if (isLevelCapped !== this.lastLevelCapped) { - this.setLevel(pokemon.level); - } - - this.lastLevelCapped = isLevelCapped; - } - - if (this.lastHp !== pokemon.hp || this.lastMaxHp !== pokemon.getMaxHp()) { - return updatePokemonHp(); - } - if (!this.player && this.lastLevel !== pokemon.level) { - this.setLevel(pokemon.level); - this.lastLevel = pokemon.level; - } - - const stats = pokemon.getStatStages(); - const statsStr = stats.join(""); - - if (this.lastStats !== statsStr) { - this.updateStats(stats); - this.lastStats = statsStr; - } - - this.shinyIcon.setVisible(pokemon.isShiny(true)); - - const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny; - const baseVariant = !doubleShiny ? pokemon.getVariant(true) : pokemon.variant; - this.shinyIcon.setTint(getVariantTint(baseVariant)); - - this.fusionShinyIcon.setVisible(doubleShiny); - if (isFusion) { - this.fusionShinyIcon.setTint(getVariantTint(pokemon.fusionVariant)); - } - this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y); - - resolve(); - }); - } - - updateNameText(pokemon: Pokemon): void { - let displayName = pokemon.getNameToRender().replace(/[♂♀]/g, ""); - let nameTextWidth: number; - - const nameSizeTest = addTextObject(0, 0, displayName, TextStyle.BATTLE_INFO); - nameTextWidth = nameSizeTest.displayWidth; - - const gender = pokemon.summonData.illusion?.gender ?? pokemon.gender; - while ( - nameTextWidth > - (this.player || !this.boss ? 60 : 98) - - ((gender !== Gender.GENDERLESS ? 6 : 0) + - (pokemon.fusionSpecies ? 8 : 0) + - (pokemon.isShiny() ? 8 : 0) + - (Math.min(pokemon.level.toString().length, 3) - 3) * 8) - ) { - displayName = `${displayName.slice(0, displayName.endsWith(".") ? -2 : -1).trimEnd()}.`; - nameSizeTest.setText(displayName); - nameTextWidth = nameSizeTest.displayWidth; - } - - nameSizeTest.destroy(); - - this.nameText.setText(displayName); - this.lastName = pokemon.getNameToRender(); - - if (this.nameText.visible) { - this.nameText.setInteractive( - new Phaser.Geom.Rectangle(0, 0, this.nameText.width, this.nameText.height), - Phaser.Geom.Rectangle.Contains, - ); - } - } - - updatePokemonExp(pokemon: Pokemon, instant?: boolean, levelDurationMultiplier = 1): Promise { - return new Promise(resolve => { - const levelUp = this.lastLevel < pokemon.level; - const relLevelExp = getLevelRelExp(this.lastLevel + 1, pokemon.species.growthRate); - const levelExp = levelUp ? relLevelExp : pokemon.levelExp; - let ratio = relLevelExp ? levelExp / relLevelExp : 0; - if (this.lastLevel >= globalScene.getMaxExpLevel(true)) { - if (levelUp) { - ratio = 1; - } else { - ratio = 0; - } - instant = true; - } - const durationMultiplier = Phaser.Tweens.Builders.GetEaseFunction("Sine.easeIn")( - 1 - Math.max(this.lastLevel - 100, 0) / 150, - ); - let duration = - this.visible && !instant - ? ((levelExp - this.lastLevelExp) / relLevelExp) * - BattleInfo.EXP_GAINS_DURATION_BASE * - durationMultiplier * - levelDurationMultiplier - : 0; - const speed = globalScene.expGainsSpeed; - if (speed && speed >= ExpGainsSpeed.DEFAULT) { - duration = speed >= ExpGainsSpeed.SKIP ? ExpGainsSpeed.DEFAULT : duration / Math.pow(2, speed); - } - if (ratio === 1) { - this.lastLevelExp = 0; - this.lastLevel++; - } else { - this.lastExp = pokemon.exp; - this.lastLevelExp = pokemon.levelExp; - } - if (duration) { - globalScene.playSound("se/exp"); - } - globalScene.tweens.add({ - targets: this.expMaskRect, - ease: "Sine.easeIn", - x: ratio * 510, - duration: duration, - onComplete: () => { - if (!globalScene) { - return resolve(); - } - if (duration) { - globalScene.sound.stopByKey("se/exp"); - } - if (ratio === 1) { - globalScene.playSound("se/level_up"); - this.setLevel(this.lastLevel); - globalScene.time.delayedCall(500 * levelDurationMultiplier, () => { - this.expMaskRect.x = 0; - this.updateInfo(pokemon, instant).then(() => resolve()); - }); - return; - } - resolve(); - }, - }); - }); - } - - setLevel(level: number): void { - const isCapped = level >= globalScene.getMaxExpLevel(); - this.levelNumbersContainer.removeAll(true); - const levelStr = level.toString(); - for (let i = 0; i < levelStr.length; i++) { - this.levelNumbersContainer.add( - globalScene.add.image(i * 8, 0, `numbers${isCapped && this.player ? "_red" : ""}`, levelStr[i]), - ); - } - this.levelContainer.setX((this.player ? -41 : -50) - 8 * Math.max(levelStr.length - 3, 0)); - } - - setHpNumbers(hp: number, maxHp: number): void { - if (!this.player || !globalScene) { - return; - } - this.hpNumbersContainer.removeAll(true); - const hpStr = hp.toString(); - const maxHpStr = maxHp.toString(); - let offset = 0; - for (let i = maxHpStr.length - 1; i >= 0; i--) { - this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", maxHpStr[i])); - } - this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", "/")); - for (let i = hpStr.length - 1; i >= 0; i--) { - this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", hpStr[i])); - } - } - - updateStats(stats: number[]): void { - this.statOrder.map((s, i) => { - if (s !== Stat.HP) { - this.statNumbers[i].setFrame(stats[s - 1].toString()); - } - }); - } - - /** - * Request the flyoutMenu to toggle if available and hides or shows the effectiveness window where necessary - */ - toggleFlyout(visible: boolean): void { - this.flyoutMenu?.toggleFlyout(visible); - - if (visible) { - this.effectivenessContainer?.setVisible(false); - } else { - this.updateEffectiveness(this.currentEffectiveness); - } - } - - /** - * Show or hide the type effectiveness multiplier window - * Passing undefined will hide the window - */ - updateEffectiveness(effectiveness?: string) { - if (this.player) { - return; - } - this.currentEffectiveness = effectiveness; - - if (!globalScene.typeHints || effectiveness === undefined || this.flyoutMenu?.flyoutVisible) { - this.effectivenessContainer.setVisible(false); - return; - } - - this.effectivenessText.setText(effectiveness); - this.effectivenessWindow.width = 10 + this.effectivenessText.displayWidth; - this.effectivenessContainer.setVisible(true); - } - - getBaseY(): number { - return this.baseY; - } - - resetY(): void { - this.y = this.baseY; - } -} - -export class PlayerBattleInfo extends BattleInfo { - constructor() { - super(Math.floor(globalScene.game.canvas.width / 6) - 10, -72, true); - } -} - -export class EnemyBattleInfo extends BattleInfo { - constructor() { - super(140, -141, false); - } - - setMini(_mini: boolean): void {} // Always mini -} diff --git a/src/ui/battle-info/battle-info.ts b/src/ui/battle-info/battle-info.ts new file mode 100644 index 00000000000..71596bf0f43 --- /dev/null +++ b/src/ui/battle-info/battle-info.ts @@ -0,0 +1,688 @@ +import type { default as Pokemon } from "../../field/pokemon"; +import { getLocalizedSpriteKey, fixedInt, getShinyDescriptor } from "#app/utils/common"; +import { addTextObject, TextStyle } from "../text"; +import { getGenderSymbol, getGenderColor, Gender } from "../../data/gender"; +import { StatusEffect } from "#enums/status-effect"; +import { globalScene } from "#app/global-scene"; +import { getTypeRgb } from "#app/data/type"; +import { PokemonType } from "#enums/pokemon-type"; +import { getVariantTint } from "#app/sprites/variant"; +import { Stat } from "#enums/stat"; +import i18next from "i18next"; + +/** + * Parameters influencing the position of elements within the battle info container + */ +export type BattleInfoParamList = { + /** X offset for the name text*/ + nameTextX: number; + /** Y offset for the name text */ + nameTextY: number; + /** X offset for the level container */ + levelContainerX: number; + /** Y offset for the level container */ + levelContainerY: number; + /** X offset for the hp bar */ + hpBarX: number; + /** Y offset for the hp bar */ + hpBarY: number; + /** Parameters for the stat box container */ + statBox: { + /** The starting offset from the left of the label for the entries in the stat box */ + xOffset: number; + /** The X padding between each number column */ + paddingX: number; + /** The index of the stat entries at which paddingX is used instead of startingX */ + statOverflow: number; + }; +}; + +export default abstract class BattleInfo extends Phaser.GameObjects.Container { + public static readonly EXP_GAINS_DURATION_BASE = 1650; + + protected baseY: number; + protected baseLvContainerX: number; + + protected player: boolean; + protected mini: boolean; + protected boss: boolean; + protected bossSegments: number; + protected offset: boolean; + protected lastName: string | null; + protected lastTeraType: PokemonType; + protected lastStatus: StatusEffect; + protected lastHp: number; + protected lastMaxHp: number; + protected lastHpFrame: string | null; + protected lastExp: number; + protected lastLevelExp: number; + protected lastLevel: number; + protected lastLevelCapped: boolean; + protected lastStats: string; + + protected box: Phaser.GameObjects.Sprite; + protected nameText: Phaser.GameObjects.Text; + protected genderText: Phaser.GameObjects.Text; + protected teraIcon: Phaser.GameObjects.Sprite; + protected shinyIcon: Phaser.GameObjects.Sprite; + protected fusionShinyIcon: Phaser.GameObjects.Sprite; + protected splicedIcon: Phaser.GameObjects.Sprite; + protected statusIndicator: Phaser.GameObjects.Sprite; + protected levelContainer: Phaser.GameObjects.Container; + protected hpBar: Phaser.GameObjects.Image; + protected levelNumbersContainer: Phaser.GameObjects.Container; + protected type1Icon: Phaser.GameObjects.Sprite; + protected type2Icon: Phaser.GameObjects.Sprite; + protected type3Icon: Phaser.GameObjects.Sprite; + protected expBar: Phaser.GameObjects.Image; + + public expMaskRect: Phaser.GameObjects.Graphics; + + protected statsContainer: Phaser.GameObjects.Container; + protected statsBox: Phaser.GameObjects.Sprite; + protected statValuesContainer: Phaser.GameObjects.Container; + protected statNumbers: Phaser.GameObjects.Sprite[]; + + get statOrder(): Stat[] { + return []; + } + + /** Helper method used by the constructor to create the tera and shiny icons next to the name */ + private constructIcons() { + const hitArea = new Phaser.Geom.Rectangle(0, 0, 12, 15); + const hitCallback = Phaser.Geom.Rectangle.Contains; + + this.teraIcon = globalScene.add + .sprite(0, 0, "icon_tera") + .setName("icon_tera") + .setVisible(false) + .setOrigin(0) + .setScale(0.5) + .setInteractive(hitArea, hitCallback) + .setPositionRelative(this.nameText, 0, 2); + + this.shinyIcon = globalScene.add + .sprite(0, 0, "shiny_star") + .setName("icon_shiny") + .setVisible(false) + .setOrigin(0) + .setScale(0.5) + .setInteractive(hitArea, hitCallback) + .setPositionRelative(this.nameText, 0, 2); + + this.fusionShinyIcon = globalScene.add + .sprite(0, 0, "shiny_star_2") + .setName("icon_fusion_shiny") + .setVisible(false) + .setOrigin(0) + .setScale(0.5) + .copyPosition(this.shinyIcon); + + this.splicedIcon = globalScene.add + .sprite(0, 0, "icon_spliced") + .setName("icon_spliced") + .setVisible(false) + .setOrigin(0) + .setScale(0.5) + .setInteractive(hitArea, hitCallback) + .setPositionRelative(this.nameText, 0, 2); + + this.add([this.teraIcon, this.shinyIcon, this.fusionShinyIcon, this.splicedIcon]); + } + + /** + * Submethod of the constructor that creates and adds the stats container to the battle info + */ + protected constructStatContainer({ xOffset, paddingX, statOverflow }: BattleInfoParamList["statBox"]): void { + this.statsContainer = globalScene.add.container(0, 0).setName("container_stats").setAlpha(0); + this.add(this.statsContainer); + + this.statsBox = globalScene.add + .sprite(0, 0, `${this.getTextureName()}_stats`) + .setName("box_stats") + .setOrigin(1, 0.5); + this.statsContainer.add(this.statsBox); + + const statLabels: Phaser.GameObjects.Sprite[] = []; + this.statNumbers = []; + + this.statValuesContainer = globalScene.add.container(); + this.statsContainer.add(this.statValuesContainer); + + const startingX = -this.statsBox.width + xOffset; + + // this gives us a different starting location from the left of the label and padding between stats for a player vs enemy + // since the player won't have HP to show, it doesn't need to change from the current version + + for (const [i, s] of this.statOrder.entries()) { + const isHp = s === Stat.HP; + // we do a check for i > statOverflow to see when the stat labels go onto the next column + // For enemies, we have HP (i=0) by itself then a new column, so we check for i > 0 + // For players, we don't have HP, so we start with i = 0 and i = 1 for our first column, and so need to check for i > 1 + const statX = + i > statOverflow + ? this.statNumbers[Math.max(i - 2, 0)].x + this.statNumbers[Math.max(i - 2, 0)].width + paddingX + : startingX; // we have the Math.max(i - 2, 0) in there so for i===1 to not return a negative number; since this is now based on anything >0 instead of >1, we need to allow for i-2 < 0 + + let statY = -this.statsBox.height / 2 + 4; // this is the baseline for the y-axis + if (isHp || s === Stat.SPD) { + statY += 5; + } else if (this.player === !!(i % 2)) { + // we compare i % 2 against this.player to tell us where to place the label + // because this.battleStatOrder for enemies has HP, this.battleStatOrder[1]=ATK, but for players + // this.battleStatOrder[0]=ATK, so this comparing i % 2 to this.player fixes this issue for us + statY += 10; + } + + const statLabel = globalScene.add + .sprite(statX, statY, "pbinfo_stat", Stat[s]) + .setName("icon_stat_label_" + i.toString()) + .setOrigin(0); + statLabels.push(statLabel); + this.statValuesContainer.add(statLabel); + + const statNumber = globalScene.add + .sprite(statX + statLabel.width, statY, "pbinfo_stat_numbers", !isHp ? "3" : "empty") + .setName("icon_stat_number_" + i.toString()) + .setOrigin(0); + this.statNumbers.push(statNumber); + this.statValuesContainer.add(statNumber); + + if (isHp) { + statLabel.setVisible(false); + statNumber.setVisible(false); + } + } + } + + /** + * Submethod of the constructor that creates and adds the pokemon type icons to the battle info + */ + protected abstract constructTypeIcons(): void; + + /** + * @param x - The x position of the battle info container + * @param y - The y position of the battle info container + * @param player - Whether this battle info belongs to a player or an enemy + * @param posParams - The parameters influencing the position of elements within the battle info container + */ + constructor(x: number, y: number, player: boolean, posParams: BattleInfoParamList) { + super(globalScene, x, y); + this.baseY = y; + this.player = player; + this.mini = !player; + this.boss = false; + this.offset = false; + this.lastName = null; + this.lastTeraType = PokemonType.UNKNOWN; + this.lastStatus = StatusEffect.NONE; + this.lastHp = -1; + this.lastMaxHp = -1; + this.lastHpFrame = null; + this.lastExp = -1; + this.lastLevelExp = -1; + this.lastLevel = -1; + this.baseLvContainerX = posParams.levelContainerX; + + // Initially invisible and shown via Pokemon.showInfo + this.setVisible(false); + + this.box = globalScene.add.sprite(0, 0, this.getTextureName()).setName("box").setOrigin(1, 0.5); + this.add(this.box); + + this.nameText = addTextObject(player ? -115 : -124, player ? -15.2 : -11.2, "", TextStyle.BATTLE_INFO) + .setName("text_name") + .setOrigin(0); + this.add(this.nameText); + + this.genderText = addTextObject(0, 0, "", TextStyle.BATTLE_INFO) + .setName("text_gender") + .setOrigin(0) + .setPositionRelative(this.nameText, 0, 2); + this.add(this.genderText); + + this.constructIcons(); + + this.statusIndicator = globalScene.add + .sprite(0, 0, getLocalizedSpriteKey("statuses")) + .setName("icon_status") + .setVisible(false) + .setOrigin(0) + .setPositionRelative(this.nameText, 0, 11.5); + this.add(this.statusIndicator); + + this.levelContainer = globalScene.add + .container(posParams.levelContainerX, posParams.levelContainerY) + .setName("container_level"); + this.add(this.levelContainer); + + const levelOverlay = globalScene.add.image(0, 0, "overlay_lv"); + this.levelContainer.add(levelOverlay); + + this.hpBar = globalScene.add.image(posParams.hpBarX, posParams.hpBarY, "overlay_hp").setName("hp_bar").setOrigin(0); + this.add(this.hpBar); + + this.levelNumbersContainer = globalScene.add + .container(9.5, globalScene.uiTheme ? 0 : -0.5) + .setName("container_level"); + this.levelContainer.add(this.levelNumbersContainer); + + this.constructStatContainer(posParams.statBox); + + this.constructTypeIcons(); + } + + getStatsValueContainer(): Phaser.GameObjects.Container { + return this.statValuesContainer; + } + + //#region Initialization methods + + initSplicedIcon(pokemon: Pokemon, baseWidth: number) { + this.splicedIcon.setPositionRelative( + this.nameText, + baseWidth + this.genderText.displayWidth + 1 + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0), + 2.5, + ); + this.splicedIcon.setVisible(pokemon.isFusion(true)); + if (!this.splicedIcon.visible) { + return; + } + this.splicedIcon + .on("pointerover", () => + globalScene.ui.showTooltip( + "", + `${pokemon.species.getName(pokemon.formIndex)}/${pokemon.fusionSpecies?.getName(pokemon.fusionFormIndex)}`, + ), + ) + .on("pointerout", () => globalScene.ui.hideTooltip()); + } + + /** + * Called by {@linkcode initInfo} to initialize the shiny icon + * @param pokemon - The pokemon object attached to this battle info + * @param baseXOffset - The x offset to use for the shiny icon + * @param doubleShiny - Whether the pokemon is shiny and its fusion species is also shiny + */ + protected initShinyIcon(pokemon: Pokemon, xOffset: number, doubleShiny: boolean) { + const baseVariant = !doubleShiny ? pokemon.getVariant(true) : pokemon.variant; + + this.shinyIcon.setPositionRelative( + this.nameText, + xOffset + + this.genderText.displayWidth + + 1 + + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0) + + (this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0), + 2.5, + ); + this.shinyIcon + .setTexture(`shiny_star${doubleShiny ? "_1" : ""}`) + .setVisible(pokemon.isShiny()) + .setTint(getVariantTint(baseVariant)); + + if (!this.shinyIcon.visible) { + return; + } + + let shinyDescriptor = ""; + if (doubleShiny || baseVariant) { + shinyDescriptor = " (" + getShinyDescriptor(baseVariant); + if (doubleShiny) { + shinyDescriptor += "/" + getShinyDescriptor(pokemon.fusionVariant); + } + shinyDescriptor += ")"; + } + + this.shinyIcon + .on("pointerover", () => globalScene.ui.showTooltip("", i18next.t("common:shinyOnHover") + shinyDescriptor)) + .on("pointerout", () => globalScene.ui.hideTooltip()); + } + + initInfo(pokemon: Pokemon) { + this.updateNameText(pokemon); + const nameTextWidth = this.nameText.displayWidth; + + this.name = pokemon.getNameToRender(); + this.box.name = pokemon.getNameToRender(); + + this.genderText + .setText(getGenderSymbol(pokemon.gender)) + .setColor(getGenderColor(pokemon.gender)) + .setPositionRelative(this.nameText, nameTextWidth, 0); + + this.lastTeraType = pokemon.getTeraType(); + + this.teraIcon + .setVisible(pokemon.isTerastallized) + .on("pointerover", () => { + if (pokemon.isTerastallized) { + globalScene.ui.showTooltip( + "", + i18next.t("fightUiHandler:teraHover", { + type: i18next.t(`pokemonInfo:Type.${PokemonType[this.lastTeraType]}`), + }), + ); + } + }) + .on("pointerout", () => globalScene.ui.hideTooltip()) + .setPositionRelative(this.nameText, nameTextWidth + this.genderText.displayWidth + 1, 2); + + const isFusion = pokemon.isFusion(true); + this.initSplicedIcon(pokemon, nameTextWidth); + + const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny; + this.initShinyIcon(pokemon, nameTextWidth, doubleShiny); + + this.fusionShinyIcon.setVisible(doubleShiny).copyPosition(this.shinyIcon); + if (isFusion) { + this.fusionShinyIcon.setTint(getVariantTint(pokemon.fusionVariant)); + } + + this.hpBar.setScale(pokemon.getHpRatio(true), 1); + this.lastHpFrame = this.hpBar.scaleX > 0.5 ? "high" : this.hpBar.scaleX > 0.25 ? "medium" : "low"; + this.hpBar.setFrame(this.lastHpFrame); + this.lastHp = pokemon.hp; + this.lastMaxHp = pokemon.getMaxHp(); + + this.setLevel(pokemon.level); + this.lastLevel = pokemon.level; + + this.shinyIcon.setVisible(pokemon.isShiny()); + + this.setTypes(pokemon.getTypes(true, false, undefined, true)); + + const stats = this.statOrder.map(() => 0); + + this.lastStats = stats.join(""); + this.updateStats(stats); + } + //#endregion + + /** + * Return the texture name of the battle info box + */ + abstract getTextureName(): string; + + setMini(_mini: boolean): void {} + + toggleStats(visible: boolean): void { + globalScene.tweens.add({ + targets: this.statsContainer, + duration: fixedInt(125), + ease: "Sine.easeInOut", + alpha: visible ? 1 : 0, + }); + } + + setOffset(offset: boolean): void { + if (this.offset === offset) { + return; + } + + this.offset = offset; + + this.x += 10 * (this.offset === this.player ? 1 : -1); + this.y += 27 * (this.offset ? 1 : -1); + this.baseY = this.y; + } + + //#region Update methods and helpers + + /** + * Update the status icon to match the pokemon's current status + * @param pokemon - The pokemon object attached to this battle info + * @param xOffset - The offset from the name text + */ + updateStatusIcon(pokemon: Pokemon, xOffset = 0) { + if (this.lastStatus !== (pokemon.status?.effect || StatusEffect.NONE)) { + this.lastStatus = pokemon.status?.effect || StatusEffect.NONE; + + if (this.lastStatus !== StatusEffect.NONE) { + this.statusIndicator.setFrame(StatusEffect[this.lastStatus].toLowerCase()); + } + + this.statusIndicator.setVisible(!!this.lastStatus).setPositionRelative(this.nameText, xOffset, 11.5); + } + } + + /** Update the pokemon name inside the container */ + protected updateName(name: string): boolean { + if (this.lastName === name) { + return false; + } + this.nameText.setText(name).setPositionRelative(this.box, -this.nameText.displayWidth, 0); + this.lastName = name; + + return true; + } + + protected updateTeraType(ty: PokemonType): boolean { + if (this.lastTeraType === ty) { + return false; + } + + this.teraIcon + .setVisible(ty !== PokemonType.UNKNOWN) + .setTintFill(Phaser.Display.Color.GetColor(...getTypeRgb(ty))) + .setPositionRelative(this.nameText, this.nameText.displayWidth + this.genderText.displayWidth + 1, 2); + this.lastTeraType = ty; + + return true; + } + + /** + * Update the type icons to match the pokemon's types + */ + setTypes(types: PokemonType[]): void { + this.type1Icon + .setTexture(`pbinfo_${this.player ? "player" : "enemy"}_type${types.length > 1 ? "1" : ""}`) + .setFrame(PokemonType[types[0]].toLowerCase()); + this.type2Icon.setVisible(types.length > 1); + this.type3Icon.setVisible(types.length > 2); + if (types.length > 1) { + this.type2Icon.setFrame(PokemonType[types[1]].toLowerCase()); + } + if (types.length > 2) { + this.type3Icon.setFrame(PokemonType[types[2]].toLowerCase()); + } + } + + /** + * Called by {@linkcode updateInfo} to update the position of the tera, spliced, and shiny icons + * @param isFusion - Whether the pokemon is a fusion or not + */ + protected updateIconDisplay(isFusion: boolean): void { + this.teraIcon.setPositionRelative(this.nameText, this.nameText.displayWidth + this.genderText.displayWidth + 1, 2); + this.splicedIcon + .setVisible(isFusion) + .setPositionRelative( + this.nameText, + this.nameText.displayWidth + + this.genderText.displayWidth + + 1 + + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0), + 1.5, + ); + this.shinyIcon.setPositionRelative( + this.nameText, + this.nameText.displayWidth + + this.genderText.displayWidth + + 1 + + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0) + + (this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0), + 2.5, + ); + } + + //#region Hp Bar Display handling + /** + * Called every time the hp frame is updated by the tween + * @param pokemon - The pokemon object attached to this battle info + */ + protected updateHpFrame(): void { + const hpFrame = this.hpBar.scaleX > 0.5 ? "high" : this.hpBar.scaleX > 0.25 ? "medium" : "low"; + if (hpFrame !== this.lastHpFrame) { + this.hpBar.setFrame(hpFrame); + this.lastHpFrame = hpFrame; + } + } + + /** + * Called by every frame in the hp animation tween created in {@linkcode updatePokemonHp} + * @param _pokemon - The pokemon the battle-info bar belongs to + */ + protected onHpTweenUpdate(_pokemon: Pokemon): void { + this.updateHpFrame(); + } + + /** Update the pokemonHp bar */ + protected updatePokemonHp(pokemon: Pokemon, resolve: (r: void | PromiseLike) => void, instant?: boolean): void { + let duration = !instant ? Phaser.Math.Clamp(Math.abs(this.lastHp - pokemon.hp) * 5, 250, 5000) : 0; + const speed = globalScene.hpBarSpeed; + if (speed) { + duration = speed >= 3 ? 0 : duration / Math.pow(2, speed); + } + globalScene.tweens.add({ + targets: this.hpBar, + ease: "Sine.easeOut", + scaleX: pokemon.getHpRatio(true), + duration: duration, + onUpdate: () => { + this.onHpTweenUpdate(pokemon); + }, + onComplete: () => { + this.updateHpFrame(); + resolve(); + }, + }); + this.lastMaxHp = pokemon.getMaxHp(); + } + + //#endregion + + async updateInfo(pokemon: Pokemon, instant?: boolean): Promise { + let resolve: (r: void | PromiseLike) => void = () => {}; + const promise = new Promise(r => (resolve = r)); + if (!globalScene) { + return resolve(); + } + + const gender: Gender = pokemon.summonData?.illusion?.gender ?? pokemon.gender; + + this.genderText.setText(getGenderSymbol(gender)).setColor(getGenderColor(gender)); + + const nameUpdated = this.updateName(pokemon.getNameToRender()); + + const teraTypeUpdated = this.updateTeraType(pokemon.isTerastallized ? pokemon.getTeraType() : PokemonType.UNKNOWN); + + const isFusion = pokemon.isFusion(true); + + if (nameUpdated || teraTypeUpdated) { + this.updateIconDisplay(isFusion); + } + + this.updateStatusIcon(pokemon); + + if (this.lastHp !== pokemon.hp || this.lastMaxHp !== pokemon.getMaxHp()) { + return this.updatePokemonHp(pokemon, resolve, instant); + } + if (!this.player && this.lastLevel !== pokemon.level) { + this.setLevel(pokemon.level); + this.lastLevel = pokemon.level; + } + + const stats = pokemon.getStatStages(); + const statsStr = stats.join(""); + + if (this.lastStats !== statsStr) { + this.updateStats(stats); + this.lastStats = statsStr; + } + + this.shinyIcon.setVisible(pokemon.isShiny(true)); + + const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny; + const baseVariant = !doubleShiny ? pokemon.getVariant(true) : pokemon.variant; + this.shinyIcon.setTint(getVariantTint(baseVariant)); + + this.fusionShinyIcon.setVisible(doubleShiny).setPosition(this.shinyIcon.x, this.shinyIcon.y); + if (isFusion) { + this.fusionShinyIcon.setTint(getVariantTint(pokemon.fusionVariant)); + } + + resolve(); + await promise; + } + //#endregion + + updateNameText(pokemon: Pokemon): void { + let displayName = pokemon.getNameToRender().replace(/[♂♀]/g, ""); + let nameTextWidth: number; + + const nameSizeTest = addTextObject(0, 0, displayName, TextStyle.BATTLE_INFO); + nameTextWidth = nameSizeTest.displayWidth; + + const gender = pokemon.summonData.illusion?.gender ?? pokemon.gender; + while ( + nameTextWidth > + (this.player || !this.boss ? 60 : 98) - + ((gender !== Gender.GENDERLESS ? 6 : 0) + + (pokemon.fusionSpecies ? 8 : 0) + + (pokemon.isShiny() ? 8 : 0) + + (Math.min(pokemon.level.toString().length, 3) - 3) * 8) + ) { + displayName = `${displayName.slice(0, displayName.endsWith(".") ? -2 : -1).trimEnd()}.`; + nameSizeTest.setText(displayName); + nameTextWidth = nameSizeTest.displayWidth; + } + + nameSizeTest.destroy(); + + this.nameText.setText(displayName); + this.lastName = pokemon.getNameToRender(); + + if (this.nameText.visible) { + this.nameText.setInteractive( + new Phaser.Geom.Rectangle(0, 0, this.nameText.width, this.nameText.height), + Phaser.Geom.Rectangle.Contains, + ); + } + } + + /** + * Set the level numbers container to display the provided level + * + * @remarks + * The numbers in the pokemon's level uses images for each number rather than a text object with a special font. + * This method sets the images for each digit of the level number and then positions the level container based + * on the number of digits. + * + * @param level - The level to display + * @param textureKey - The texture key for the level numbers + */ + setLevel(level: number, textureKey: "numbers" | "numbers_red" = "numbers"): void { + this.levelNumbersContainer.removeAll(true); + const levelStr = level.toString(); + for (let i = 0; i < levelStr.length; i++) { + this.levelNumbersContainer.add(globalScene.add.image(i * 8, 0, textureKey, levelStr[i])); + } + this.levelContainer.setX(this.baseLvContainerX - 8 * Math.max(levelStr.length - 3, 0)); + } + + updateStats(stats: number[]): void { + for (const [i, s] of this.statOrder.entries()) { + if (s !== Stat.HP) { + this.statNumbers[i].setFrame(stats[s - 1].toString()); + } + } + } + + getBaseY(): number { + return this.baseY; + } + + resetY(): void { + this.y = this.baseY; + } +} diff --git a/src/ui/battle-info/enemy-battle-info.ts b/src/ui/battle-info/enemy-battle-info.ts new file mode 100644 index 00000000000..e8f5434dcf2 --- /dev/null +++ b/src/ui/battle-info/enemy-battle-info.ts @@ -0,0 +1,235 @@ +import { globalScene } from "#app/global-scene"; +import BattleFlyout from "../battle-flyout"; +import { addTextObject, TextStyle } from "#app/ui/text"; +import { addWindow, WindowVariant } from "#app/ui/ui-theme"; +import { Stat } from "#enums/stat"; +import i18next from "i18next"; +import type { EnemyPokemon } from "#app/field/pokemon"; +import type { GameObjects } from "phaser"; +import BattleInfo from "./battle-info"; +import type { BattleInfoParamList } from "./battle-info"; + +export class EnemyBattleInfo extends BattleInfo { + protected player: false = false; + protected championRibbon: Phaser.GameObjects.Sprite; + protected ownedIcon: Phaser.GameObjects.Sprite; + protected flyoutMenu: BattleFlyout; + + protected hpBarSegmentDividers: GameObjects.Rectangle[] = []; + + // #region Type effectiveness hint objects + protected effectivenessContainer: Phaser.GameObjects.Container; + protected effectivenessWindow: Phaser.GameObjects.NineSlice; + protected effectivenessText: Phaser.GameObjects.Text; + protected currentEffectiveness?: string; + // #endregion + + override get statOrder(): Stat[] { + return [Stat.HP, Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD]; + } + + override getTextureName(): string { + return this.boss ? "pbinfo_enemy_boss_mini" : "pbinfo_enemy_mini"; + } + + override constructTypeIcons(): void { + this.type1Icon = globalScene.add.sprite(-15, -15.5, "pbinfo_enemy_type1").setName("icon_type_1").setOrigin(0); + this.type2Icon = globalScene.add.sprite(-15, -2.5, "pbinfo_enemy_type2").setName("icon_type_2").setOrigin(0); + this.type3Icon = globalScene.add.sprite(0, 15.5, "pbinfo_enemy_type3").setName("icon_type_3").setOrigin(0); + this.add([this.type1Icon, this.type2Icon, this.type3Icon]); + } + + constructor() { + const posParams: BattleInfoParamList = { + nameTextX: -124, + nameTextY: -11.2, + levelContainerX: -50, + levelContainerY: -5, + hpBarX: -71, + hpBarY: 4.5, + statBox: { + xOffset: 5, + paddingX: 2, + statOverflow: 0, + }, + }; + + super(140, -141, false, posParams); + + this.ownedIcon = globalScene.add + .sprite(0, 0, "icon_owned") + .setName("icon_owned") + .setVisible(false) + .setOrigin(0, 0) + .setPositionRelative(this.nameText, 0, 11.75); + + this.championRibbon = globalScene.add + .sprite(0, 0, "champion_ribbon") + .setName("icon_champion_ribbon") + .setVisible(false) + .setOrigin(0, 0) + .setPositionRelative(this.nameText, 8, 11.75); + // Ensure these two icons are positioned below the stats container + this.addAt([this.ownedIcon, this.championRibbon], this.getIndex(this.statsContainer)); + + this.flyoutMenu = new BattleFlyout(this.player); + this.add(this.flyoutMenu); + + this.moveBelow(this.flyoutMenu, this.box); + + this.effectivenessContainer = globalScene.add + .container(0, 0) + .setVisible(false) + .setPositionRelative(this.type1Icon, 22, 4); + this.add(this.effectivenessContainer); + + this.effectivenessText = addTextObject(5, 4.5, "", TextStyle.BATTLE_INFO); + this.effectivenessWindow = addWindow(0, 0, 0, 20, undefined, false, undefined, undefined, WindowVariant.XTHIN); + + this.effectivenessContainer.add([this.effectivenessWindow, this.effectivenessText]); + } + + override initInfo(pokemon: EnemyPokemon): void { + this.flyoutMenu.initInfo(pokemon); + super.initInfo(pokemon); + + if (this.nameText.visible) { + this.nameText + .on("pointerover", () => + globalScene.ui.showTooltip( + "", + i18next.t("battleInfo:generation", { + generation: i18next.t(`starterSelectUiHandler:gen${pokemon.species.generation}`), + }), + ), + ) + .on("pointerout", () => globalScene.ui.hideTooltip()); + } + + const dexEntry = globalScene.gameData.dexData[pokemon.species.speciesId]; + this.ownedIcon.setVisible(!!dexEntry.caughtAttr); + const opponentPokemonDexAttr = pokemon.getDexAttr(); + if ( + globalScene.gameMode.isClassic && + globalScene.gameData.starterData[pokemon.species.getRootSpeciesId()].classicWinCount > 0 && + globalScene.gameData.starterData[pokemon.species.getRootSpeciesId(true)].classicWinCount > 0 + ) { + this.championRibbon.setVisible(true); + } + + // Check if Player owns all genders and forms of the Pokemon + const missingDexAttrs = (dexEntry.caughtAttr & opponentPokemonDexAttr) < opponentPokemonDexAttr; + + const ownedAbilityAttrs = globalScene.gameData.starterData[pokemon.species.getRootSpeciesId()].abilityAttr; + + // Check if the player owns ability for the root form + const playerOwnsThisAbility = pokemon.checkIfPlayerHasAbilityOfStarter(ownedAbilityAttrs); + + if (missingDexAttrs || !playerOwnsThisAbility) { + this.ownedIcon.setTint(0x808080); + } + + if (this.boss) { + this.updateBossSegmentDividers(pokemon as EnemyPokemon); + } + } + + /** + * Show or hide the type effectiveness multiplier window + * Passing undefined will hide the window + */ + updateEffectiveness(effectiveness?: string) { + this.currentEffectiveness = effectiveness; + + if (!globalScene.typeHints || effectiveness === undefined || this.flyoutMenu.flyoutVisible) { + this.effectivenessContainer.setVisible(false); + return; + } + + this.effectivenessText.setText(effectiveness); + this.effectivenessWindow.width = 10 + this.effectivenessText.displayWidth; + this.effectivenessContainer.setVisible(true); + } + + /** + * Request the flyoutMenu to toggle if available and hides or shows the effectiveness window where necessary + */ + toggleFlyout(visible: boolean): void { + this.flyoutMenu.toggleFlyout(visible); + + if (visible) { + this.effectivenessContainer.setVisible(false); + } else { + this.updateEffectiveness(this.currentEffectiveness); + } + } + + updateBossSegments(pokemon: EnemyPokemon): void { + const boss = !!pokemon.bossSegments; + + if (boss !== this.boss) { + this.boss = boss; + + [ + this.nameText, + this.genderText, + this.teraIcon, + this.splicedIcon, + this.shinyIcon, + this.ownedIcon, + this.championRibbon, + this.statusIndicator, + this.levelContainer, + this.statValuesContainer, + ].map(e => (e.x += 48 * (boss ? -1 : 1))); + this.hpBar.x += 38 * (boss ? -1 : 1); + this.hpBar.y += 2 * (this.boss ? -1 : 1); + this.hpBar.setTexture(`overlay_hp${boss ? "_boss" : ""}`); + this.box.setTexture(this.getTextureName()); + this.statsBox.setTexture(`${this.getTextureName()}_stats`); + } + + this.bossSegments = boss ? pokemon.bossSegments : 0; + this.updateBossSegmentDividers(pokemon); + } + + updateBossSegmentDividers(pokemon: EnemyPokemon): void { + while (this.hpBarSegmentDividers.length) { + this.hpBarSegmentDividers.pop()?.destroy(); + } + + if (this.boss && this.bossSegments > 1) { + const uiTheme = globalScene.uiTheme; + const maxHp = pokemon.getMaxHp(); + for (let s = 1; s < this.bossSegments; s++) { + const dividerX = (Math.round((maxHp / this.bossSegments) * s) / maxHp) * this.hpBar.width; + const divider = globalScene.add.rectangle( + 0, + 0, + 1, + this.hpBar.height - (uiTheme ? 0 : 1), + pokemon.bossSegmentIndex >= s ? 0xffffff : 0x404040, + ); + divider.setOrigin(0.5, 0).setName("hpBar_divider_" + s.toString()); + this.add(divider); + this.moveBelow(divider as Phaser.GameObjects.GameObject, this.statsContainer); + + divider.setPositionRelative(this.hpBar, dividerX, uiTheme ? 0 : 1); + this.hpBarSegmentDividers.push(divider); + } + } + } + + override updateStatusIcon(pokemon: EnemyPokemon): void { + super.updateStatusIcon(pokemon, (this.ownedIcon.visible ? 8 : 0) + (this.championRibbon.visible ? 8 : 0)); + } + + protected override updatePokemonHp( + pokemon: EnemyPokemon, + resolve: (r: void | PromiseLike) => void, + instant?: boolean, + ): void { + super.updatePokemonHp(pokemon, resolve, instant); + this.lastHp = pokemon.hp; + } +} diff --git a/src/ui/battle-info/player-battle-info.ts b/src/ui/battle-info/player-battle-info.ts new file mode 100644 index 00000000000..634f89b7922 --- /dev/null +++ b/src/ui/battle-info/player-battle-info.ts @@ -0,0 +1,242 @@ +import { getLevelRelExp, getLevelTotalExp } from "#app/data/exp"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import { ExpGainsSpeed } from "#enums/exp-gains-speed"; +import { Stat } from "#enums/stat"; +import BattleInfo from "./battle-info"; +import type { BattleInfoParamList } from "./battle-info"; + +export class PlayerBattleInfo extends BattleInfo { + protected player: true = true; + protected hpNumbersContainer: Phaser.GameObjects.Container; + + override get statOrder(): Stat[] { + return [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD]; + } + + override getTextureName(): string { + return this.mini ? "pbinfo_player_mini" : "pbinfo_player"; + } + + override constructTypeIcons(): void { + this.type1Icon = globalScene.add.sprite(-139, -17, "pbinfo_player_type1").setName("icon_type_1").setOrigin(0); + this.type2Icon = globalScene.add.sprite(-139, -1, "pbinfo_player_type2").setName("icon_type_2").setOrigin(0); + this.type3Icon = globalScene.add.sprite(-154, -17, "pbinfo_player_type3").setName("icon_type_3").setOrigin(0); + this.add([this.type1Icon, this.type2Icon, this.type3Icon]); + } + + constructor() { + const posParams: BattleInfoParamList = { + nameTextX: -115, + nameTextY: -15.2, + levelContainerX: -41, + levelContainerY: -10, + hpBarX: -61, + hpBarY: -1, + statBox: { + xOffset: 8, + paddingX: 4, + statOverflow: 1, + }, + }; + super(Math.floor(globalScene.game.canvas.width / 6) - 10, -72, true, posParams); + + this.hpNumbersContainer = globalScene.add.container(-15, 10).setName("container_hp"); + + // hp number container must be beneath the stat container for overlay to display properly + this.addAt(this.hpNumbersContainer, this.getIndex(this.statsContainer)); + + const expBar = globalScene.add.image(-98, 18, "overlay_exp").setName("overlay_exp").setOrigin(0); + this.add(expBar); + + const expMaskRect = globalScene.make + .graphics({}) + .setScale(6) + .fillStyle(0xffffff) + .beginPath() + .fillRect(127, 126, 85, 2); + + const expMask = expMaskRect.createGeometryMask(); + + expBar.setMask(expMask); + + this.expBar = expBar; + this.expMaskRect = expMaskRect; + } + + override initInfo(pokemon: PlayerPokemon): void { + super.initInfo(pokemon); + this.setHpNumbers(pokemon.hp, pokemon.getMaxHp()); + this.expMaskRect.x = (pokemon.levelExp / getLevelTotalExp(pokemon.level, pokemon.species.growthRate)) * 510; + this.lastExp = pokemon.exp; + this.lastLevelExp = pokemon.levelExp; + + this.statValuesContainer.setPosition(8, 7); + } + + override setMini(mini: boolean): void { + if (this.mini === mini) { + return; + } + + this.mini = mini; + + this.box.setTexture(this.getTextureName()); + this.statsBox.setTexture(`${this.getTextureName()}_stats`); + + if (this.player) { + this.y -= 12 * (mini ? 1 : -1); + this.baseY = this.y; + } + + const offsetElements = [ + this.nameText, + this.genderText, + this.teraIcon, + this.splicedIcon, + this.shinyIcon, + this.statusIndicator, + this.levelContainer, + ]; + offsetElements.forEach(el => (el.y += 1.5 * (mini ? -1 : 1))); + + [this.type1Icon, this.type2Icon, this.type3Icon].forEach(el => { + el.x += 4 * (mini ? 1 : -1); + el.y += -8 * (mini ? 1 : -1); + }); + + this.statValuesContainer.x += 2 * (mini ? 1 : -1); + this.statValuesContainer.y += -7 * (mini ? 1 : -1); + + const toggledElements = [this.hpNumbersContainer, this.expBar]; + toggledElements.forEach(el => el.setVisible(!mini)); + } + + /** + * Updates the Hp Number text (that is the "HP/Max HP" text that appears below the player's health bar) + * while the health bar is tweening. + * @param pokemon - The Pokemon the health bar belongs to. + */ + protected override onHpTweenUpdate(pokemon: PlayerPokemon): void { + const tweenHp = Math.ceil(this.hpBar.scaleX * pokemon.getMaxHp()); + this.setHpNumbers(tweenHp, pokemon.getMaxHp()); + this.lastHp = tweenHp; + this.updateHpFrame(); + } + + updatePokemonExp(pokemon: PlayerPokemon, instant?: boolean, levelDurationMultiplier = 1): Promise { + const levelUp = this.lastLevel < pokemon.level; + const relLevelExp = getLevelRelExp(this.lastLevel + 1, pokemon.species.growthRate); + const levelExp = levelUp ? relLevelExp : pokemon.levelExp; + let ratio = relLevelExp ? levelExp / relLevelExp : 0; + if (this.lastLevel >= globalScene.getMaxExpLevel(true)) { + ratio = levelUp ? 1 : 0; + instant = true; + } + const durationMultiplier = Phaser.Tweens.Builders.GetEaseFunction("Sine.easeIn")( + 1 - Math.max(this.lastLevel - 100, 0) / 150, + ); + let duration = + this.visible && !instant + ? ((levelExp - this.lastLevelExp) / relLevelExp) * + BattleInfo.EXP_GAINS_DURATION_BASE * + durationMultiplier * + levelDurationMultiplier + : 0; + const speed = globalScene.expGainsSpeed; + if (speed && speed >= ExpGainsSpeed.DEFAULT) { + duration = speed >= ExpGainsSpeed.SKIP ? ExpGainsSpeed.DEFAULT : duration / Math.pow(2, speed); + } + if (ratio === 1) { + this.lastLevelExp = 0; + this.lastLevel++; + } else { + this.lastExp = pokemon.exp; + this.lastLevelExp = pokemon.levelExp; + } + if (duration) { + globalScene.playSound("se/exp"); + } + return new Promise(resolve => { + globalScene.tweens.add({ + targets: this.expMaskRect, + ease: "Sine.easeIn", + x: ratio * 510, + duration: duration, + onComplete: () => { + if (!globalScene) { + return resolve(); + } + if (duration) { + globalScene.sound.stopByKey("se/exp"); + } + if (ratio === 1) { + globalScene.playSound("se/level_up"); + this.setLevel(this.lastLevel); + globalScene.time.delayedCall(500 * levelDurationMultiplier, () => { + this.expMaskRect.x = 0; + this.updateInfo(pokemon, instant).then(() => resolve()); + }); + return; + } + resolve(); + }, + }); + }); + } + + /** + * Updates the info on the info bar. + * + * In addition to performing all the steps of {@linkcode BattleInfo.updateInfo}, + * it also updates the EXP Bar + */ + override async updateInfo(pokemon: PlayerPokemon, instant?: boolean): Promise { + await super.updateInfo(pokemon, instant); + const isLevelCapped = pokemon.level >= globalScene.getMaxExpLevel(); + const oldLevelCapped = this.lastLevelCapped; + this.lastLevelCapped = isLevelCapped; + + if (this.lastExp !== pokemon.exp || this.lastLevel !== pokemon.level) { + const durationMultipler = Math.max( + Phaser.Tweens.Builders.GetEaseFunction("Cubic.easeIn")(1 - Math.min(pokemon.level - this.lastLevel, 10) / 10), + 0.1, + ); + await this.updatePokemonExp(pokemon, false, durationMultipler); + } else if (isLevelCapped !== oldLevelCapped) { + this.setLevel(pokemon.level); + } + } + + /** + * Set the HP numbers text, that is the "HP/Max HP" text that appears below the player's health bar. + * @param hp - The current HP of the player. + * @param maxHp - The maximum HP of the player. + */ + setHpNumbers(hp: number, maxHp: number): void { + if (!globalScene) { + return; + } + this.hpNumbersContainer.removeAll(true); + const hpStr = hp.toString(); + const maxHpStr = maxHp.toString(); + let offset = 0; + for (let i = maxHpStr.length - 1; i >= 0; i--) { + this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", maxHpStr[i])); + } + this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", "/")); + for (let i = hpStr.length - 1; i >= 0; i--) { + this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", hpStr[i])); + } + } + + /** + * Set the level numbers container to display the provided level + * + * Overrides the default implementation to handle displaying level capped numbers in red. + * @param level - The level to display + */ + override setLevel(level: number): void { + super.setLevel(level, level >= globalScene.getMaxExpLevel() ? "numbers_red" : "numbers"); + } +} diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 5a0978a934d..6dbbcd47300 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -10,7 +10,7 @@ import { getLocalizedSpriteKey, fixedInt, padInt } from "#app/utils/common"; import { MoveCategory } from "#enums/MoveCategory"; import i18next from "i18next"; import { Button } from "#enums/buttons"; -import type { PokemonMove } from "#app/field/pokemon"; +import type { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import type { CommandPhase } from "#app/phases/command-phase"; import MoveInfoOverlay from "./move-info-overlay"; @@ -279,7 +279,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { this.moveInfoOverlay.show(pokemonMove.getMove()); pokemon.getOpponents().forEach(opponent => { - opponent.updateEffectiveness(this.getEffectivenessText(pokemon, opponent, pokemonMove)); + (opponent as EnemyPokemon).updateEffectiveness(this.getEffectivenessText(pokemon, opponent, pokemonMove)); }); } @@ -391,7 +391,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { const opponents = (globalScene.getCurrentPhase() as CommandPhase).getPokemon().getOpponents(); opponents.forEach(opponent => { - opponent.updateEffectiveness(undefined); + (opponent as EnemyPokemon).updateEffectiveness(); }); } diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 18b5d2384ef..d8012a58875 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 { DexEntry, StarterDataEntry } from "../system/game-data"; import { DexAttr } from "../system/game-data"; -import { fixedInt } from "#app/utils/common"; +import { fixedInt, getShinyDescriptor } from "#app/utils/common"; import ConfirmUiHandler from "./confirm-ui-handler"; import { StatsContainer } from "./stats-container"; import { TextStyle, addBBCodeTextObject, addTextObject, getTextColor } from "./text"; @@ -343,18 +343,19 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.pokemonShinyIcon.setVisible(pokemon.isShiny()); this.pokemonShinyIcon.setTint(getVariantTint(baseVariant)); if (this.pokemonShinyIcon.visible) { - const shinyDescriptor = - doubleShiny || baseVariant - ? `${baseVariant === 2 ? i18next.t("common:epicShiny") : baseVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}${doubleShiny ? `/${pokemon.fusionVariant === 2 ? i18next.t("common:epicShiny") : pokemon.fusionVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}` : ""}` - : ""; - this.pokemonShinyIcon.on("pointerover", () => - globalScene.ui.showTooltip( - "", - `${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`, - true, - ), - ); - this.pokemonShinyIcon.on("pointerout", () => globalScene.ui.hideTooltip()); + let shinyDescriptor = ""; + if (doubleShiny || baseVariant) { + shinyDescriptor = " (" + getShinyDescriptor(baseVariant); + if (doubleShiny) { + shinyDescriptor += "/" + getShinyDescriptor(pokemon.fusionVariant); + } + shinyDescriptor += ")"; + } + this.pokemonShinyIcon + .on("pointerover", () => + globalScene.ui.showTooltip("", i18next.t("common:shinyOnHover") + shinyDescriptor, true), + ) + .on("pointerout", () => globalScene.ui.hideTooltip()); const newShiny = BigInt(1 << (pokemon.shiny ? 1 : 0)); const newVariant = BigInt(1 << (pokemon.variant + 4)); diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index f93a1826b3e..24e790f7c61 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -11,6 +11,7 @@ import { isNullOrUndefined, toReadableString, formatStat, + getShinyDescriptor, } from "#app/utils/common"; import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; @@ -444,18 +445,19 @@ export default class SummaryUiHandler extends UiHandler { this.shinyIcon.setVisible(this.pokemon.isShiny(false)); this.shinyIcon.setTint(getVariantTint(baseVariant)); if (this.shinyIcon.visible) { - const shinyDescriptor = - doubleShiny || baseVariant - ? `${baseVariant === 2 ? i18next.t("common:epicShiny") : baseVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}${doubleShiny ? `/${this.pokemon.fusionVariant === 2 ? i18next.t("common:epicShiny") : this.pokemon.fusionVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}` : ""}` - : ""; - this.shinyIcon.on("pointerover", () => - globalScene.ui.showTooltip( - "", - `${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`, - true, - ), - ); - this.shinyIcon.on("pointerout", () => globalScene.ui.hideTooltip()); + let shinyDescriptor = ""; + if (doubleShiny || baseVariant) { + shinyDescriptor = " (" + getShinyDescriptor(baseVariant); + if (doubleShiny) { + shinyDescriptor += "/" + getShinyDescriptor(this.pokemon.fusionVariant); + } + shinyDescriptor += ")"; + } + this.shinyIcon + .on("pointerover", () => + globalScene.ui.showTooltip("", i18next.t("common:shinyOnHover") + shinyDescriptor, true), + ) + .on("pointerout", () => globalScene.ui.hideTooltip()); } this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y); diff --git a/src/utils/common.ts b/src/utils/common.ts index b9111578e2f..a018b49da3c 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -2,6 +2,7 @@ import { MoneyFormat } from "#enums/money-format"; import { Moves } from "#enums/moves"; import i18next from "i18next"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; +import type { Variant } from "#app/sprites/variant"; export type nil = null | undefined; @@ -576,3 +577,18 @@ export function animationFileName(move: Moves): string { export function camelCaseToKebabCase(str: string): string { return str.replace(/[A-Z]+(?![a-z])|[A-Z]/g, (s, o) => (o ? "-" : "") + s.toLowerCase()); } + +/** Get the localized shiny descriptor for the provided variant + * @param variant - The variant to get the shiny descriptor for + * @returns The localized shiny descriptor + */ +export function getShinyDescriptor(variant: Variant): string { + switch (variant) { + case 2: + return i18next.t("common:epicShiny"); + case 1: + return i18next.t("common:rareShiny"); + case 0: + return i18next.t("common:commonShiny"); + } +} diff --git a/test/testUtils/mocks/mockGameObject.ts b/test/testUtils/mocks/mockGameObject.ts index 4c243ec9ca1..0dff7c48c73 100644 --- a/test/testUtils/mocks/mockGameObject.ts +++ b/test/testUtils/mocks/mockGameObject.ts @@ -1,3 +1,4 @@ export interface MockGameObject { name: string; + destroy?(): void; } diff --git a/test/testUtils/mocks/mocksContainer/mockContainer.ts b/test/testUtils/mocks/mocksContainer/mockContainer.ts index 5e739fbe3cc..e116e7d151a 100644 --- a/test/testUtils/mocks/mocksContainer/mockContainer.ts +++ b/test/testUtils/mocks/mocksContainer/mockContainer.ts @@ -2,199 +2,253 @@ import type MockTextureManager from "#test/testUtils/mocks/mockTextureManager"; import type { MockGameObject } from "../mockGameObject"; export default class MockContainer implements MockGameObject { - protected x; - protected y; + protected x: number; + protected y: number; protected scene; - protected width; - protected height; - protected visible; - private alpha; + protected width: number; + protected height: number; + protected visible: boolean; + private alpha: number; private style; public frame; protected textureManager; public list: MockGameObject[] = []; public name: string; - constructor(textureManager: MockTextureManager, x, y) { + constructor(textureManager: MockTextureManager, x: number, y: number) { this.x = x; this.y = y; this.frame = {}; this.textureManager = textureManager; } - setVisible(visible) { + setVisible(visible: boolean): this { this.visible = visible; + return this; } - once(_event, _callback, _source) {} + once(_event, _callback, _source): this { + return this; + } - off(_event, _callback, _source) {} + off(_event, _callback, _source): this { + return this; + } - removeFromDisplayList() { + removeFromDisplayList(): this { // same as remove or destroy + return this; } - removeBetween(_startIndex, _endIndex, _destroyChild) { + removeBetween(_startIndex, _endIndex, _destroyChild): this { // Removes multiple children across an index range + return this; } addedToScene() { // This callback is invoked when this Game Object is added to a Scene. } - setSize(_width, _height) { + setSize(_width: number, _height: number): this { // Sets the size of this Game Object. + return this; } - setMask() { + setMask(): this { /// Sets the mask that this Game Object will use to render with. + return this; } - setPositionRelative(_source, _x, _y) { + setPositionRelative(_source, _x, _y): this { /// Sets the position of this Game Object to be a relative position from the source Game Object. + return this; } - setInteractive = () => null; + setInteractive(): this { + return this; + } - setOrigin(x, y) { + setOrigin(x = 0.5, y = x): this { this.x = x; this.y = y; + return this; } - setAlpha(alpha) { + setAlpha(alpha = 1): this { this.alpha = alpha; + return this; } - setFrame(_frame, _updateSize?: boolean, _updateOrigin?: boolean) { + setFrame(_frame, _updateSize?: boolean, _updateOrigin?: boolean): this { // Sets the frame this Game Object will use to render with. + return this; } - setScale(_scale) { + setScale(_x = 1, _y = _x): this { // Sets the scale of this Game Object. + return this; } - setPosition(x, y) { + setPosition(x = 0, y = x, _z = 0, _w = 0): this { this.x = x; this.y = y; + return this; } - setX(x) { + setX(x = 0): this { this.x = x; + return this; } - setY(y) { + setY(y = 0): this { this.y = y; + return this; } destroy() { this.list = []; } - setShadow(_shadowXpos, _shadowYpos, _shadowColor) { + setShadow(_shadowXpos, _shadowYpos, _shadowColor): this { // Sets the shadow settings for this Game Object. + return this; } - setLineSpacing(_lineSpacing) { + setLineSpacing(_lineSpacing): this { // Sets the line spacing value of this Game Object. + return this; } - setText(_text) { + setText(_text): this { // Sets the text this Game Object will display. + return this; } - setAngle(_angle) { + setAngle(_angle): this { // Sets the angle of this Game Object. + return this; } - setShadowOffset(_offsetX, _offsetY) { + setShadowOffset(_offsetX, _offsetY): this { // Sets the shadow offset values. + return this; } setWordWrapWidth(_width) { // Sets the width (in pixels) to use for wrapping lines. } - setFontSize(_fontSize) { + setFontSize(_fontSize): this { // Sets the font size of this Game Object. + return this; } getBounds() { return { width: this.width, height: this.height }; } - setColor(_color) { + setColor(_color): this { // Sets the tint of this Game Object. + return this; } - setShadowColor(_color) { + setShadowColor(_color): this { // Sets the shadow color. + return this; } - setTint(_color) { + setTint(_color: this) { // Sets the tint of this Game Object. + return this; } - setStrokeStyle(_thickness, _color) { + setStrokeStyle(_thickness, _color): this { // Sets the stroke style for the graphics. return this; } - setDepth(_depth) { - // Sets the depth of this Game Object. + setDepth(_depth): this { + // Sets the depth of this Game Object.\ + return this; } - setTexture(_texture) { - // Sets the texture this Game Object will use to render with. + setTexture(_texture): this { + // Sets the texture this Game Object will use to render with.\ + return this; } - clearTint() { - // Clears any previously set tint. + clearTint(): this { + // Clears any previously set tint.\ + return this; } - sendToBack() { - // Sends this Game Object to the back of its parent's display list. + sendToBack(): this { + // Sends this Game Object to the back of its parent's display list.\ + return this; } - moveTo(_obj) { - // Moves this Game Object to the given index in the list. + moveTo(_obj): this { + // Moves this Game Object to the given index in the list.\ + return this; } - moveAbove(_obj) { + moveAbove(_obj): this { // Moves this Game Object to be above the given Game Object in the display list. + return this; } - moveBelow(_obj) { + moveBelow(_obj): this { // Moves this Game Object to be below the given Game Object in the display list. + return this; } - setName(name: string) { + setName(name: string): this { this.name = name; + return this; } - bringToTop(_obj) { + bringToTop(_obj): this { // Brings this Game Object to the top of its parents display list. + return this; } - on(_event, _callback, _source) {} + on(_event, _callback, _source): this { + return this; + } - add(obj) { + add(...obj: MockGameObject[]): this { // Adds a child to this Game Object. - this.list.push(obj); + this.list.push(...obj); + return this; } - removeAll() { + removeAll(): this { // Removes all Game Objects from this Container. this.list = []; + return this; } - addAt(obj, index) { + addAt(obj: MockGameObject | MockGameObject[], index = 0): this { // Adds a Game Object to this Container at the given index. - this.list.splice(index, 0, obj); + if (!Array.isArray(obj)) { + obj = [obj]; + } + this.list.splice(index, 0, ...obj); + return this; } - remove(obj) { - const index = this.list.indexOf(obj); - if (index !== -1) { - this.list.splice(index, 1); + remove(obj: MockGameObject | MockGameObject[], destroyChild = false): this { + if (!Array.isArray(obj)) { + obj = [obj]; } + for (const item of obj) { + const index = this.list.indexOf(item); + if (index !== -1) { + this.list.splice(index, 1); + } + if (destroyChild) { + item.destroy?.(); + } + } + return this; } getIndex(obj) { @@ -210,15 +264,28 @@ export default class MockContainer implements MockGameObject { return this.list; } - getByName(key: string) { + getByName(key: string): MockGameObject | null { return this.list.find(v => v.name === key) ?? new MockContainer(this.textureManager, 0, 0); } - disableInteractive = () => null; + disableInteractive(): this { + return this; + } - each(method) { + each(method): this { for (const item of this.list) { method(item); } + return this; + } + + copyPosition(source: { x?: number; y?: number }): this { + if (source.x !== undefined) { + this.x = source.x; + } + if (source.y !== undefined) { + this.y = source.y; + } + return this; } } diff --git a/test/testUtils/mocks/mocksContainer/mockGraphics.ts b/test/testUtils/mocks/mocksContainer/mockGraphics.ts index ebf84e935e3..1650d2f9f60 100644 --- a/test/testUtils/mocks/mocksContainer/mockGraphics.ts +++ b/test/testUtils/mocks/mocksContainer/mockGraphics.ts @@ -8,57 +8,76 @@ export default class MockGraphics implements MockGameObject { this.scene = textureManager.scene; } - fillStyle(_color) { + fillStyle(_color): this { // Sets the fill style to be used by the fill methods. + return this; } - beginPath() { + beginPath(): this { // Starts a new path by emptying the list of sub-paths. Call this method when you want to create a new path. + return this; } - fillRect(_x, _y, _width, _height) { + fillRect(_x, _y, _width, _height): this { // Adds a rectangle shape to the path which is filled when you call fill(). + return this; } - createGeometryMask() { + createGeometryMask(): this { // Creates a geometry mask. + return this; } - setOrigin(_x, _y) {} + setOrigin(_x, _y): this { + return this; + } - setAlpha(_alpha) {} + setAlpha(_alpha): this { + return this; + } - setVisible(_visible) {} + setVisible(_visible): this { + return this; + } - setName(_name) {} + setName(_name) { + return this; + } - once(_event, _callback, _source) {} + once(_event, _callback, _source) { + return this; + } - removeFromDisplayList() { + removeFromDisplayList(): this { // same as remove or destroy + return this; } addedToScene() { // This callback is invoked when this Game Object is added to a Scene. } - setPositionRelative(_source, _x, _y) { + setPositionRelative(_source, _x, _y): this { /// Sets the position of this Game Object to be a relative position from the source Game Object. + return this; } destroy() { this.list = []; } - setScale(_scale) { - // Sets the scale of this Game Object. + setScale(_scale): this { + return this; } - off(_event, _callback, _source) {} + off(_event, _callback, _source): this { + return this; + } - add(obj) { + add(obj): this { // Adds a child to this Game Object. this.list.push(obj); + return this; } removeAll() { @@ -90,4 +109,8 @@ export default class MockGraphics implements MockGameObject { getAll() { return this.list; } + + copyPosition(_source): this { + return this; + } } diff --git a/test/testUtils/mocks/mocksContainer/mockRectangle.ts b/test/testUtils/mocks/mocksContainer/mockRectangle.ts index 7bdf343759d..f9a92c41904 100644 --- a/test/testUtils/mocks/mocksContainer/mockRectangle.ts +++ b/test/testUtils/mocks/mocksContainer/mockRectangle.ts @@ -10,34 +10,47 @@ export default class MockRectangle implements MockGameObject { this.fillColor = fillColor; this.scene = textureManager.scene; } - setOrigin(_x, _y) {} + setOrigin(_x, _y): this { + return this; + } - setAlpha(_alpha) {} - setVisible(_visible) {} + setAlpha(_alpha): this { + return this; + } + setVisible(_visible): this { + return this; + } - setName(_name) {} + setName(_name): this { + return this; + } - once(_event, _callback, _source) {} + once(_event, _callback, _source): this { + return this; + } - removeFromDisplayList() { + removeFromDisplayList(): this { // same as remove or destroy + return this; } addedToScene() { // This callback is invoked when this Game Object is added to a Scene. } - setPositionRelative(_source, _x, _y) { + setPositionRelative(_source, _x, _y): this { /// Sets the position of this Game Object to be a relative position from the source Game Object. + return this; } destroy() { this.list = []; } - add(obj) { + add(obj): this { // Adds a child to this Game Object. this.list.push(obj); + return this; } removeAll() { @@ -45,16 +58,18 @@ export default class MockRectangle implements MockGameObject { this.list = []; } - addAt(obj, index) { + addAt(obj, index): this { // Adds a Game Object to this Container at the given index. this.list.splice(index, 0, obj); + return this; } - remove(obj) { + remove(obj): this { const index = this.list.indexOf(obj); if (index !== -1) { this.list.splice(index, 1); } + return this; } getIndex(obj) { @@ -69,9 +84,12 @@ export default class MockRectangle implements MockGameObject { getAll() { return this.list; } - setScale(_scale) { + setScale(_scale): this { // return this.phaserText.setScale(scale); + return this; } - off() {} + off(): this { + return this; + } } diff --git a/test/testUtils/mocks/mocksContainer/mockSprite.ts b/test/testUtils/mocks/mocksContainer/mockSprite.ts index dcc3588f127..b8ccfcced1f 100644 --- a/test/testUtils/mocks/mocksContainer/mockSprite.ts +++ b/test/testUtils/mocks/mocksContainer/mockSprite.ts @@ -1,6 +1,5 @@ import Phaser from "phaser"; import type { MockGameObject } from "../mockGameObject"; -import Sprite = Phaser.GameObjects.Sprite; import Frame = Phaser.Textures.Frame; export default class MockSprite implements MockGameObject { @@ -21,7 +20,9 @@ export default class MockSprite implements MockGameObject { Phaser.GameObjects.Sprite.prototype.setInteractive = this.setInteractive; // @ts-ignore Phaser.GameObjects.Sprite.prototype.setTexture = this.setTexture; + // @ts-ignore Phaser.GameObjects.Sprite.prototype.setSizeToFrame = this.setSizeToFrame; + // @ts-ignore Phaser.GameObjects.Sprite.prototype.setFrame = this.setFrame; // Phaser.GameObjects.Sprite.prototype.disable = this.disable; @@ -37,46 +38,55 @@ export default class MockSprite implements MockGameObject { }; } - setTexture(_key: string, _frame?: string | number) { + setTexture(_key: string, _frame?: string | number): this { return this; } - setSizeToFrame(_frame?: boolean | Frame): Sprite { - return {} as Sprite; + setSizeToFrame(_frame?: boolean | Frame): this { + return this; } - setPipeline(obj) { + setPipeline(obj): this { // Sets the pipeline of this Game Object. - return this.phaserSprite.setPipeline(obj); + this.phaserSprite.setPipeline(obj); + return this; } - off(_event, _callback, _source) {} + off(_event, _callback, _source): this { + return this; + } - setTintFill(color) { + setTintFill(color): this { // Sets the tint fill color. - return this.phaserSprite.setTintFill(color); + this.phaserSprite.setTintFill(color); + return this; } - setScale(scale) { - return this.phaserSprite.setScale(scale); + setScale(scale = 1): this { + this.phaserSprite.setScale(scale); + return this; } - setOrigin(x, y) { - return this.phaserSprite.setOrigin(x, y); + setOrigin(x = 0.5, y = x): this { + this.phaserSprite.setOrigin(x, y); + return this; } - setSize(width, height) { + setSize(width, height): this { // Sets the size of this Game Object. - return this.phaserSprite.setSize(width, height); + this.phaserSprite.setSize(width, height); + return this; } - once(event, callback, source) { - return this.phaserSprite.once(event, callback, source); + once(event, callback, source): this { + this.phaserSprite.once(event, callback, source); + return this; } - removeFromDisplayList() { + removeFromDisplayList(): this { // same as remove or destroy - return this.phaserSprite.removeFromDisplayList(); + this.phaserSprite.removeFromDisplayList(); + return this; } addedToScene() { @@ -84,97 +94,117 @@ export default class MockSprite implements MockGameObject { return this.phaserSprite.addedToScene(); } - setVisible(visible) { - return this.phaserSprite.setVisible(visible); + setVisible(visible): this { + this.phaserSprite.setVisible(visible); + return this; } - setPosition(x, y) { - return this.phaserSprite.setPosition(x, y); + setPosition(x, y): this { + this.phaserSprite.setPosition(x, y); + return this; } - setRotation(radians) { - return this.phaserSprite.setRotation(radians); + setRotation(radians): this { + this.phaserSprite.setRotation(radians); + return this; } - stop() { - return this.phaserSprite.stop(); + stop(): this { + this.phaserSprite.stop(); + return this; } - setInteractive = () => null; - - on(event, callback, source) { - return this.phaserSprite.on(event, callback, source); + setInteractive(): this { + return this; } - setAlpha(alpha) { - return this.phaserSprite.setAlpha(alpha); + on(event, callback, source): this { + this.phaserSprite.on(event, callback, source); + return this; } - setTint(color) { + setAlpha(alpha): this { + this.phaserSprite.setAlpha(alpha); + return this; + } + + setTint(color): this { // Sets the tint of this Game Object. - return this.phaserSprite.setTint(color); + this.phaserSprite.setTint(color); + return this; } - setFrame(frame, _updateSize?: boolean, _updateOrigin?: boolean) { + setFrame(frame, _updateSize?: boolean, _updateOrigin?: boolean): this { // Sets the frame this Game Object will use to render with. this.frame = frame; - return frame; + return this; } - setPositionRelative(source, x, y) { + setPositionRelative(source, x, y): this { /// Sets the position of this Game Object to be a relative position from the source Game Object. - return this.phaserSprite.setPositionRelative(source, x, y); + this.phaserSprite.setPositionRelative(source, x, y); + return this; } - setY(y) { - return this.phaserSprite.setY(y); + setY(y: number): this { + this.phaserSprite.setY(y); + return this; } - setCrop(x, y, width, height) { + setCrop(x: number, y: number, width: number, height: number): this { // Sets the crop size of this Game Object. - return this.phaserSprite.setCrop(x, y, width, height); + this.phaserSprite.setCrop(x, y, width, height); + return this; } - clearTint() { + clearTint(): this { // Clears any previously set tint. - return this.phaserSprite.clearTint(); + this.phaserSprite.clearTint(); + return this; } - disableInteractive() { + disableInteractive(): this { // Disables Interactive features of this Game Object. - return null; + return this; } apply() { - return this.phaserSprite.apply(); + this.phaserSprite.apply(); + return this; } - play() { + play(): this { // return this.phaserSprite.play(); return this; } - setPipelineData(key, value) { + setPipelineData(key: string, value: any): this { this.pipelineData[key] = value; + return this; } destroy() { return this.phaserSprite.destroy(); } - setName(name) { - return this.phaserSprite.setName(name); + setName(name: string): this { + this.phaserSprite.setName(name); + return this; } - setAngle(angle) { - return this.phaserSprite.setAngle(angle); + setAngle(angle): this { + this.phaserSprite.setAngle(angle); + return this; } - setMask() {} + setMask(): this { + return this; + } - add(obj) { + add(obj): this { // Adds a child to this Game Object. this.list.push(obj); + return this; } removeAll() { @@ -182,16 +212,18 @@ export default class MockSprite implements MockGameObject { this.list = []; } - addAt(obj, index) { + addAt(obj, index): this { // Adds a Game Object to this Container at the given index. this.list.splice(index, 0, obj); + return this; } - remove(obj) { + remove(obj): this { const index = this.list.indexOf(obj); if (index !== -1) { this.list.splice(index, 1); } + return this; } getIndex(obj) { @@ -206,4 +238,9 @@ export default class MockSprite implements MockGameObject { getAll() { return this.list; } + + copyPosition(obj): this { + this.phaserSprite.copyPosition(obj); + return this; + } } diff --git a/test/testUtils/mocks/mocksContainer/mockText.ts b/test/testUtils/mocks/mocksContainer/mockText.ts index 1f3f0ad792f..dc648616fe6 100644 --- a/test/testUtils/mocks/mocksContainer/mockText.ts +++ b/test/testUtils/mocks/mocksContainer/mockText.ts @@ -107,42 +107,51 @@ export default class MockText implements MockGameObject { } } - setScale(_scale) { + setScale(_scale): this { // return this.phaserText.setScale(scale); + return this; } - setShadow(_shadowXpos, _shadowYpos, _shadowColor) { + setShadow(_shadowXpos, _shadowYpos, _shadowColor): this { // Sets the shadow settings for this Game Object. // return this.phaserText.setShadow(shadowXpos, shadowYpos, shadowColor); + return this; } - setLineSpacing(_lineSpacing) { + setLineSpacing(_lineSpacing): this { // Sets the line spacing value of this Game Object. // return this.phaserText.setLineSpacing(lineSpacing); + return this; } - setOrigin(_x, _y) { + setOrigin(_x, _y): this { // return this.phaserText.setOrigin(x, y); + return this; } - once(_event, _callback, _source) { + once(_event, _callback, _source): this { // return this.phaserText.once(event, callback, source); + return this; } off(_event, _callback, _obj) {} removedFromScene() {} - addToDisplayList() {} - - setStroke(_color, _thickness) { - // Sets the stroke color and thickness. - // return this.phaserText.setStroke(color, thickness); + addToDisplayList(): this { + return this; } - removeFromDisplayList() { + setStroke(_color, _thickness): this { + // Sets the stroke color and thickness. + // return this.phaserText.setStroke(color, thickness); + return this; + } + + removeFromDisplayList(): this { // same as remove or destroy // return this.phaserText.removeFromDisplayList(); + return this; } addedToScene() { @@ -154,12 +163,14 @@ export default class MockText implements MockGameObject { // return this.phaserText.setVisible(visible); } - setY(_y) { + setY(_y): this { // return this.phaserText.setY(y); + return this; } - setX(_x) { + setX(_x): this { // return this.phaserText.setX(x); + return this; } /** @@ -169,27 +180,33 @@ export default class MockText implements MockGameObject { * @param z The z position of this Game Object. Default 0. * @param w The w position of this Game Object. Default 0. */ - setPosition(_x?: number, _y?: number, _z?: number, _w?: number) {} + setPosition(_x?: number, _y?: number, _z?: number, _w?: number): this { + return this; + } - setText(text) { + setText(text): this { // Sets the text this Game Object will display. // return this.phaserText.setText\(text); this.text = text; + return this; } - setAngle(_angle) { + setAngle(_angle): this { // Sets the angle of this Game Object. // return this.phaserText.setAngle(angle); + return this; } - setPositionRelative(_source, _x, _y) { + setPositionRelative(_source, _x, _y): this { /// Sets the position of this Game Object to be a relative position from the source Game Object. // return this.phaserText.setPositionRelative(source, x, y); + return this; } - setShadowOffset(_offsetX, _offsetY) { + setShadowOffset(_offsetX, _offsetY): this { // Sets the shadow offset values. // return this.phaserText.setShadowOffset(offsetX, offsetY); + return this; } setWordWrapWidth(width) { @@ -197,9 +214,10 @@ export default class MockText implements MockGameObject { this.wordWrapWidth = width; } - setFontSize(_fontSize) { + setFontSize(_fontSize): this { // Sets the font size of this Game Object. // return this.phaserText.setFontSize(fontSize); + return this; } getBounds() { @@ -209,25 +227,31 @@ export default class MockText implements MockGameObject { }; } - setColor(color: string) { + setColor(color: string): this { this.color = color; + return this; } - setInteractive = () => null; + setInteractive(): this { + return this; + } - setShadowColor(_color) { + setShadowColor(_color): this { // Sets the shadow color. // return this.phaserText.setShadowColor(color); + return this; } - setTint(_color) { + setTint(_color): this { // Sets the tint of this Game Object. // return this.phaserText.setTint(color); + return this; } - setStrokeStyle(_thickness, _color) { + setStrokeStyle(_thickness, _color): this { // Sets the stroke style for the graphics. // return this.phaserText.setStrokeStyle(thickness, color); + return this; } destroy() { @@ -235,20 +259,24 @@ export default class MockText implements MockGameObject { this.list = []; } - setAlpha(_alpha) { + setAlpha(_alpha): this { // return this.phaserText.setAlpha(alpha); + return this; } - setName(name: string) { + setName(name: string): this { this.name = name; + return this; } - setAlign(_align) { + setAlign(_align): this { // return this.phaserText.setAlign(align); + return this; } - setMask() { + setMask(): this { /// Sets the mask that this Game Object will use to render with. + return this; } getBottomLeft() { @@ -265,37 +293,43 @@ export default class MockText implements MockGameObject { }; } - disableInteractive() { + disableInteractive(): this { // Disables interaction with this Game Object. + return this; } - clearTint() { + clearTint(): this { // Clears tint on this Game Object. + return this; } - add(obj) { + add(obj): this { // Adds a child to this Game Object. this.list.push(obj); + return this; } - removeAll() { + removeAll(): this { // Removes all Game Objects from this Container. this.list = []; + return this; } - addAt(obj, index) { + addAt(obj, index): this { // Adds a Game Object to this Container at the given index. this.list.splice(index, 0, obj); + return this; } - remove(obj) { + remove(obj): this { const index = this.list.indexOf(obj); if (index !== -1) { this.list.splice(index, 1); } + return this; } - getIndex(obj) { + getIndex(obj): number { const index = this.list.indexOf(obj); return index || -1; } diff --git a/test/testUtils/testFileInitialization.ts b/test/testUtils/testFileInitialization.ts index 15635289e6f..ba90cba7d5b 100644 --- a/test/testUtils/testFileInitialization.ts +++ b/test/testUtils/testFileInitialization.ts @@ -70,10 +70,10 @@ export function initTestFile() { * @param x The relative x position * @param y The relative y position */ - const setPositionRelative = function (guideObject: any, x: number, y: number) { + const setPositionRelative = function (guideObject: any, x: number, y: number): any { const offsetX = guideObject.width * (-0.5 + (0.5 - guideObject.originX)); const offsetY = guideObject.height * (-0.5 + (0.5 - guideObject.originY)); - this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y); + return this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y); }; Phaser.GameObjects.Container.prototype.setPositionRelative = setPositionRelative; From d8c00616fc155fdf5a6b90a48a2cc3bb65c80819 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Wed, 28 May 2025 20:00:54 -0500 Subject: [PATCH 12/14] [Test] Add iterate, fix each method in mock container (#5882) --- .../mocks/mocksContainer/mockContainer.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/test/testUtils/mocks/mocksContainer/mockContainer.ts b/test/testUtils/mocks/mocksContainer/mockContainer.ts index e116e7d151a..b392e61f9f7 100644 --- a/test/testUtils/mocks/mocksContainer/mockContainer.ts +++ b/test/testUtils/mocks/mocksContainer/mockContainer.ts @@ -272,9 +272,24 @@ export default class MockContainer implements MockGameObject { return this; } - each(method): this { + // biome-ignore lint/complexity/noBannedTypes: This matches the signature of the method it mocks + each(callback: Function, context?: object, ...args: any[]): this { + if (context !== undefined) { + callback = callback.bind(context); + } + for (const item of this.list.slice()) { + callback(item, ...args); + } + return this; + } + + // biome-ignore lint/complexity/noBannedTypes: This matches the signature of the method it mocks + iterate(callback: Function, context?: object, ...args: any[]): this { + if (context !== undefined) { + callback = callback.bind(context); + } for (const item of this.list) { - method(item); + callback(item, ...args); } return this; } From 86fa3198fd897b3aafbcba7abf5d4dc6e972738e Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Wed, 28 May 2025 21:47:27 -0500 Subject: [PATCH 13/14] [Test] Fix mock text to make it compatible with method chaining (#5884) --- test/testUtils/mocks/mocksContainer/mockText.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/testUtils/mocks/mocksContainer/mockText.ts b/test/testUtils/mocks/mocksContainer/mockText.ts index dc648616fe6..8f72cd0d34f 100644 --- a/test/testUtils/mocks/mocksContainer/mockText.ts +++ b/test/testUtils/mocks/mocksContainer/mockText.ts @@ -159,8 +159,8 @@ export default class MockText implements MockGameObject { // return this.phaserText.addedToScene(); } - setVisible(_visible) { - // return this.phaserText.setVisible(visible); + setVisible(_visible): this { + return this; } setY(_y): this { @@ -209,9 +209,10 @@ export default class MockText implements MockGameObject { return this; } - setWordWrapWidth(width) { + setWordWrapWidth(width): this { // Sets the width (in pixels) to use for wrapping lines. this.wordWrapWidth = width; + return this; } setFontSize(_fontSize): this { @@ -351,5 +352,6 @@ export default class MockText implements MockGameObject { return this.runWordWrap(this.text).split("\n"); } + // biome-ignore lint/complexity/noBannedTypes: This matches the signature of the class this mocks on(_event: string | symbol, _fn: Function, _context?: any) {} } From f9e6785c351d07e7f2a7e11299831aff476c6db4 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Wed, 28 May 2025 22:18:06 -0500 Subject: [PATCH 14/14] [Test] Fix mocks' add method to match phaser signature (#5885) --- test/testUtils/mocks/mocksContainer/mockContainer.ts | 9 ++++++--- test/testUtils/mocks/mocksContainer/mockRectangle.ts | 8 ++++++-- test/testUtils/mocks/mocksContainer/mockSprite.ts | 8 ++++++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/test/testUtils/mocks/mocksContainer/mockContainer.ts b/test/testUtils/mocks/mocksContainer/mockContainer.ts index b392e61f9f7..883c3856c26 100644 --- a/test/testUtils/mocks/mocksContainer/mockContainer.ts +++ b/test/testUtils/mocks/mocksContainer/mockContainer.ts @@ -214,9 +214,12 @@ export default class MockContainer implements MockGameObject { return this; } - add(...obj: MockGameObject[]): this { - // Adds a child to this Game Object. - this.list.push(...obj); + add(obj: MockGameObject | MockGameObject[]): this { + if (Array.isArray(obj)) { + this.list.push(...obj); + } else { + this.list.push(obj); + } return this; } diff --git a/test/testUtils/mocks/mocksContainer/mockRectangle.ts b/test/testUtils/mocks/mocksContainer/mockRectangle.ts index f9a92c41904..c11af824c56 100644 --- a/test/testUtils/mocks/mocksContainer/mockRectangle.ts +++ b/test/testUtils/mocks/mocksContainer/mockRectangle.ts @@ -47,9 +47,13 @@ export default class MockRectangle implements MockGameObject { this.list = []; } - add(obj): this { + add(obj: MockGameObject | MockGameObject[]): this { // Adds a child to this Game Object. - this.list.push(obj); + if (Array.isArray(obj)) { + this.list.push(...obj); + } else { + this.list.push(obj); + } return this; } diff --git a/test/testUtils/mocks/mocksContainer/mockSprite.ts b/test/testUtils/mocks/mocksContainer/mockSprite.ts index b8ccfcced1f..ad70b6ced07 100644 --- a/test/testUtils/mocks/mocksContainer/mockSprite.ts +++ b/test/testUtils/mocks/mocksContainer/mockSprite.ts @@ -201,9 +201,13 @@ export default class MockSprite implements MockGameObject { return this; } - add(obj): this { + add(obj: MockGameObject | MockGameObject[]): this { // Adds a child to this Game Object. - this.list.push(obj); + if (Array.isArray(obj)) { + this.list.push(...obj); + } else { + this.list.push(obj); + } return this; }