From 5416c76ecbe254d2f3bccb7501dde0320f0d4314 Mon Sep 17 00:00:00 2001 From: Dean <69436131+emdeann@users.noreply.github.com> Date: Tue, 1 Apr 2025 18:31:52 -0700 Subject: [PATCH 01/33] [Bug][Hotfix] Fix crash when pulling trainer names for (certain) non-English languages (#5608) * Fix key usage for trainer locales * Update gts * Fix partner name generation --------- Co-authored-by: Wlowscha <54003515+Wlowscha@users.noreply.github.com> --- .../global-trade-system-encounter.ts | 9 ++-- src/field/trainer.ts | 49 ++++++++++++------- src/system/trainer-data.ts | 10 ++-- 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index f2b7001f81b..f80620647b0 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -985,12 +985,11 @@ function doTradeReceivedSequence( function generateRandomTraderName() { const length = TrainerType.YOUNGSTER - TrainerType.ACE_TRAINER + 1; // +1 avoids TrainerType.UNKNOWN - const trainerTypePool = i18next.t("trainersCommon:" + TrainerType[randInt(length) + 1], { returnObjects: true }); + const classKey = `trainersCommon:${TrainerType[randInt(length) + 1]}`; // Some trainers have 2 gendered pools, some do not - const gender = randInt(2) === 0 ? "MALE" : "FEMALE"; - const trainerNameString = randSeedItem( - Object.values(trainerTypePool.hasOwnProperty(gender) ? trainerTypePool[gender] : trainerTypePool), - ) as string; + const genderKey = i18next.exists(`${classKey}.MALE`) ? (randInt(2) === 0 ? ".MALE" : ".FEMALE") : ""; + const trainerNameKey = randSeedItem(Object.keys(i18next.t(`${classKey}${genderKey}`, { returnObjects: true }))); + const trainerNameString = i18next.t(`${classKey}${genderKey}.${trainerNameKey}`); // Some names have an '&' symbol and need to be trimmed to a single name instead of a double name const trainerNames = trainerNameString.split(" & "); return trainerNames[randInt(trainerNames.length)]; diff --git a/src/field/trainer.ts b/src/field/trainer.ts index c52957eef75..ccd8c83e684 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -33,14 +33,16 @@ export default class Trainer extends Phaser.GameObjects.Container { public partyTemplateIndex: number; public name: string; public partnerName: string; + public nameKey: string; + public partnerNameKey: string | undefined; public originalIndexes: { [key: number]: number } = {}; constructor( trainerType: TrainerType, variant: TrainerVariant, partyTemplateIndex?: number, - name?: string, - partnerName?: string, + nameKey?: string, + partnerNameKey?: string, trainerConfigOverride?: TrainerConfig, ) { super(globalScene, -72, 80); @@ -59,28 +61,41 @@ export default class Trainer extends Phaser.GameObjects.Container { : Utils.randSeedWeightedItem(this.config.partyTemplates.map((_, i) => i)), this.config.partyTemplates.length - 1, ); - if (i18next.exists("trainersCommon:" + TrainerType[trainerType], { returnObjects: true })) { - const namePool = i18next.t("trainersCommon:" + TrainerType[trainerType], { returnObjects: true }); - this.name = - name || - Utils.randSeedItem( - Object.values( - namePool.hasOwnProperty("MALE") - ? namePool[variant === TrainerVariant.FEMALE ? "FEMALE" : "MALE"] - : namePool, - ), + const classKey = `trainersCommon:${TrainerType[trainerType]}`; + if (i18next.exists(classKey, { returnObjects: true })) { + if (nameKey) { + this.nameKey = nameKey; + } else { + const genderKey = i18next.exists(`${classKey}.MALE`) + ? variant === TrainerVariant.FEMALE + ? ".FEMALE" + : ".MALE" + : ""; + const trainerKey = Utils.randSeedItem( + Object.keys(i18next.t(`${classKey}${genderKey}`, { returnObjects: true })), ); + this.nameKey = `${classKey}${genderKey}.${trainerKey}`; + } + this.name = i18next.t(this.nameKey); if (variant === TrainerVariant.DOUBLE) { if (this.config.doubleOnly) { - if (partnerName) { - this.partnerName = partnerName; + if (partnerNameKey) { + this.partnerNameKey = partnerNameKey; + this.partnerName = i18next.t(this.partnerNameKey); } else { [this.name, this.partnerName] = this.name.split(" & "); } } else { - this.partnerName = - partnerName || - Utils.randSeedItem(Object.values(namePool.hasOwnProperty("FEMALE") ? namePool["FEMALE"] : namePool)); + const partnerGenderKey = i18next.exists(`${classKey}.FEMALE`) ? ".FEMALE" : ""; + const partnerTrainerKey = Utils.randSeedItem( + Object.keys( + i18next.t(`${classKey}${partnerGenderKey}`, { + returnObjects: true, + }), + ), + ); + this.partnerNameKey = `${classKey}${partnerGenderKey}.${partnerTrainerKey}`; + this.partnerName = i18next.t(this.partnerNameKey); } } } diff --git a/src/system/trainer-data.ts b/src/system/trainer-data.ts index 48ab8d8d42a..0e6298309bc 100644 --- a/src/system/trainer-data.ts +++ b/src/system/trainer-data.ts @@ -5,8 +5,8 @@ export default class TrainerData { public trainerType: TrainerType; public variant: TrainerVariant; public partyTemplateIndex: number; - public name: string; - public partnerName: string; + public nameKey: string; + public partnerNameKey: string | undefined; constructor(source: Trainer | any) { const sourceTrainer = source instanceof Trainer ? (source as Trainer) : null; @@ -17,11 +17,11 @@ export default class TrainerData { ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT; this.partyTemplateIndex = source.partyMemberTemplateIndex; - this.name = source.name; - this.partnerName = source.partnerName; + this.nameKey = source.nameKey; + this.partnerNameKey = source.partnerNameKey; } toTrainer(): Trainer { - return new Trainer(this.trainerType, this.variant, this.partyTemplateIndex, this.name, this.partnerName); + return new Trainer(this.trainerType, this.variant, this.partyTemplateIndex, this.nameKey, this.partnerNameKey); } } From 70ba974348dda7f650f078677a8ede2cb7befbc8 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 1 Apr 2025 15:58:25 -0700 Subject: [PATCH 02/33] [Balance] Remove accuracy cap from Wide Lens --- src/modifier/modifier.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 7c9207bbea5..80f14ba22ce 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -2734,7 +2734,7 @@ export class PokemonMoveAccuracyBoosterModifier extends PokemonHeldItemModifier * @returns always `true` */ override apply(_pokemon: Pokemon, moveAccuracy: NumberHolder): boolean { - moveAccuracy.value = Math.min(moveAccuracy.value + this.accuracyAmount * this.getStackCount(), 100); + moveAccuracy.value = moveAccuracy.value + this.accuracyAmount * this.getStackCount(); return true; } From d92bf2903086d1a3d61785552a76dde0489a778a Mon Sep 17 00:00:00 2001 From: damocleas Date: Wed, 2 Apr 2025 00:25:05 -0400 Subject: [PATCH 03/33] [i18n] Update locales submodule and 1.8.2 bump --- package-lock.json | 4 ++-- package.json | 2 +- public/locales | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index d72bd6dcc58..64b62996dc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pokemon-rogue-battle", - "version": "1.8.1", + "version": "1.8.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pokemon-rogue-battle", - "version": "1.8.1", + "version": "1.8.2", "hasInstallScript": true, "dependencies": { "@material/material-color-utilities": "^0.2.7", diff --git a/package.json b/package.json index f4542403c06..e395e8d2a54 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pokemon-rogue-battle", "private": true, - "version": "1.8.1", + "version": "1.8.2", "type": "module", "scripts": { "start": "vite", diff --git a/public/locales b/public/locales index 8538aa3e0f6..213701f8047 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 8538aa3e0f6f38c9c9c74fd0cf6df1e2f8a0bd6d +Subproject commit 213701f80471d38142827dec2d798f047db471d1 From 951d8b633d5ed9f71b5775101cffdbdf4b9a7b12 Mon Sep 17 00:00:00 2001 From: damocleas Date: Wed, 2 Apr 2025 02:42:10 -0400 Subject: [PATCH 04/33] [Balance] Minor Passive Adjustments for 1.8.2 (#5613) * Update passives.ts --- src/data/balance/passives.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/data/balance/passives.ts b/src/data/balance/passives.ts index e39c86ee4b3..624e242944b 100644 --- a/src/data/balance/passives.ts +++ b/src/data/balance/passives.ts @@ -15,7 +15,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [Species.VENUSAUR]: { 0: Abilities.GRASSY_SURGE, 1: Abilities.SEED_SOWER, 2: Abilities.FLOWER_VEIL }, [Species.CHARMANDER]: { 0: Abilities.SHEER_FORCE }, [Species.CHARMELEON]: { 0: Abilities.BEAST_BOOST }, - [Species.CHARIZARD]: { 0: Abilities.BEAST_BOOST, 1: Abilities.LEVITATE, 2: Abilities.INTIMIDATE, 3: Abilities.UNNERVE }, + [Species.CHARIZARD]: { 0: Abilities.BEAST_BOOST, 1: Abilities.LEVITATE, 2: Abilities.TURBOBLAZE, 3: Abilities.UNNERVE }, [Species.SQUIRTLE]: { 0: Abilities.DAUNTLESS_SHIELD }, [Species.WARTORTLE]: { 0: Abilities.DAUNTLESS_SHIELD }, [Species.BLASTOISE]: { 0: Abilities.DAUNTLESS_SHIELD, 1: Abilities.BULLETPROOF, 2: Abilities.BULLETPROOF }, @@ -154,14 +154,14 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [Species.LEAFEON]: { 0: Abilities.GRASSY_SURGE }, [Species.GLACEON]: { 0: Abilities.SNOW_WARNING }, [Species.SYLVEON]: { 0: Abilities.COMPETITIVE }, - [Species.PORYGON]: { 0: Abilities.LEVITATE }, - [Species.PORYGON2]: { 0: Abilities.LEVITATE }, + [Species.PORYGON]: { 0: Abilities.TRANSISTOR }, + [Species.PORYGON2]: { 0: Abilities.TRANSISTOR }, [Species.PORYGON_Z]: { 0: Abilities.PROTEAN }, [Species.OMANYTE]: { 0: Abilities.STURDY }, [Species.OMASTAR]: { 0: Abilities.STURDY }, [Species.KABUTO]: { 0: Abilities.TOUGH_CLAWS }, [Species.KABUTOPS]: { 0: Abilities.TOUGH_CLAWS }, - [Species.AERODACTYL]: { 0: Abilities.INTIMIDATE, 1: Abilities.DELTA_STREAM }, + [Species.AERODACTYL]: { 0: Abilities.INTIMIDATE, 1: Abilities.INTIMIDATE }, [Species.ARTICUNO]: { 0: Abilities.SNOW_WARNING }, [Species.ZAPDOS]: { 0: Abilities.DRIZZLE }, [Species.MOLTRES]: { 0: Abilities.DROUGHT }, @@ -309,7 +309,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [Species.SHIFTRY]: { 0: Abilities.SHARPNESS }, [Species.TAILLOW]: { 0: Abilities.AERILATE }, [Species.SWELLOW]: { 0: Abilities.AERILATE }, - [Species.WINGULL]: { 0: Abilities.DRIZZLE }, + [Species.WINGULL]: { 0: Abilities.WATER_ABSORB }, [Species.PELIPPER]: { 0: Abilities.SWIFT_SWIM }, [Species.RALTS]: { 0: Abilities.NEUROFORCE }, [Species.KIRLIA]: { 0: Abilities.NEUROFORCE }, @@ -612,8 +612,8 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [Species.REUNICLUS]: { 0: Abilities.PSYCHIC_SURGE }, [Species.DUCKLETT]: { 0: Abilities.DRIZZLE }, [Species.SWANNA]: { 0: Abilities.DRIZZLE }, - [Species.VANILLITE]: { 0: Abilities.SNOW_WARNING }, - [Species.VANILLISH]: { 0: Abilities.SNOW_WARNING }, + [Species.VANILLITE]: { 0: Abilities.REFRIGERATE }, + [Species.VANILLISH]: { 0: Abilities.REFRIGERATE }, [Species.VANILLUXE]: { 0: Abilities.SLUSH_RUSH }, [Species.DEERLING]: { 0: Abilities.FLOWER_VEIL, 1: Abilities.CUD_CHEW, 2: Abilities.HARVEST, 3: Abilities.FUR_COAT }, [Species.SAWSBUCK]: { 0: Abilities.FLOWER_VEIL, 1: Abilities.CUD_CHEW, 2: Abilities.HARVEST, 3: Abilities.FUR_COAT }, @@ -838,7 +838,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [Species.CELESTEELA]: { 0: Abilities.HEATPROOF }, [Species.KARTANA]: { 0: Abilities.TECHNICIAN }, [Species.GUZZLORD]: { 0: Abilities.POISON_HEAL }, - [Species.NECROZMA]: { 0: Abilities.BEAST_BOOST, 1: Abilities.FULL_METAL_BODY, 2: Abilities.SHADOW_SHIELD, 3: Abilities.PRISM_ARMOR }, + [Species.NECROZMA]: { 0: Abilities.BEAST_BOOST, 1: Abilities.FULL_METAL_BODY, 2: Abilities.SHADOW_SHIELD, 3: Abilities.UNNERVE }, [Species.MAGEARNA]: { 0: Abilities.STEELY_SPIRIT, 1: Abilities.STEELY_SPIRIT }, [Species.MARSHADOW]: { 0: Abilities.IRON_FIST }, [Species.POIPOLE]: { 0: Abilities.LEVITATE }, From 277335419344b64bdaf1a4b68c89c43058b4f8a8 Mon Sep 17 00:00:00 2001 From: Dean <69436131+emdeann@users.noreply.github.com> Date: Tue, 1 Apr 2025 23:56:19 -0700 Subject: [PATCH 05/33] [Bug][Hotfix] Fix various ability flyouts not disappearing (#5614) * Add hide phases for rogue ability showing * Hide ability bar on game over * Hide ability bar in TurnEndPhase --- src/battle-scene.ts | 19 ++++++++++++------- src/data/arena-tag.ts | 5 +++-- src/data/battler-tags.ts | 5 +++-- src/data/moves/move.ts | 5 +++-- src/field/arena.ts | 3 --- src/phases/game-over-phase.ts | 2 ++ src/phases/hide-ability-phase.ts | 23 ++++------------------- src/phases/move-effect-phase.ts | 3 +++ src/phases/move-phase.ts | 16 ++++++++++------ src/phases/show-ability-phase.ts | 2 +- src/phases/turn-end-phase.ts | 2 ++ 11 files changed, 43 insertions(+), 42 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 7ab96566ef5..a759cbb84c2 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -167,7 +167,7 @@ import { ExpGainsSpeed } from "#enums/exp-gains-speed"; import { BattlerTagType } from "#enums/battler-tag-type"; import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters"; import { StatusEffect } from "#enums/status-effect"; -import { globalScene, initGlobalScene } from "#app/global-scene"; +import { initGlobalScene } from "#app/global-scene"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import { HideAbilityPhase } from "#app/phases/hide-ability-phase"; import { timedEventManager } from "./global-event-manager"; @@ -2665,7 +2665,7 @@ export default class BattleScene extends SceneBase { case "mystery_encounter_delibirdy": // Firel Delibirdy return 82.28; case "title_afd": // Andr06 - PokéRogue Title Remix (AFD) - return 47.660; + return 47.66; case "battle_rival_3_afd": // Andr06 - Final N Battle Remix (AFD) return 49.147; } @@ -2937,14 +2937,19 @@ export default class BattleScene extends SceneBase { * @param show Whether to show or hide the bar */ public queueAbilityDisplay(pokemon: Pokemon, passive: boolean, show: boolean): void { - this.unshiftPhase( - show - ? new ShowAbilityPhase(pokemon.getBattlerIndex(), passive) - : new HideAbilityPhase(pokemon.getBattlerIndex(), passive), - ); + this.unshiftPhase(show ? new ShowAbilityPhase(pokemon.getBattlerIndex(), passive) : new HideAbilityPhase()); this.clearPhaseQueueSplice(); } + /** + * Hides the ability bar if it is currently visible + */ + public hideAbilityBar(): void { + if (this.abilityBar.isVisible()) { + this.unshiftPhase(new HideAbilityPhase()); + } + } + /** * Moves everything from nextCommandPhaseQueue to phaseQueue (keeping order) */ diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 8f1d6b09a73..871f622f70a 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -28,7 +28,6 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; -import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { CommonAnimPhase } from "#app/phases/common-anim-phase"; @@ -1160,9 +1159,11 @@ class TailwindTag extends ArenaTag { ); } // Raise attack by one stage if party member has WIND_RIDER ability + // TODO: Ability displays should be handled by the ability if (pokemon.hasAbility(Abilities.WIND_RIDER)) { - globalScene.unshiftPhase(new ShowAbilityPhase(pokemon.getBattlerIndex())); + globalScene.queueAbilityDisplay(pokemon, false, true); globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.ATK], 1, true)); + globalScene.queueAbilityDisplay(pokemon, false, false); } } } diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index c391c4010b8..546dbb4a3db 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -30,7 +30,6 @@ import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MovePhase } from "#app/phases/move-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; -import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import type { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import i18next from "#app/plugins/i18n"; @@ -1901,12 +1900,14 @@ export class TruantTag extends AbilityBattlerTag { if (lastMove && lastMove.move !== Moves.NONE) { (globalScene.getCurrentPhase() as MovePhase).cancel(); - globalScene.unshiftPhase(new ShowAbilityPhase(pokemon.id, passive)); + // TODO: Ability displays should be handled by the ability + globalScene.queueAbilityDisplay(pokemon, passive, true); globalScene.queueMessage( i18next.t("battlerTags:truantLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), ); + globalScene.queueAbilityDisplay(pokemon, passive, false); } return true; diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 8204f13fcca..80e8f2dae55 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -106,7 +106,6 @@ import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { SwitchPhase } from "#app/phases/switch-phase"; import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; -import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import { SpeciesFormChangeRevertWeatherFormTrigger } from "../pokemon-forms"; import type { GameMode } from "#app/game-mode"; import { applyChallenges, ChallengeType } from "../challenge"; @@ -1924,7 +1923,9 @@ export class PartyStatusCureAttr extends MoveEffectAttr { pokemon.resetStatus(); pokemon.updateInfo(); } else { - globalScene.unshiftPhase(new ShowAbilityPhase(pokemon.id, pokemon.getPassiveAbility()?.id === this.abilityCondition)); + // TODO: Ability displays should be handled by the ability + globalScene.queueAbilityDisplay(pokemon, pokemon.getPassiveAbility()?.id === this.abilityCondition, true); + globalScene.queueAbilityDisplay(pokemon, pokemon.getPassiveAbility()?.id === this.abilityCondition, false); } } } diff --git a/src/field/arena.ts b/src/field/arena.ts index 4f243789567..cf48647e45e 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -40,7 +40,6 @@ import { TrainerType } from "#enums/trainer-type"; import { Abilities } from "#enums/abilities"; import { SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "#app/data/pokemon-forms"; import { CommonAnimPhase } from "#app/phases/common-anim-phase"; -import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import { WeatherType } from "#enums/weather-type"; import { FieldEffectModifier } from "#app/modifier/modifier"; @@ -378,7 +377,6 @@ export class Arena { const isCherrimWithFlowerGift = p.hasAbility(Abilities.FLOWER_GIFT) && p.species.speciesId === Species.CHERRIM; if (isCastformWithForecast || isCherrimWithFlowerGift) { - new ShowAbilityPhase(p.getBattlerIndex()); globalScene.triggerPokemonFormChange(p, SpeciesFormChangeWeatherTrigger); } }); @@ -395,7 +393,6 @@ export class Arena { p.hasAbility(Abilities.FLOWER_GIFT, false, true) && p.species.speciesId === Species.CHERRIM; if (isCastformWithForecast || isCherrimWithFlowerGift) { - new ShowAbilityPhase(p.getBattlerIndex()); return globalScene.triggerPokemonFormChange(p, SpeciesFormChangeRevertWeatherFormTrigger); } }); diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts index 2090592367d..f105b625cc8 100644 --- a/src/phases/game-over-phase.ts +++ b/src/phases/game-over-phase.ts @@ -45,6 +45,8 @@ export class GameOverPhase extends BattlePhase { start() { super.start(); + globalScene.hideAbilityBar(); + // Failsafe if players somehow skip floor 200 in classic mode if (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex > 200) { this.isVictory = true; diff --git a/src/phases/hide-ability-phase.ts b/src/phases/hide-ability-phase.ts index 0745b3f832a..142bb4b251d 100644 --- a/src/phases/hide-ability-phase.ts +++ b/src/phases/hide-ability-phase.ts @@ -1,27 +1,12 @@ import { globalScene } from "#app/global-scene"; -import type { BattlerIndex } from "#app/battle"; -import { PokemonPhase } from "./pokemon-phase"; - -export class HideAbilityPhase extends PokemonPhase { - private passive: boolean; - - constructor(battlerIndex: BattlerIndex, passive = false) { - super(battlerIndex); - - this.passive = passive; - } +import { Phase } from "#app/phase"; +export class HideAbilityPhase extends Phase { start() { super.start(); - const pokemon = this.getPokemon(); - - if (pokemon) { - globalScene.abilityBar.hide().then(() => { - this.end(); - }); - } else { + globalScene.abilityBar.hide().then(() => { this.end(); - } + }); } } diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 995684f8c03..bd1c9caad96 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -69,6 +69,7 @@ import type { Phase } from "#app/phase"; import { ShowAbilityPhase } from "./show-ability-phase"; import { MovePhase } from "./move-phase"; import { MoveEndPhase } from "./move-end-phase"; +import { HideAbilityPhase } from "#app/phases/hide-ability-phase"; export class MoveEffectPhase extends PokemonPhase { public move: PokemonMove; @@ -326,12 +327,14 @@ export class MoveEffectPhase extends PokemonPhase { ? getMoveTargets(target, move.id).targets : [user.getBattlerIndex()]; if (!isReflecting) { + // TODO: Ability displays should be handled by the ability queuedPhases.push( new ShowAbilityPhase( target.getBattlerIndex(), target.getPassiveAbility().hasAttr(ReflectStatusMoveAbAttr), ), ); + queuedPhases.push(new HideAbilityPhase()); } queuedPhases.push( diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index f8edaa56981..e04f48c2880 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -42,7 +42,6 @@ import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { MoveChargePhase } from "#app/phases/move-charge-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; -import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import { NumberHolder } from "#app/utils"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; @@ -535,11 +534,16 @@ export class MovePhase extends BattlePhase { if (this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr)) { redirectTarget.value = currentTarget; - globalScene.unshiftPhase( - new ShowAbilityPhase( - this.pokemon.getBattlerIndex(), - this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr), - ), + // TODO: Ability displays should be handled by the ability + globalScene.queueAbilityDisplay( + this.pokemon, + this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr), + true, + ); + globalScene.queueAbilityDisplay( + this.pokemon, + this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr), + false, ); } diff --git a/src/phases/show-ability-phase.ts b/src/phases/show-ability-phase.ts index 1b3c6dde568..8097af33fe0 100644 --- a/src/phases/show-ability-phase.ts +++ b/src/phases/show-ability-phase.ts @@ -35,7 +35,7 @@ export class ShowAbilityPhase extends PokemonPhase { // If the bar is already out, hide it before showing the new one if (globalScene.abilityBar.isVisible()) { - globalScene.unshiftPhase(new HideAbilityPhase(this.battlerIndex, this.passive)); + globalScene.unshiftPhase(new HideAbilityPhase()); globalScene.unshiftPhase(new ShowAbilityPhase(this.battlerIndex, this.passive)); return this.end(); } diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index 836647fbfb4..ddfc0955508 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -28,6 +28,8 @@ export class TurnEndPhase extends FieldPhase { globalScene.currentBattle.incrementTurn(); globalScene.eventTarget.dispatchEvent(new TurnEndEvent(globalScene.currentBattle.turn)); + globalScene.hideAbilityBar(); + const handlePokemon = (pokemon: Pokemon) => { if (!pokemon.switchOutStatus) { pokemon.lapseTags(BattlerTagLapseType.TURN_END); From a7394130a75bfd36fa8ceb97fa46b3397174c82d Mon Sep 17 00:00:00 2001 From: Unicorn_Power <189861924+Unicornpowerstar@users.noreply.github.com> Date: Wed, 2 Apr 2025 23:21:04 +0200 Subject: [PATCH 06/33] (Beta) Undoing the Pika-etern stuff (#5615) --- public/images/pokemon/890-eternamax.json | 773 ++++++++++++++++- public/images/pokemon/890-eternamax.png | Bin 5107 -> 40595 bytes .../images/pokemon/shiny/890-eternamax.json | 773 ++++++++++++++++- public/images/pokemon/shiny/890-eternamax.png | Bin 5107 -> 40592 bytes .../pokemon/variant/890-eternamax_2.json | 775 +++++++++++++++++- .../pokemon/variant/890-eternamax_2.png | Bin 5130 -> 44756 bytes .../pokemon/variant/890-eternamax_3.json | 775 +++++++++++++++++- .../pokemon/variant/890-eternamax_3.png | Bin 5218 -> 44757 bytes .../images/pokemon/variant/_masterlist.json | 2 +- 9 files changed, 3019 insertions(+), 79 deletions(-) diff --git a/public/images/pokemon/890-eternamax.json b/public/images/pokemon/890-eternamax.json index 70a327ef22c..98cb6f20446 100644 --- a/public/images/pokemon/890-eternamax.json +++ b/public/images/pokemon/890-eternamax.json @@ -1,20 +1,755 @@ -{ "frames": [ - { - "filename": "0001.png", - "frame": { "x": 0, "y": 0, "w": 96, "h": 98 }, - "rotated": false, - "trimmed": false, - "spriteSourceSize": { "x": 0, "y": 0, "w": 96, "h": 98 }, - "sourceSize": { "w": 96, "h": 98 }, - "duration": 100 - } - ], - "meta": { - "app": "https://www.aseprite.org/", - "version": "1.3.13-x64", - "image": "890-eternamax.png", - "format": "RGBA8888", - "size": { "w": 96, "h": 98 }, - "scale": "1" - } +{ + "textures": [ + { + "image": "890-eternamax.png", + "format": "RGBA8888", + "size": { + "w": 579, + "h": 579 + }, + "scale": 1, + "frames": [ + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 9, + "w": 100, + "h": 98 + }, + "frame": { + "x": 0, + "y": 0, + "w": 100, + "h": 98 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 95, + "h": 100 + }, + "frame": { + "x": 100, + "y": 0, + "w": 95, + "h": 100 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 8, + "w": 91, + "h": 100 + }, + "frame": { + "x": 0, + "y": 98, + "w": 91, + "h": 100 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 9, + "w": 96, + "h": 98 + }, + "frame": { + "x": 91, + "y": 100, + "w": 96, + "h": 98 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 9, + "w": 95, + "h": 99 + }, + "frame": { + "x": 187, + "y": 100, + "w": 95, + "h": 99 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 9, + "y": 10, + "w": 91, + "h": 98 + }, + "frame": { + "x": 0, + "y": 198, + "w": 91, + "h": 98 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 10, + "w": 88, + "h": 98 + }, + "frame": { + "x": 91, + "y": 198, + "w": 88, + "h": 98 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 10, + "w": 95, + "h": 97 + }, + "frame": { + "x": 195, + "y": 0, + "w": 95, + "h": 97 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 11, + "w": 95, + "h": 97 + }, + "frame": { + "x": 179, + "y": 199, + "w": 95, + "h": 97 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 11, + "w": 95, + "h": 97 + }, + "frame": { + "x": 274, + "y": 199, + "w": 95, + "h": 97 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 11, + "w": 95, + "h": 97 + }, + "frame": { + "x": 290, + "y": 0, + "w": 95, + "h": 97 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 11, + "w": 94, + "h": 96 + }, + "frame": { + "x": 282, + "y": 97, + "w": 94, + "h": 96 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 11, + "w": 90, + "h": 97 + }, + "frame": { + "x": 369, + "y": 193, + "w": 90, + "h": 97 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 13, + "w": 93, + "h": 95 + }, + "frame": { + "x": 385, + "y": 0, + "w": 93, + "h": 95 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 13, + "y": 9, + "w": 91, + "h": 96 + }, + "frame": { + "x": 385, + "y": 95, + "w": 91, + "h": 96 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 12, + "y": 11, + "w": 87, + "h": 97 + }, + "frame": { + "x": 369, + "y": 290, + "w": 87, + "h": 97 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 12, + "w": 90, + "h": 96 + }, + "frame": { + "x": 456, + "y": 290, + "w": 90, + "h": 96 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 12, + "y": 8, + "w": 90, + "h": 96 + }, + "frame": { + "x": 459, + "y": 191, + "w": 90, + "h": 96 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 13, + "y": 8, + "w": 90, + "h": 95 + }, + "frame": { + "x": 476, + "y": 95, + "w": 90, + "h": 95 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 12, + "y": 11, + "w": 89, + "h": 95 + }, + "frame": { + "x": 478, + "y": 0, + "w": 89, + "h": 95 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 12, + "w": 89, + "h": 96 + }, + "frame": { + "x": 456, + "y": 386, + "w": 89, + "h": 96 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 11, + "w": 89, + "h": 95 + }, + "frame": { + "x": 0, + "y": 296, + "w": 89, + "h": 95 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 9, + "y": 14, + "w": 89, + "h": 94 + }, + "frame": { + "x": 89, + "y": 296, + "w": 89, + "h": 94 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 12, + "y": 11, + "w": 88, + "h": 95 + }, + "frame": { + "x": 178, + "y": 296, + "w": 88, + "h": 95 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 12, + "y": 11, + "w": 87, + "h": 95 + }, + "frame": { + "x": 89, + "y": 390, + "w": 87, + "h": 95 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 12, + "w": 89, + "h": 94 + }, + "frame": { + "x": 0, + "y": 391, + "w": 89, + "h": 94 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 14, + "w": 89, + "h": 93 + }, + "frame": { + "x": 266, + "y": 387, + "w": 89, + "h": 93 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 16, + "y": 13, + "w": 85, + "h": 91 + }, + "frame": { + "x": 266, + "y": 296, + "w": 85, + "h": 91 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 13, + "w": 88, + "h": 94 + }, + "frame": { + "x": 176, + "y": 391, + "w": 88, + "h": 94 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 12, + "y": 13, + "w": 87, + "h": 94 + }, + "frame": { + "x": 355, + "y": 387, + "w": 87, + "h": 94 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 16, + "y": 11, + "w": 87, + "h": 94 + }, + "frame": { + "x": 264, + "y": 480, + "w": 87, + "h": 94 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 14, + "w": 89, + "h": 93 + }, + "frame": { + "x": 351, + "y": 481, + "w": 89, + "h": 93 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 12, + "y": 11, + "w": 87, + "h": 93 + }, + "frame": { + "x": 440, + "y": 482, + "w": 87, + "h": 93 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 13, + "y": 10, + "w": 86, + "h": 94 + }, + "frame": { + "x": 0, + "y": 485, + "w": 86, + "h": 94 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 13, + "y": 14, + "w": 85, + "h": 91 + }, + "frame": { + "x": 86, + "y": 485, + "w": 85, + "h": 91 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:8fd9e1830200ec8e4aac8571cc2d27a6:c966e3efce03c7bae43d7bca6d6dfa62:cedd2711a12bbacba5623505fe88bd92$" + } } diff --git a/public/images/pokemon/890-eternamax.png b/public/images/pokemon/890-eternamax.png index a1cf684c026eece85e4fdb32d1b8ac75178c9b16..33c8f5f96318b1cf75ab8b8add7e86c833c4b8da 100644 GIT binary patch literal 40595 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 D0Q9kL literal 5107 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 O1N5{F;nkWBQU3?QvA=}? diff --git a/public/images/pokemon/shiny/890-eternamax.json b/public/images/pokemon/shiny/890-eternamax.json index 39c1d175c8c..26813186ba8 100644 --- a/public/images/pokemon/shiny/890-eternamax.json +++ b/public/images/pokemon/shiny/890-eternamax.json @@ -1,20 +1,755 @@ -{ "frames": [ - { - "filename": "0001.png", - "frame": { "x": 1, "y": 1, "w": 92, "h": 94 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 1, "w": 92, "h": 94 }, - "sourceSize": { "w": 96, "h": 98 }, - "duration": 100 - } - ], - "meta": { - "app": "https://www.aseprite.org/", - "version": "1.3.13-x64", - "image": "890-eternamax.png", - "format": "RGBA8888", - "size": { "w": 94, "h": 96 }, - "scale": "1" - } +{ + "textures": [ + { + "image": "890-eternamax.png", + "format": "RGBA8888", + "size": { + "w": 579, + "h": 579 + }, + "scale": 1, + "frames": [ + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 9, + "w": 100, + "h": 98 + }, + "frame": { + "x": 0, + "y": 0, + "w": 100, + "h": 98 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 95, + "h": 100 + }, + "frame": { + "x": 100, + "y": 0, + "w": 95, + "h": 100 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 8, + "w": 91, + "h": 100 + }, + "frame": { + "x": 0, + "y": 98, + "w": 91, + "h": 100 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 9, + "w": 96, + "h": 98 + }, + "frame": { + "x": 91, + "y": 100, + "w": 96, + "h": 98 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 9, + "w": 95, + "h": 99 + }, + "frame": { + "x": 187, + "y": 100, + "w": 95, + "h": 99 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 9, + "y": 10, + "w": 91, + "h": 98 + }, + "frame": { + "x": 0, + "y": 198, + "w": 91, + "h": 98 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 10, + "w": 88, + "h": 98 + }, + "frame": { + "x": 91, + "y": 198, + "w": 88, + "h": 98 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 10, + "w": 95, + "h": 97 + }, + "frame": { + "x": 195, + "y": 0, + "w": 95, + "h": 97 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 11, + "w": 95, + "h": 97 + }, + "frame": { + "x": 179, + "y": 199, + "w": 95, + "h": 97 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 11, + "w": 95, + "h": 97 + }, + "frame": { + "x": 274, + "y": 199, + "w": 95, + "h": 97 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 11, + "w": 95, + "h": 97 + }, + "frame": { + "x": 290, + "y": 0, + "w": 95, + "h": 97 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 11, + "w": 94, + "h": 96 + }, + "frame": { + "x": 282, + "y": 97, + "w": 94, + "h": 96 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 11, + "w": 90, + "h": 97 + }, + "frame": { + "x": 369, + "y": 193, + "w": 90, + "h": 97 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 8, + "y": 13, + "w": 93, + "h": 95 + }, + "frame": { + "x": 385, + "y": 0, + "w": 93, + "h": 95 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 13, + "y": 9, + "w": 91, + "h": 96 + }, + "frame": { + "x": 385, + "y": 95, + "w": 91, + "h": 96 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 12, + "y": 11, + "w": 87, + "h": 97 + }, + "frame": { + "x": 369, + "y": 290, + "w": 87, + "h": 97 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 12, + "w": 90, + "h": 96 + }, + "frame": { + "x": 456, + "y": 290, + "w": 90, + "h": 96 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 12, + "y": 8, + "w": 90, + "h": 96 + }, + "frame": { + "x": 459, + "y": 191, + "w": 90, + "h": 96 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 13, + "y": 8, + "w": 90, + "h": 95 + }, + "frame": { + "x": 476, + "y": 95, + "w": 90, + "h": 95 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 12, + "y": 11, + "w": 89, + "h": 95 + }, + "frame": { + "x": 478, + "y": 0, + "w": 89, + "h": 95 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 12, + "w": 89, + "h": 96 + }, + "frame": { + "x": 456, + "y": 386, + "w": 89, + "h": 96 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 11, + "w": 89, + "h": 95 + }, + "frame": { + "x": 0, + "y": 296, + "w": 89, + "h": 95 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 9, + "y": 14, + "w": 89, + "h": 94 + }, + "frame": { + "x": 89, + "y": 296, + "w": 89, + "h": 94 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 12, + "y": 11, + "w": 88, + "h": 95 + }, + "frame": { + "x": 178, + "y": 296, + "w": 88, + "h": 95 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 12, + "y": 11, + "w": 87, + "h": 95 + }, + "frame": { + "x": 89, + "y": 390, + "w": 87, + "h": 95 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 12, + "w": 89, + "h": 94 + }, + "frame": { + "x": 0, + "y": 391, + "w": 89, + "h": 94 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 14, + "w": 89, + "h": 93 + }, + "frame": { + "x": 266, + "y": 387, + "w": 89, + "h": 93 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 16, + "y": 13, + "w": 85, + "h": 91 + }, + "frame": { + "x": 266, + "y": 296, + "w": 85, + "h": 91 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 13, + "w": 88, + "h": 94 + }, + "frame": { + "x": 176, + "y": 391, + "w": 88, + "h": 94 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 12, + "y": 13, + "w": 87, + "h": 94 + }, + "frame": { + "x": 355, + "y": 387, + "w": 87, + "h": 94 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 16, + "y": 11, + "w": 87, + "h": 94 + }, + "frame": { + "x": 264, + "y": 480, + "w": 87, + "h": 94 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 11, + "y": 14, + "w": 89, + "h": 93 + }, + "frame": { + "x": 351, + "y": 481, + "w": 89, + "h": 93 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 12, + "y": 11, + "w": 87, + "h": 93 + }, + "frame": { + "x": 440, + "y": 482, + "w": 87, + "h": 93 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 13, + "y": 10, + "w": 86, + "h": 94 + }, + "frame": { + "x": 0, + "y": 485, + "w": 86, + "h": 94 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 112, + "h": 112 + }, + "spriteSourceSize": { + "x": 13, + "y": 14, + "w": 85, + "h": 91 + }, + "frame": { + "x": 86, + "y": 485, + "w": 85, + "h": 91 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:1eb3f67ba4e434995b4589c97560f1be:539129d777c30d08fa799dcebaeb523e:cf277fd83435e8c90cd46073c543568b$" + } } diff --git a/public/images/pokemon/shiny/890-eternamax.png b/public/images/pokemon/shiny/890-eternamax.png index 8e493b12f3e9d7caba4fc733abef7736247c5b3c..3e7b5c1721f508790067ab31aef1ccd1b7d4ae91 100644 GIT binary patch literal 40592 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

%ERCn)Z2?M1y3c9EG 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 A#sB~S literal 5107 zcmWkyWmr{B7d>>yy(rzSq;!XLm(myE(uhb3NVfu)4nb1#rKB6Ygmg+b(sc!-yT0@N zm^07$G5eXl_nNiV4C1wxG6bIr9{>P|s)~XRIK#nVj*AU`zvlJSf)kpXjkfkxD+>n-pOOi6hhYuzPjvj5&kFv9S-$|L)x)Wc#v zrSjEa_KqywZ?wAYwB0UK$HhPdy0ZmOhHvlNJpXx)id`y2We%(*-a{ zejuR0!p)^=rdD6z0f#du7Xi0derw+uB!sdW)6WXJ`ObH&sw>`Zg!wpL^37juh%xK|{HY?MdrfY_A>T zS^fVO53Ntdc4uNJ>4WB&Ft05;rU)5!5Ug=nK^0;}PFPLc;rI)y?ljouEXFB7jKg%f zWMC=}A=gTz7gHEK4&$ovGAR2H6NrYIZ4n031vL={8>g*`Rco9io$AQb+I3yV{hDzb z&RJ_CHwJcJ&V)FedlA!S8reT*61`qD4l*mECupcQcfWKpDVzHtk)1}+LtK5==mn&U z9BURZJ)06f5GlKC|AQ0WyshYzwm(ERg|W54_^!#4(Rg2&f> z_`zU`Sc8LxIT{i(^@D0)%n$L5iS*KvT*TMjvXZ$C@7r@v%l1mqTc!?cKHaW-bQ^fe zNVMl`QE72sQ^ApgG-1ZWpcj$wo0lm#)H{Ib5r;m4`AI}hku#Iycr$0Ee%V2uBOk)0A6kH#L9uusJ~5RcHm0sE64_|NbPRIz}I zAv69m6Fi$PY+BU12XF7!!aU&_Q5`}VDQT&og$K+EeFJnL1yY)5Wpgg(d>pj=m;JS6 zH2dP8m(h22ln1|umRjPtq@VypNDqys*FIM)msD)nY+lD;^50HQ!z)+&%d@>$YPAc_ zGhPs27-PN)|1ajuWa!vGHW4De7}?$3Q`u)if4tFo1RWBhBqm?h)uM;hdg zZ|pZGL-$m>#V+Lw$?-#yh-1#gM~_WtUvuW|%Z|hV(?R}BEg4>NJsmP{YT!N?V%;&7{ zNZXx3NRr^Kw-~eKNTNfU4z{-}5+ocG?MDNWUy75G2~k*%=GhgKNe?O16cgbOHKYD) zKB>O!kjD}aguHx5GN)+6$_Txh`L)3$Szt;_WRqlT4^pD{*S%VkaDC+(RfV-$UmYe8Sw~oLG5=#0H@bL z@Yv3Wr#F5vj7|8@Z%KQ>9PsmFo6qHPwRDVmNGw4riFR7R1S`9FFcLpWMp$qz<-rM< z4Va$2#O*@4pq2y#R?-=K=~*yZpVGz02NHhBV)5p;u4h7kz`jG)nS8SHe?Col$O$%e zD#MAUWG7cFc%sTuw_>lf6C{UMPjz+3O;8(v?ZDM(nD`Kgb`Z3JqKSv@Y_SvEB;Oe&s*@^;;^ zS8XNe;|A@yCyKh|+*4NwdtGq?tzNirFY0Fs#_yVG;%f!*XYmUKVP_5Bt+4qOM&)V# zsyo_u>>lZJQPYs{$AuQ&>PC^^w3kP;JL@nLJ7LXFAp8C&wSJNM0UmvsK{l{kp1Bw3ca*>bA~GcT4f4sJd0yrQ=C*dgQ{`xgt(_ zq+i6hZuc5lr~mdF5Hs(v1rlu{i2mC)#a`nqX`q?Q`S3J`nRt6Ci|guS{=FlQVR%B% zVq~aziL1B?F4xLny*?5)a^wCG00NwwJne+b!&JX`uaw`KW#q3bkn92qG00`VD|;Ly zC}G4rO3}5=<$ zWwgUjg!(8g_jyv3x1RUqeP;EbmoZtOHOh7JXqKRl0||5H4DFvC8Zm|Qv&zl*o2d$c z1lBdM?Pli*SISX3AC<>)WaBima1lcJUfo%U(sI*Lyrib7W63hnE$aHxhGpR%=Sd++ z(VLpcp8lQexM4ju7e8X7u-*?5ZEj;}VS@Yq+fwQ^o}hBm6WNNJZK!^q0)c+439j>qq!)joN-F@Y~AJ4D`N>fEhzv&0()@32s5oplC@F|!AYz& zZDVOqCbUVyyR0)RELWx1N4gsRY%GpFa+mH<#%q7SPAyM&c16zmAmfP62m(;d5r-ap z83j7VewEI=vWsV9+61Z4ECVf&B5I{!-MnQiFq%m7NulI#9K%rKfD6qo@uDs)s%ciomz-v3cE1Q1B|iz% z!anJjY(|ku8-3|GDL0Zv$*+X)hA?MkIv8uMCfi%xY+#IeeAitz!rt|<=Q#V8FFWDb z!Z81{iWF!3yvG#a{i=1P47JETnEFcov1OvS+|<)y>9tG zz`7s_M{VsiyjdZ&p6Ex4{7$QL(&#Xk+0<+=pNC8c@hc}TGeB?XSlbotYge`0Ul9VM zOJN6fPYi&HVk&r#Q!zvcPQg*Jv7|#i(ZL5yMStX5fT7afbg@YcKPTd<^M+%S>`qv zmcIA;1o_$SeaXo6(jPP3N#v@_6n#Tk>!tL*pe$p@ZFQRDkSz|xPk-NUY^D>~q)Nmh&VDT;?Da!(~2HFen>66=NkwandAgJS?k z?L`tkkfI^ZKNzke4%9U~OjW7w8{XWTN~@v+Ela9`%F?pL(FRZ%!#*uub^9%J4T;%z zbgdRfO5+ZNP>wVWj>Uj0N0ubj`<)5KWEaGlLU)Y(qZb{bpJb-u7q~SrMqNx}-nHvS zP~PNP=Zz|T;{|Fn@}GzXg8X^d$<*XAZonhYkE$yi<~z?`vUs%dxG;htu)l8239`!+ z3EbO*ZV>GCpM4Tu70lF}jdGx1?9Y*g-iG5XBZw?9zVZLtyHQO{`nPZtUN$-}wQNz` zKA>lF!H_$71?t5ck}nqf%X#8_{`()BU9L}TR^yWWT0i`;SAgpeG~$16YgbN^XmoAm z5}3*zOwq)vGO6E>_6Qlp*Nj@T5W*> zvJ`F#%_`!&F)isDmD&9AlFvdXp)61q%~@^L7GPF4W=NyG+202~pMT0DZz0RD+TPmN@ zA8_<2|5TgYjKmyjN9)M?hk;wa>}?~ZnXBpC9*)DwX8vN2u~S(k#OS;NRLiUQ5;x4~sXa5}c}vZ&d1P~Ze( zr@BO(`~ruR!s(Y4aDn*Lb;$&OpZm)5fT+N4@Xj#sDw}Cttvh0aU>t6;OGY9ba8%fK z5&L`Lp5f=N*(>>=N(8H4jlYWHew+w*@e#x`er4NjG!j3QqLrF5)sOLfok1kNVw>oh zcDD>x0k*n(rIK~_gGf$uTnKAzz(k)k6L?4`o9{9^qXpqCrWLDL6ZzT-qKfUdDA*xd zjNh*)SY5BzT60g>X#HBf=nOdzB@y`m>klCB5@}OV- z!|uOLJ_ma>$Lf3{ANjZ(1Rr`&SBlhRo3BJh95NPe4VyS6-$k)TCzS2iMp3PJwJuNj z53{BkBVnJ_1P+Jc=d3efNz*pOTS^ycNzmeQwNLbDLL7x@3@kjB>>h0$I`(WTOnKRy z{)V2lGCp*P`cPI~MOQ9-FVA9nvG_bTeeR+7w{N*)|FeC9ahz-2E?3J_oO%k1lQ&E- z!P$1}gbxlU5m9Ic?@mU|qt@o#ELjZ3$h7V)`5bF3^{7L-!&Rqz$z7>Ga#{&@psBRV z?}{^J#c!U8dipJ)XQ)6o5Pj1yoTKxr7hkfy@IyMJI|ZY7^K=u&gv}y?F-H7SIs&Xi ztb6U8xf1y|4BqGPvss%D-Rc#*6X3G5Jpi8GIiQE9zqeH* zVm!mx&i_J4%GFBcs-`YX_Ku7fajHL3CUppp7mn&LKAtC%4`mT_d#W-Ndi9Ej{%jK7 zR#AYFCb+zr#XT?MWdXfhL>triDV8yu?97{-o_6O;E6|=*@C7{=^c1HBA9yt>hspTz zMC4PWDA%qdVE$$h2&5wW>{!o{`?vHU*6khSSyN)vrHc!`&Q@IFC8K`QBS*hurUq%K270 zA|D6aGZ`^ab?tYd^BFFcVE>uOw2qiV4&$bJcf6Xi@@54ZTQ_E-Enz5K6$1 z03tR-RB%L5k){YTDBysjLy@r}iiH7DvFijGMAUI`6dRUFWUU$Bym{}eS9UO(Z2>7`&z9wUXbV-Il z#&6`Y8GKGQ04S2&F6MJnWNa;Ck|;8QE#r9r;7G||@X{|>%+C|c55>;RS}qbKr-&IQ zTvLXPlM{>K&(BTgi^a?^4mXV>;xX8n8Ce|RasXz}{8imI52H3ZN4bf ze_i~WlJ|C&UW9+{8AKoW!}eExnGFE2re(F+`iE_46#!l90Z_aBhs|Iw0E)7{bq;-T z9=d#9QpDmcXDh4R++0fmpKB>E=%LdZt9g z$j;($`3&Zthxi`{{&gM}5&R^+h%b~yM9Zd3AWW9ETgVfL1(`yIK=_}U_z%PWq}jQa ziQ4!P(3V&Nr6C$XejWfQDiI(Fdt@un?|lo#M+5oIi_w{wo%_#%{(V=tO#a9gB!7-$ zM?^BX5>d|Vn*3S!?g~$*UQipUP zL&zMmg;!4Do9IA%up=Rh?=qPj=x&RGBx1dpI68aT- z2O}^EromdU5o`ssU{5#*j)WJ%$?!5bA1;Eoz?EiTr=n?cd`V|I)p<|3O zju?MT93~aB0<#&j8`F+Cg&D?-VWzQItUA^l>xvDRIYI4MQ`g1<+DyrL=EogS06Xii({|v`U^zjmmKqDIK93(F5q| z^fLNk`gQs{RV`IdRle#b)i%{Ds;|}NsClUI)k@Ub)kf6bsWa4l)YH_rsduU0(?DsM zX@qO!YV6TCtMPOWZH~(v?wpc2hv(eZgf-1HBQ#fN?$aF5oYvCT^3%%Fs?s{6^;Da# z?V+8jy+iwi_M{F~$4y6|vqR^k&SQoO!;_KDsATjprgSxR{dFa}^}2()GkV5)QF?`X z?Rxk03HmJkB>f%wz4}uIItC#I1qQ7Kw+-=zEW;GTU55RJuZ@h2VvIHzbs0S}Rx=JT z&Npr~zH34@aW`3J(qMAU6l2OVO*7qXdf5y%vo}jIt1%lghs_<#1?IcWhb_<+P8LFo z28$a^64R5J!)#@aTGB0pEekEXET35!SjAgyv+B3{Xl-wuZrx~o$A)4PXj5p@WAm%6 znJw40#`fA=@?77!tLJvleQsxN$G6*KchjC~A7a13zSsVPgQJ7Uq0M2^(ZDg$vDWbh zi^d9LZDyT!LOXdmt#&%*^w!zIS?qk+`4<X~g?%562@eae34a)26HyS+zks@6 z$%2*zuOhu7%OdYYnM6sVdZQJi6QY}=U&naIl*dS8tzuWkUW(I*6U24LW8oFzvR(TOpMEs5_rp_~TJ^wNN(wM(bC zZ0;`Z6P^ce2XB(^$}i_nB)KM)Cp}7bP2Qe7nc|*Ok@8f)7E}wKr~0SXrM^xJP1~RL zDLp2=Jp-4Km~m7{5vB?IGPN`FGKaIwvx>8%%bb_(Ts9>N5;bK**^9Ef#WdN^)PTf9 zvR*Qp{o-l7TcBI8wqSIn=gRt3(5j`Y zdRObOE?Pal#&6AmwS={4Ykw%TE-Wv6xh`g1Pmxy9nxe7we(PI{6^cd0H#WFzsN0Cz zDA+i-Y3`<~O&?2mB^OJrODjs>Z{}{k_?699m0x|@lC)*8%%N=0R?Jr6*6Z8cw;d=~ zF3&F?+a9vLa|dHb$&Qyhm+ZVyVOLSNi?B>BD~E ze(8aT1AWbo&CM;EEoH56tE6@EV8X%6-*|u1-NtOIZ>P7H9s-9XhaP{M`0e$>L5F*f zu#U8SXZT%h2eqT56Y5;vIn|ZYCGC#u9zGg)w718lr{jCe@An_mJyvsE<#^c%!il02 zpHAkVoIaIx>gnm^(__6$dheWxJ#(!uyl?Pq(Ao3ne9xWf_v}A;-u3*k3(gmgUSwVD zy5w-FbHIL};|Kd6ItCpEJBJ*Hx-UCj?irppeBz4xmD5+fub#UWaP88_{E^}7QP*$Y zNVp-r$-DXJR{E{yw{vdK+*xxMeYfPE(!GlNn)e%iH2tw%>L5Kn>ODH}V8MesW8ASP zKV|>)e!S=*`C-L`&P4Mg+egPHeJ3wJUif(YN!F8@r^P=j|6Kdbc>FRj6+1Ql zT=e|YubW?}zu5oM?q%~UXiAjm39GJ&N#pQ12 z_@?{+@%sPX#P?bN000bhQchC<|NsC0|NsC0|Ns99#84&x03ZNKL_t(|oa}vxg7c`( ztP#d`lJ|e#i%JsUEe6N*{B!R3EZtvsT41Rp6=EBP|DOMzfA%4A`W27s(SXC3$0eJ1 z`O=~LSvAkAWz~$+7kIoG*RSK&+1Tw69Qq%oDTLskZj)#VE;gbzeoSn#uJ%80|Gjrl zwnF1u9JclMdXqfn>xO)1m~9LZYHaV-ag-F(>wbtiFSL$Ox&W;713} zsLBaUTYd&oR1d#^w>}T!IPzD=U(^cs6I#^F;?VWcTF5FYLfvm}7F&s4F!}Tn>5-rO zfFVYUij;pl_T$+7rq#?Dzh_Cdk5v(1M#W%iJ^^iI9^phXs4RZfS7@L;Q zvdnmq-(qPD4$GT8Y#d3C3XvJP*yB8tMQEN}4fjMx98vA4o1zh|nmlE#;=7AFCs~Up+_9z`wlXKcq1VJxq_Cw6)I2auD=giInzs&b{RmqDfZ(B9GTk*R1Ct3WE16^ z58ry56eHdeKY^gnEbtUg6oIrT&AtcIIBW$)8Pu6q3K~Za5jkJpkROCeO9m)jKsifr z`0>M#6!TW+%C2=6_eJ+D)4vy2*x+6G@dJ*`bg&FVV_}1N9*sGM=?07# zLu>t;KyivNM5^i$nB2|&gHtCyu~iT^$iG<)!`tM08x5#NcK8=0HyAg~y6 z2d8(arf{y2yUwZe0Sv*5Gn3dhm0J73kws0J)s@M{En);npV}iU@{B_oa9#iual`)| zUQFL(UiBo|7wc-dE@`tuyX{sD~%~mf%Q3yfEoFI0R5g&ELsh%Qt`8Z&xd0fULFDk^c zqlNJLd_M1Y#E=)d?tV>XsMn|$r>4}8;l|>};WtMXddJT?R>yIEiBJJivxPa)3{o7L zx$`WrR2KVq3`NQ*sIkgID1QfE+5)r2*%+oA@S;X9qkv(hm;g@T7J(2HBo0TBKF*xk z8Zi_-QqyFFJ)*I*kqq*`xYh$8THbE3(mFR4R&K0*ymsW~@j=LgrWl{+RSVe&&ifma zAEZH)*jM=n2g-7+BqkLe45LXrrafI@i(n3I@=ZBBbc$`abzUJqN&_+4AkoKWB0yg( z*?m5qJ#U^vqvBBmLX?YQqx-Uch0MLhX#-pfvu7=G_wT@IR$4++8#hjVoE&**wE|Dl zBM>uG0K;vdFe4x(wI?&-atR&B%3|xz;_NNGj|u{YvMmIozF;iu&`{1?)E}!mAO9mL zCxeiO8Qxq^Lt~4NSuJMJ(Bxo=mqRQk0M&{mNm_Wyz{P;K{1k{d2ACI%hBj`z@ndo1 zRt#|oHW3zg7$_8>LW!E*z$u1ec$0C#iL!tp*E(UKMkbAO(t=^MkPqd|&2*6_!4SuZ zsG~T-_(+u{yk3L&6AZJwKOdQMuNI9tG9UFq453BKW@(%x3Szp>HK-pj#0Cgrftb$^ z-MI8)bL3V=!6ROvNIdm0PzXX@bBTrI4aOIz$W&rHP!?mLebPydOipZ}jBd+VmJhg$ zw9qD=Fp&JV2d=aT)fJ6CLb7b z+=m8p@Z7VrRIn(7B8X5!kck6S=)6vUH(`i}WsZFd?=JBzqVt`S9NPw|py4Irn8`(|{+7_6%BGbM&jGpd9es<{ zGTIPZlO$&`1XPwDT1!vmJHP;J0eJC&8?XI%zENJWSSOBr7ARt}hzR9quBDb*>0;z4 z7h&kDvOdkT1*MT+sew^2x{>d}sp|$D<|WaGolr&Cz%hnU5nAwT7eNV~I3DdaFPLQ6 zQQuCr!c^B7sFPaOEJeRxO4VurSk|J@#*J5gjG}(?*jr-V$g%n=P#{v&VMyP)^yJ~Y zvStv-+Fs|*>qLabxdrms&I?KrP8o~0#;%))QuQ0mA4$n7dcnMKmq}iNL$q7w6nm(B zDJd!g5{4|kEh$NAx7~ZZhoQXzBn%z6ap}i59NB226T)&4C_ciwD&EM`ZoHoeur#;A``6Kxb)*DM^0@SZDhi75h!F-Q&pwg8=3#|o3b!_*@&Ui z$fGsjZIFRv^Wp!!k#2nLsE>k557EKAdcgRBCdkbSAX(g?=5;OAS1Lv&9N9(F9N&s= z&)RLQ_51}xW`Goi_HLYGb^FM8I7l|v;D#|-vDVKGfPU;XmAyu{)n$>6Z(!)i z0LN~;^5dc2kq>F(WuT~kgUKHm`Kz)TYIGu=^~2v0t z7y?wFkeM^)Yykq*@@nO{GzFHT*t*vvmosfaFJ_o(WpCc$$jkO_T==nf_0{%6+ISTx zT3=yhWRZ8MtU=pyZ5ZNYla1_5J~xoc_&M7Q$Mfn%i)kOIjD9%Lftzc%@8hNKIO6I}D z;8FL_#Y1{BGI5YWpzx^y$u_$COSgb;#b<7^#gHX4Qx7;gXL-(MMR?QK`l6yulM_bU zGCuKy*$j4KO_IQ;BuOl`z6?XP2FRUVyD^pC(C{ipw9&ioQT4kh(T*Y09~s$-vu0zN z!-Xzo-uH;FoQpR%^MwyY@oWT%731c$cmBSyssErB_)fJi5Tbt8auC1xRl zDg&quw*`(m9+KH&=zG>@R0B9&aC0%!QELE2?SUH?vATiS9nnTvL2?i%E?yy7Vf6|$ zY>h0NfM*0Y)|3zmfZ+@Lzla}7BMbef5$M4TYtA+jqR%kolT>HYy#YJuGE#C9B2da5 z$1F(}In=2=7qy&jDU(78utEYMbUg1o^%cwhZWur-X>x$87 z3iNFa<9!3@s9+X1b}1Kpq0>#Yag>R-Wzix1X(G!byTS^x1?$Af?8*$Gg5a?c82o#n zR*82SD3(&Zs7J-KhP*~-w-R4CEGwFKLF=}6TY)v=fEd!wL;TF+a%VmF)l5j*Onw_C z+v={}q;atv>~$@dY%Ko5Bkmgj*zq76Zi9+nd714BsG~~Z3SIBR~k9G za!dwRsSx@mIc6U}>%=4fnvo%XOxs;Z5vmhHZi}JhvG7${z&z>c4tS)~K{_8P1Sd0R zi`$ht9**_S?2s`e9Mo(G2#Mz|*H!fA2a_K?Nxl#oAOhe>! zt(WAmITaRV+CdfyYu8)mCr$arL7{avCen+#F0+Zs{h;>^uoB}=bHNX^al|qZ#6($~ zcD%e+p^47P3nVkRYzCi<>|g|zOep{zcDzkIh1uweh?fATBxpl}3h^ZIA7D_ie)YH) z&#nqkWgO$RBp-O z<7Rt>Yb(@>7pxTG2V-y-wNXf43i0cd<}|->vXMx{OJr_f$DXy{O63g{h2i5{0W|z# z01PvXPMq_JA7~@~n$9Js7AmapQZvs>)K-{UcJlQWuhLWecN(aZ{(175TbTL$D#p;{ zS{L=BA^p0-5X@l&f~QW}<=)qjgc3{j-wJ@YY2#42mSJlJzqP_^Q7fn!V%BzZwJY}* zKh3HXohgAn%?=SmQ*_?fYouwyOe@!5>p@f$RMaaCLy3hZ4Bdm!Z4K%1k2>G-EmYj5 zjl*yuIln@(f|x-Qi)ClCLQIf;3T}xEE2jTDM(!hiUIZ=0=tGk@(m-Usaga(}?Q^^{ zN;fN3K8cP}A#`B=fPcm`GOOD8{I*)D+gG@!A@i7;CT`Ql+E++cFol&?nAHku23TRL zZ=k#QNu(D)n!GS__RwYIO7Z-{;l~_@Se=veRjME4_)mOAz{vzc8C;a6Gz%4*x<2n- zp{s^OU|-s3Rw&F+SRul4^kxM%1FSGPUu}i1;#K;(@)#`J#OH;hoTDpq-r>@&V5s^~ z2`#C}0EYjkhl(?S&^cEP{-f zaUx+T+`~{`1E{>dw6PGpF)~{r_?0Iwcmr6W*2o*CIIN8Pv-slC_1UBsqA+pbFCTs! zW9hX>F5_Edu0Ry~s$HebLv4+MVrZHeMC`hwVkq3%MAq2=BCj)TESlOFIavX_1lbOt z?P`T)E1w$qG4afPQ`ndnoICx|M=&HH@>mG)RXwt(Z-XHX{`j6TL`Y>JSR7)#-`=oLyMs}jr_Ct zL?-xdp%gVRKlysUSul|pK$dl)QaD|^F_G4G^=$q1h+D@=N|I#1BS(ij@jo}&!vOsB zqK!%DS0jrR%2!yVTw7seX;C7jr^GSfmFOp@JtwrG5Ym>>1&O;W7D}rN-q}Z@d1#s5X;`A z7;=}RrKJE7Ho!KIWqKL_P4uIU_6i3^Rx41n!p@V&S}TZrk8abEk)IH+8K77VDxU3h zQp@QyfgnhJ&+57!-rF(8c_@=8M8XiOU0ipRY<3GnT@5e{U1_80>d?qCiseCwH7kqG zIITA`5cSB0(z#gmCIe<&cbQ#j--i&CRf zh`kb@Fh4n&_edCe>u{-5y96SCV`SFV07)aAXk+;XTO;Egkm?s0VeQG4R^ZVF`K^(k z6Hj@k;7((t7+S`8eRfVN^x9e2NB6Y0+ct9+Sg=}Y*g#W$TedFl9 zp8+y z_AgTiab1BBnuXGzZ_YIv~&M_#ttmr6*Y7^eemrHs`Wnk))UxPi&ZmMlH~s`#808mpvsHbDMp zKiYU~_ygK_=E+XZJ>BNd<9GNH=Jv~Y9~Hmf5iw|hhWf9z@zL^4ynY6( zOQykG@e`bkfs*lBchnMwdK*AK)0Z}$8rfrc9(_4XBEQ3xk@whsO#E^no{x3Wg8ro? zBZw|Pgqa1Lg z{#Y&{hen;~p7<`~%BQmdQX?H{QS%LVYF-MJSq|;34fW4? zXv1mOoef}(^rVePXBqR5&(3IT>T1=~S-egjgy~6u^7uOkodQE=pKlQT7Y6t%+L%8p zrHN2`i%#RyS-e#Q=|K!R7gm%s=rmS`Fv`#K8u}~RShnbOvC{Lh`-_*+#)GQCtm=AL zlNRDU)57+7Ttk0F8w>ft4&3PzDEf*Y(sG=qoS0(*UB+#k<{Wa8yHm(}5eNMds zQCIPUuAG064q;Si+$0tD9|wjWINJ zqKln1lpA`MHkOjp!175){wwj?f)DfjsB*%x>Ml=o^Zw6&r-o8X57Wj%@?<5nZh@ks z_(6m8yj0J6);uAsA;gO%LLUtMfrhN1r)gs$8Y9bR{rK<14-%yLKGg>$(gs6>iyb=! zivAkvL>tQn!pXX*e}(u##^N_`JvfuNFVbP={?TBUW%t)mAKF;9fLG{Kd+INK&~a3E zHo-?<&V4}CT|<3nNuL){_7H{j8~7egIr z<3@bnY}`9c`~`*_PG9MVq5gsNdD^&=-q!~GOY8~e?=Sr z2Opae*8}k$5&49WKY#wq2aQh#4$q(x4vW7&GNV#t%))$ zAx;wIQ_1qIWmZ)E#_Sfsk5fjG_n4$(@qAoUFbm``p+ETTJ;;ymC0*Es0~hop54(AQ z?(dS3^IM~gaRW^K*H1nF)-H~wTAo`1)RQub65*nM?NFDEOQx&-cNz3CuNid`trtT$Hi`MPGMqYjBxKQ zNDhqigb zN(+5?V}#7xvP;xO4ilw?@7S23&s|QZw?>f>J0u`_$6WB9Eg0R4b!1CQ^;on~pP_Jp zpOf*rMO=5L$YH)>);K>uwnAu6$|x;#|w)xq+JcN*aE14GecMGC?#QhJ|;oKUVe%_y#J%I!%RrG;+1 zQF@DHqh8ELJ~Htw8%GSunS+9$4;Ef$APga@NYA`%HZ=Q|^@%(GfuIuowwEYcyS49!4X*bU#_!n5%R4^tUGNI6rsz)@ zrI~)b5g1~~yq(4%zOO4?e^i+GRv=>CKmq)J9D(>W;J|W+*PG=BG!ZHNLi6leL7Fk7 zegDk60B8B^5fBv@dnoU6ixYOlzvZS{(^-GYsLgcajk;qOOnMb3VB(=L@f{mOLvgs_ zB}_-r5j03Z;q`vMUN1yYTtq+$u%P^yvx#geHPKapuPa-!apQ5xyT z8>3^zIPq2x>itHrm)ARM>c78_Tq!^Jb~W z-h&*BuVoe}$>Z%U)GM6urA9iWxrL--XYiB&H}EU`6o^)VqCl&(6qp7@;x)KtSMEz0 zrHOvLktPmAnA}hsj~g4CiBU$FZydL)c7DH@w?!prY>SGD`gsLdB{nc3Rkn`G2(sl5 zPvPnAfq(ZDfnvm4C|XrX3rsAo+!%*kPs%7w^y7^*@fpdCq1TJ!a4J%pjRi%Y&nRW1 z8;2k&hCXjK^9Lr53I6?N?`3^`K8ugGm}d+RT7)~rkoy8pih*@@H%0@yH?csmaWqv@ zMC*c^L2tw%*N-v|7&!IgjW`1NG8bk503ZNKL_t(1@I24>evb;FV;h6Kw{~6{;Z!_+ z5QbR1k;K<)keLC8WW2mTA2&4C4mgGi{A=KO`!PC73Zaj98l$yn=_3IzT~AXl-7wUF zGNOUtI`BqH!ecVS)XC>PsEx~V2j&2YXvwIsk+Gm&qwv3k z=lw_4gbiBI9ZE;rw8S>zT+ff=tmjT!0BP$sOxHVPzNQTZ(@kj#!hUk6|kES zvig>j071*5S3$=V`f+o75wU^`Z3SKo7I{or0nc9^HbP*|h%=82t}-O!RcN^-cVk*q zNog<2C>RY-^Tu?2yBu!|6!pN6EwTwdfrvwrn1I;@PkKk_3-%pKc(qN4Y@VEwu78K; zkBhh12pG5UA&MEUHd$#c352T0^?Op9zbp2LIqHN2IAZu-quiTQGBAs)W+b~n7W&0{jOu84*QUm+hk&R>eY{wAgb#a;3YDL(Cd z;V{%O*_0}&DV6O>>64Vvtc`i;jM0t!D&9!>UW*b#G^Avv7y=#% zbuKe{M})r@O0e7Y1v1)gfz5}tQA?NDh1U8;!V z)dS!<+d4)BAF{O*wHP`grNe`iFPlObmLU(aUWJ=b>6pxElkSu~xgysi7aKlD%u@csrqjk^yZsp% zUo^pSTVFz(0)zmsLrv_zJt3u!QAQ<-a@CvUGB{d&&d=FUwoQ`mrNa%7eiMggK?oo; z!U(e2%n-z$e&_4}!b)Q=j3s`rMgfGZ6oGw7)IfxiXv3$&u*uuVa?vdXJP7`2Z)j-* z3B#5&9hPkwhGZs7(ik0A7i&prK^YHnaua1V4kKG~i-A*p&QCN*F1QVH?W1muH4Ke< zLGvQNaDdRLEB(!7l&Z9B1ujMenXsowInIJ1e1yxoBE41i8lSp7a2z$lX@kFISQ*+MB`!;RtH)<5IT=3DO^3A$^CBx$ zSovtL0YZfptfhn@O1VtlMbKkyNept|s8ACrof9clI&n=I%Q&~ekR>xOeMIo)kn51Hhq09%tmH~(84pXJ|9J(Y34>c$R41XGmXfS z`YragE{voSL%G7v#k0{OgT~n=o?cpEiy?$@2^8dX@M`f6^-F=!eq4N?l;*PUP(vA4 zac&*vGKLVUbN3gU8vBy-HH%FNl;y~o3vf$`wcnSW;F)AIK6RjN>9M5=15vg@fgwspuz`pE3guTi-KBn;l%|5$ zSIU@ZKpE#G%q57z1#kzt#yJ^#Gid(1o*Dhh`tGXkuT!w@%AE&eVkHRFDzj7Ed3^8y@+ zb48d#_XVS<3(W!rlm9K}$DYrNH~d(m64D5%EP6826XH`tV*exfrzfs;FYJtD9DM1( zKVv9I=pfq_4ueDG6E%V_Y6giED=REo&9p!<&%S<{;%!n&R)_zZktG3>=y}ala+rgL z45QdmplPNe=aXczlAj^?DT>@O#^^)yizGkCgvJ=k1o-xo>}+9mHap*kkXfyi9db4w z??yPKLEtoD9V+xpO>F(KkUw+rFEk7$?~+on@>MgCMMB|n4XAu#m|JA2r8+=xWBp=L zgHYKKO`tM-f01B^INcDxLF~$uS9RTfvYlOtA(Z}FMW>O6%K>yif^jKFkgPMf6+kl& z+NYUpg+&SJjJ869m)#|$)#5MWoa}r<;&NE!5n57>+^@ZvMUyed_(HG26eE;S35;VJ zU}%H7mgm6E^6z;zJ69{mo`Ui!1xg9V89a4fpjmd3?1eNN+g+PMcd@b=7Fe0h(1M|R zq_kFkMVyoFn*&6QHb8Vqa3~7kvfz@zv9W9k8V)weNr@8yYF5w6mqN z_y$ZzTleUY%!tV_gEk4qNuGyjuGrjzKF(>zy#NVAR6@yiLPsZ#ft&HlQM=v>eM#vy z&be)z!+8lDF(k~*jM4hN8I3Hz1QsQ zGKka(=4L}n^^6h)-G=B0q9pp}W%nC1qy+(Hl|{{fTPFlVy8XRMcwbUl#yMR)BE=ai zz*?Le;Q)(av{E;&>~_Ks)U)tO+7Uy}J6QtBtSHFTO*@7pSkTtc&J3+_R4HAHWJ@){ z`>pI0q8Ah0EZdq0dOL|Ltw4e#v?OK_`lu5*?z~_$#W`{>H~>;v+rTN4dsNUuBJ04=`~BHqh*=UZdaKW72Zlbd^Rk!= z&r%hv>AThbWK%_YXXCq)VM@`0&pXFOW__+OV%`-v*Is29OvYv^p}_~o^7;i)c4 zBU==rZ7G}H60nRI9wpHjE>+52Hx%-_<{4O|fMf=knH%MIpqpcl9;CFKjx90D`m#8e zF*>&ooNVR6Kxq+`*oCFB%!naG9)~c*i!ZVCupUFQPPZsX*rJ8P&fYEN!iu4~MJ0%3 zD8XXfnigl28OEpR&?bA;PS}~jc;ng(A2#xw%-nodbQe;(;?l{bl>>3j>&*v83r3f@ zec&Y1x*U7c1|!Mx!-!kZVk9EDr|+#xoTUYAqNWAeQiWQ(7}{?Q?X^&ZP=SnE%jodF z(`4i<0k+66B6?|;T|T85P##ECie!clxT9ozGqbLsuWzR_DLufb#yLKr&AnB8>%a+_ z7YrmNF*$6H-HR_ye516*lQtZLcxB1dgdw>$@|&S`6{FbMs6?%Bs~=li@)AXc@#bI6 zvWsVhAu+RQYhE;77e-cRW;44A!1gDl+s@nQ264{0wfJU?Vn!|(@iF;~RZ4Oz-%BvW zX2Gf$Ok7$MmF0Ze8rp9S&DFS=1&TCKeWQg8y9*=uRfgg7ek{Y7@b=p>)(w_sCYWIr zDHN~J-=uc95ZRlQo^;;g+!mvAD@NlmZo>97Z+p}vzex(kMutc!Bl*yJm zF*LFz$I-0(grV9%R37f8i!&_4i0B+0+GHO$ND_q-xhu>Ne`QUL^MvAdjTw5A((LOb z>%LRK$Z?J#nrzTKa4I$=SfG%Y;bfN=SI#)KkLli{0sDPJeNy;p=cs8(1c&KXAMu?eFbJ`+1!y%W+| zI($P9Jx#w>;$DxTd0lFkeKat%Segnuiv!o%*>(<2I$W|fHz~vLk5Y8V0IiW-Hbc6E zD4AJKuGeKN4rUISH;z=8`H~QICZ*Q*1B~)@(c1aK0V*-dm?7IG?$9oFmq>8DoYuUF`DCCJ4Sghpqys6|BP_E@Z@dF=5GC;z_c1ext>3{Rc%EK zEq34s@*K-!jVw`8DRiby6VN!IEM1O}KfI5ugh(D&fYbulsBAeC>_xr!LD%w@|;?W z&?_dSm3(iGAzO_O{(5S{Q2H~nV@hac=)#3{ZmoF$ZhI~WB65aFqUBaEVUzbTG|9s1 z^plMlT-dA&s5A2)kkTCIwird6n+-&*7$sd;vdUv==dL=&Idh<8x`Cn6(n=*b4LD!A zFaxdK&Lk6OvCC7;nUiIhwP;{yLQ%7^LabOr=+ex8L`u_^rf(R< z6|1cn)dh5GE+jV11Eb7o^Qx)nt)56G6k+s6u$Z8vhw7a+9#s|z#tCZ)*h!MeVQ7K%|jDdP@CB@FF5 zhFS$}c+#JH?F07Ng^Bj7~=oD$1GHTBfC$Q*SISX<(jL+cv4q%tSWs zPQy^Fyv6wyyYN6wH3|HMovu-MvKoZEW#;jOI9`~!Hz|c?=1maVU{oETz4H==4*xL0 z|FmbtJJj34@dB}(r8SbSGcmQM8;nECJ|oMx@gO`HK=wl4E4kqcy5Y&+LPY?9$O-hN5LE3BJG-&B^>XXR$$JumA?%H~klTX11A|0_i-+H7(=D0ZAaekk_ltjLXb=+cEivl zbD2{gKGBtwBF^=~Xbz(7>$56b*lbE2WR>s}tE@o-eTA9MLJ@rGbzml_C8Y)jG?(7hPL5?=lmYBG#^u3{F5a&9PiuS zoLHt1m_l^>-cnEhAJr2_Tq?8+> zo){%ysKa0*J*BDb0Om(5y*|XzB;P%d`udUEdLGA6cTzf_Xw(ylayng%(C_JI>0w_@cuX!w3%2z0qI;9l)b)OW z=+7~PRvARSgm;n5(ITe(E|vgwK#ITaOei$a^&2_;GZ;e4|4g%YAL?i6-lVi+*G2x) zu`uyJ_4K2T-AQS8kp2gux~DIOx?16%7&-lqJkMs!j+X9CDE@(w)BnOFn--rH`5%V< zPd*)&olH~fevT`d9Wm6IQT%fwr_PUaQY-3EwnHg=c^aVo=A3$y(*Mbk6YwLm?bx+g zeIxDVLvmlpG7`Nz&SU<&o}Q}${-H;%*Woj$a3&bej(J8h4hZ7up|@;%b*{^F`+v_r z{pjhQo)@p;@NL=s`amoqoItP0A>8C>hm!v_jvfrHJi`3X3s#Slb6}HMvABhgbPvC2 zJsOSD@n0k-C+dX=imTuIUfUC<^bZXSoo#)RE8rfHxqsBv`b&SnwVWNgiGPi92Cy76 z?|+=q(K}W}GRYCE<0$AMzduB?$0N$pQ`DZ>e1KyVvCrAl@lNwwk2Co$VIId8(6a$l z&RU=>J^T_WU|oePGamE35{-yhyjjr5SY+>R1iG~43`aPxa z{B@!~9nk5pij4gBgvh&cfgvyoSXePia$&>+YFdf+N&JM> zM9X`|iT|;I+x;)jS{*gMEza9zuFSmpE;uE*g#@Q`d3l)$8R+)o5QTg?{+544@+t=> zfwgsZaOuJvi*j;urfgvO&D$W}js!O8NjzG(CtvXcrH&(xnt$UOd79_>vv4%RM!;Lr zMfN(peQ-@t7WdnIieDSI8zt1R%ISRyx%b3+39TYzv`4(;vJKxqEVqun_&@&y2VS<+1m>5=K1@P+_4rVh9soCxr7 zn4BiaFT^Qa>mjHs$>iV5P(n!K_A<&15AL>+ReV(VnRuK*k6+#p&A}Z5C(jXRL}m%$ zg{w39=3}llE{nUB%s`~vL3pzOCmrS%pQhDtT!{&=X_0f}*ZyYFLN|pu_*_o=lJybQ z?}8|j+%e@{_B(-0Y-3sAb6T{!CtV7siG|mA-_uw{5mJyi)gbKgNS)UJ( zzj1z?aQ!g_2@Vc++1oILLB&I(&ArYLEf8wr2Z#>?8z&WgTV6nQz!7H_&8VfLuEo>~ zUy%@p<8EoTa}nM~;9dpmO=C-h=6jZ{+7HJeS1eLm<($u)vZ%W?R2DwXFAyDrRUTgN zRo183aRP5VHhEgo;iqEZgwNM|U?UHlA=n^uyO9$_0T|J}iGQnj!t_=g9ekwT)gWKS zda>Wc5EAi=w+0ljZUAp@#!w1x>R2Y2Z;uthi&42}-F7+0S-`kg-IP1);#)CP zggPSYQ_A}Dhj7hon^eP)1a1xwz|HV_zhAExs?;Wamm!{Oj28@bIC z22m0S?Zg}J9i;gd49V4-L{Xp4EFBGP*mN>xnq|b=&RND6moJAbc~sIag^1W)HZc)5h5_1BRELTk+}&%(uDP(hw{N1e1OCBVkDAwNTQMNhL9Zckl5IhTcqW$t25=j4``7+^qP~ z3jWiZg#t=_STQjB(}99GM+1gPvH>t8RDHwnm}JEt&aU}fp=jeEg`0UuL$h6tKu0|msv1^{FJjkWdSk(eqeN`2?FAKT-K?>IDLXc7#) zUW|~_!Oa~bLwo^I{_@TaAmU|+!<>Z&;caxCDI%79$f_Q~)X!+^7|qo1gt2j`!BQhM zQTrdc63&hUv2pmq*P!_FtUi3TnX`ch~rc|FA1?BjvLzeI!_3&Y&T%mnJw+~phiJW zG{P(tj}u$1w$m#jyg9oiaM7@=T+2yze?FB>Ahei`nBCPt!PO550Ztyo#`6VYO%7$ZD~uY;R*Ba2Qrh87UzX~R;y3~?MNa8(F3rA4Jo4ud}V36@~Y z+0vW~OcxEjr7RH-x-45HkHuJ02C5xO)Dm2II)Zb~_kluF>jF+62O#5$JLbIq!LTeE zxpf4aEs-H~)aM2xuf#iN5FLhMm^@~GHu1H?0s9Q)nt@(|x3jdFx+UjOUg9!p5i`hX zkG77{Dq5}shE!iXc-NeBI#6iF5Xvk(vJtV$9r<^X_h++0^{9+Yw99u;eeZMonC}3h zwvu64#ZXKcP~AG5DPc(0M_~Pq*Lou+lrYT>2-#xm`kX_nDSnwu38|kv{X7CfvLgTK z+15f|mji{S<_(-sRD1{Jj(A7S`!bg>)EilEC&yv{PddeL-e=cfWQfhpX!+8a_yOV< z9h0$bK5x8=uM(ef4Mo`ke#S?dP%%q?R4MAP)gB9XEtk5IY&s&t&xW%@-);(4F(kNS-;v_(=ge6 z67O}#7pLw-Dt~iGao79(t;U@oa+T1nDS91?eq5TeN}@wLxG z(@k3`%B>(e0RJXGiIq>$eG@gNmC(wFG`WS~$@@wVWUL9vJ}$fM@{q2)p{+;1;;E=+Ru?YBPTH)7;U3ff+jTZ0A3ZH>B%`A8?qf#?MO#X#GBo$ec_giqx6sEIj4Xq4Xs1faTvlhQ9px?iu^ z91TL)Nn!!6Rvnj5sV@?Jk+ZlE2MXh?tM=5|)etVgnKQp;fdnB?ya7KvTh~??Cw)sn z;t-404TDa`t2(&RHBZULO}j=Ep?Y41K~%+)&%LLl9_rliYXE^2oa74H(zZGS$l|*CLAgus`4FQmd+jLx!ey{ z+ZuomWKSvT7!R`K$5UB)yAsp1h@kjiBE}NR-@b8+H>ECz?KVIjs3Hu>+IR^;t;N=& zr7sCOPzo32C^HWfERj{NHz`LTdIxm0=v%YDdfX_S!JR{>aOM?;NM#3-3%F(Hqh|{{ z$p=36c}O-sQ`$kkp@Ou86hfthv#4Lipd96svolyu@UI}Eg!+sjy>4{|zGnu}=*Rq* zt;@4Y3}u3dG^4TpKxAV zp>*q}8A6w4i^c>)3-)Wmz9V-!#Wx-se?4fY%r}#bu|y}XgeXD!xQKMw-c5eD2~Q;D z(S+A6yxL)7yHd9a2}8(bzZ?%3%BXL>Rv%bpwJgc$oug*xz zIeRR@2~wr#oN0<625g>4S|F(V|lWTQ1S`^KJzdA+pr*6& z{2HRBk_^$6PKsC&Nek~I(9;l3;#-uNB&t@uiyV0p3p16HA$Wjjr^)dV43U-0%&kNr zKZw?M+A54}tV?Br&iaxB&E7HV$4*$|PzcuV_Uw@|I1_t}FDARAgF7}wLZw`F9^^*v zCAQIy1%pu3@2;VU(C|?sZ z)P>mC_a52gJcp^l(gpi;)}Rh>0iuvGH2OmWC z5S37}4ai?Yg>SC+Wv98SGJiIc9@^D!VE&QubGFgXO9`2p=yGg zrFu0)>hn5Y@PeL~qG(>C^q3CDhN=Gxh&pHK26OeuHVx8)917_)mAF)EP|?%G7$PBf zkrKu3Ox33&>flJE(MH=E4BRZkfcc>d?&}rC`ohYs=yeqss%MX+FExkS^t|z9rEsfM zMvK5`2B(O$$<5VnR|8QEbTjbJ;%lYHzR$9~u&BOHX-OC`#GR+WK_p@5Wd46IUI~}q zJlTx1b{Yz=)W}t%8-=;8!qRrTS=)}Lp6XwwhCQ0#sUAbqY@L*aBj(K7=V+CPp_g)s zh1U~|b2WxG5OrIK$}aAgA#`T(mD2fs706is&^xQlFQXiH3C;2J9=?&tRWslq#DtyC zrh{Re!*MpxV(2J8Q_1l*3S(2(D6D}!>PyMyk-9$5i#PnpkEVh{#{4y5AE$8&AADoF zF^CrVCV5wa=ok}DY|O;h$=*X~DDFIPFFi6s&K^(4a#kF zm>?R!irBWP=XK(#^nL9-$zmj8xx{;Z8AGffS&k&-waY*RcimvfR>zfO9jY-5`RRdL zwd7|j2&NyFK8-~Z8--Ok-4T0K!7KKdUaFcr%EiCh;*s2PHTNuUr+IGnv*W}T!(CL!kD)PwBin`ev zD@!o(i{%3yMB%c)Yf6zsQki@+M0e??fKY?@wTk5E6T=Wxok8?g5KS1K2yOo=P8p~~ z7G15o3I?(iHDgnJ_D2w{hFD2sFvyX@8l0|Vk9h_bmR_^R_`K`MYhEmacfAojaSQx9kxwRZAYXt)P;b#SMBvPbt>OTHwJ!%%D?Fjp4A=F4S5fR!gmp4@tv*s2kQ zsM~UuM)8_zFaop{&KN_Xj0_9`GAw(+Za8V+Qsuk~VpWll%`I8oVCx&Q{xFEv261xT z#AmB8vGldmZK^l6dlOrt580z$%Q#(Mot4(ZVo~-)YYf4cSF#tG!JeAa%`rr`;^#K; z4s$voR|AEC$pkw)t6awDvD6U%Cz`oTW_*g6l!;9_bV%0UO97&1E^p0FE!$GuvBHew zvBG$_Zhhf*V+uNBk8-u4CjPI=(xr?;+Jx)O7VOKG4^;AEyfltvtDG*A#EnJ@h_dtz z?X4CMdLBZW*XH%$JdT0;1^w43WiF zqLo3``MKbD@&3`_nSxb?8$Q~y$^7!51u-K4uOxngRlwyeEB+0)L8k|aAM)BlI zBTr`7}La18&Aa{XFu?F3i1}E$(!g{SB7%JjnS`GT0KN#XBXV>leHCbO1E#vCH z*Q))oL3C2$^Hzneo363+Ht}2PJ!6k%it9kc<;c7w0P{j};Fy#$qFZzF^nF{D{ot~ki*PDE==LqT^niBk2D&9kfxqWSrq^RschTBloK zC};jJ?2+nBk;wFFUIMD%#TYX<1zKM7(x6idml(3MH}b^CHrmRcR2JF|{M5oLh6eTBkE~=7pkH?D4`BI5sFy*hQh#=OG`1 zEEaB^Xh}*hnAm}@?lfm+Xd3Hy_xso&Ndo5;-!}ffl`%Y z57DfWao&WBD)EFfBc`(RGI}lMJmA(fATBhqbM5H66j7Izf+r@WGO9%TugdtG!U|wyE#TcUO@K=59 z>{T<^q$6}zvd1&Yyz+#X!mjo+7JbGd85@l4{@6sHVrYU!Y&>=*{!k?q;#J79@hWai z=kLD<$R$);nM?cdc&YGH>%g~B1UU~L-d?3M<(+N7mKETzo$CFnh zUGBWn_NLGN^bmzhVv3REya_^;DyibVvH#1m_IxRr()Oi}{#cLLzW9{wO;08ThTPFf z2@hb1MJ#)bt@HB<#rx`Xq-RB~&{@qMznMZ;4H=sOe~u`_MCVY zvMgK2OOq;>wyecaShG3e^^J$Ursd~h*@?z-e*Tgwzgyba+E;X*ias~R(XI=%GHI8; zy0eJ>mVfsUeNg-%hVsja*CdOh7#SOF}XW6 zGQV``z$@zW^S6P5M!0+ERR1`p90w38`do2#2rXZ7S+RHRo_Hp{U)>|@ny*e}l}|bF zv#EkJr>&z{U!Fq`7mrNUke^ST?iij;rZ}?nffws9e04q;hI0h-!QY1%Z4mzjqWs%~ z(>A`I!`=L>{UB&X;uJ#=3zY0cTEkl=t(vzRDCwZ+GCu_KSRP;t$UmMw`Tc zXOZh)^4-56!M{&?szA3XK$$fk!|3KYHc36j`{Hzm)@Wdl#~!lxV$1stg1@zSg3*V> z9|*<(BTP?;c8Z~L5?h$uBjOL9tx3!t4ZuB%g@@MonLX}ZTsN&hL`!!Vl?IBKuvXrVDhIZ{wPG%j?E)8oCwdu6{^9gir8q4K!JN`dQ| z4`XQhI}GW(_;cyGknCiEeyF`oyIwLDWpwDWNXOFlZsG@|pXsV}*(PxDYYg!jF%QbW zYF_FI9XJ^8NE3KIE;-m4gndBNSNtIE_4^*LLf+z5sm#l=s>kGCwjX@eQokFLE%A2% zkq(>sVW^+@fyrEP&dvi*SW!_srShTEsB0cuY5Fh1&_JEKtkQdF1djumo974RhkXG= zj^6z<6zR6+4ubV2-3|lfo^ir7(u;1%lBM~_+4GgmgXM9$sO66m8pg3*vZw62e^#_C zZs;)FE87S?#mk*05kyaqH8#ye5B>`bsX;>9Aw6yKABJZh+4q5cXN^cBfx_Wxgy$Z; zSnm%&lnm10g?bqS8>#wqdFZQ=qLuC#l2-mC5UE8TWq4U|A7jYa`}rQ5c)@5l`O-*O z9^^w`+WB?CSzRkg&OP3!A3VeH;>O3st42Cw=x^w5U(#ibaB}WK@Vdd1fT3>MQH*xg z{@-MoKYIum>OwAh!LwVBReWdp!~YzHhJFOEmoa+VxIc!5j-`oz;^{a@=p?%Lu;x#O zP5)C*M}pT8Lp@FWca-;k<>`!}E==qH0}OSlaz4Cfz@Cm4=_I;0CH+4@dDmO$hsT!i z+wO0X_!yjM={Gsl3qyS=X>a*eeeimsJ_5&M99%ZS;`k0(@cWF|o`tARMj6 z+yR`eg(YROAm|~<4x&TQ^JD*UU-|Fp^WZ3tV0Sgj^(T)Qn*^LVWYv>h9rEAvzxUW- zU_PC&n=S_UQ83^rXZc{}njrdtE;m09FXxc|r{^W7=P~A(PkDYu;QqnUuK7v;fev2= z`n$1lERnb44*9@6N0KW4a^b9X=Bb5cB=DFnXZF)>k>| zUoK@)p--N_km3a2oU$y-8;3G`oc{Wgt?f1jDG-hLncUSF`~$a5xA*7wjqdrXB)i~L zGrvDM!t1x@9@v@h1v{Ci*cr;tZA{k75kwEPeCB!njC{m;L=ny8?_!A0&sP@2$277$ zj&E|%Gq~3C+W=2`qO2bI^S7vqh3O@5yj`PBa%;&WD0-&DIA>k0>*Q)7mypC#AHz_( zt{`3f#^E7wo6ADW?GiKPyPV<+Fn%I7BaU!>7j`7OU-M)|C!EtG7bS+%Zxs~Ucrz{U zsH6|B|HF^NuL=6W4o5M`i8T@EBR_c(L$dFT6PUMCU3qT|-z?zAPc;3la6wA4gh6wB zLpFHolqi>g7^=KnS8n&M5N6hK81cPtn(X)9_%pb#iE)BAd8L61~5oH zb%uv<-l&z2oNxbry zIEx>28$^1c1R(U8$K-Il*Dgldc}CIVhgW*8!&2Y#azPH?54ezF9{Lw2euz^m=Jf_U z7hbqQR<=tLX1XGV+M6P~5NV8&e!-fp!)=4$z4sbb^1}!k ziwqyQP=vwAtOMJBzQZ(%0WkRqD*EQYDL0t$>G~1z(TShH5c4(-Lop=l>HH)3FaI!V z2V>S7u4TT&qTUkUULV{AnS6Qncfi@w1?>Y_qLHo)ZhwPDl{}bM0K~wFx_Q3`joBfv zs%E^}ZT|9DhxNXA0Uri4DQ$J75FB+C?o(t@&8`$cl@tj0l}pl z#?Wn!_h5&~r|X1dJZyw*6h;^1?zAT+3*<6Q$CpKg*q9RtVVI%db*z~iwNgpOaDjO= zY(~K!0p|h?xqcX;Tvd=<8soSqk!uz{++e;@P4AcxW(h*^&ja#)imXvnQI9Cs-U)J# zc9>9pAPquq4*7)-M;8W1dEx{QJyO+ichQ(vz`kFcGNa8uKK}Slxg7(tfqOkW?zY_{ zXW(R>#M!goiCuONF?1+loI?heEEX`|rl$3Xx^a;MbDUjNq#;|57mohI2`&EapYE`h zf*hygetzQ47;?h=H4-2TpB_stA`8Bd3XVB>nu|F}-J6+;u>Vr&VSUx4TT8Ja_s zO(beo!JQbjdi7wb6c%xrjK$co$O$EFuf?Y3f5=EDS;f49eJ;N0P4f6yhoeQ^TMnFH zeaA%bQ72X(=223F7P!Pus|>}~(P>5=c_R!BtesK9Gjqf^u4L-fmT+(jD0@s042fJk zqsnE$Z!3V@?f=(4Xk`THG6>HbQy}N8qpP}GUrQJ$>qv%7G4HEY1Vgxt;6`kHw!<_n zGNKv4CETJ;958}(0-+f!*#EX9Lv8KcD+UV25OZvfpQs6Ol#a%hf>|4J@D4Y4V`T@} zkOa6vuoML|h#;}ULJ4%iYW%1kEPAh$gbWSj&KDQiSG|?Kmc!^vV(Y-=5S>3mWOX+u z03Yr!V@P-#;)6JG1kk6V<=S>nbO$>IcA z$fY3iq?#Eq|Edsr@)D!f!`fj!i9w+T^3<{f@_8m-u@=ETAuEFtX<7JZmiAeF7FiqF zkOh|KIxJ#CSl&R+`{gV+e9-BX2OQQR-@4bJREjPlQ9v^7pUuuvdj(gl!)d-vS7xq3 zon=Hi0zyf!T=xzZ+HKC>!PJDrpdkabU(&4e!CZC?hMc{Xml};wM@#$CSTu_ws5LX7 z==5lZO|Is$&p0n^N)oYb1kEq}v74)$r;SnsNlvP(Zk~0<6(uzJZCxWtZXnxQq>0jt zc z{3uf9lmj$+EC^X2$$hRa2q4Zvhp)5C$tmM<>y9q>G(Xw`U4(RD2 z8Bty+$0{FRuThr2m4O`EluuMxL|;qxpm^B}xtkV=NwV3RR4Et&2#C-M7utniOQfq*N+H)y``1lmnDP+8^N1P|w;G-w z)JtNJ?-97)Z;lg8^9};Jt4a(dh9+D0B$|nSAyyfI0a-lX_);XpUVDD;aP9y$lnI8Q za7zSBq;498!i_l@enN>My>CwzJaMg2Amn5S(iG`J8Q+RcA}kkOXTj*Y+$+~%iyWL< z6?!|B<)}5^a!7W$Ply(;&Bu-#WY( zTwrLBnOtdWP8brNC7Zk}nQn>>shH)3pg}8=;d$Bo%)@SyA^l~pDZ-n5m*tweO)maB z@7gEe001BWNkl*SIJ?aas=(?A}))Jm) zVO-FOmu)GRMh-=IQ@*f~OFELOaZM#d0BC=I8Y`&B@kJzfdgbS7gcRX61~ z2|_cr^8_!9jMiSQ9%0JTxUf|h-gdE43q0=bQ;uO%#els}2C)hD<4K2)9;;(bI}FXH z0&0t#nruWZ=6r@ExYlz0g^}84l&nN$z-Kmvz%){5MrMQLY#mLZii? z(X2&ztg?C=HE0^E`chr?R&%b_x$*v7->MFx!@V>%9<9VOo_6@?$$sRmwr8XIqGZ$3 zWpOR>A=|N^&dI^QURZ01)HsW0RbZ%O4mPA%iOq6(s!-(4{V|lr-{#gfITZCb%4n9b zbC8b@*e}kIa4M%u*n9ZhYvHX@!S|Q>;-U^>Q%-qojBa0a_~0>eVLEV{a@mi-2b+7D z2~bjp9SdXs0y;Tm6_fI1lsk|+-#Id*<`wOm!X(+NQ@Nr}79g|~>RAy0pWe!WR)}Lvl^xxd zdF@;?y~{lfY_#ZfOIc-TOBhNzxT$~an#36r2}4r)PDCWN@Wq1o`LP%F{8~quO(5ta%7fRh!tgW9mIyTsSa}A z-hv?+DCsegNeA~N4p5682_t*kto)=VUzTZNtwd8pCo{+M>1xrU=E#5@4yJCQRXUv6 z$z=>(%B_D^5mwe*C`+q#`d09a=n>0HT*IPvo$xTF5RoeeS_g6Z zFK45@+Y4f{4x~DfF!al?*f3O9zNh40ury zXeul3>(Z3gyk^%r0FQq)FKcBbZIOBg;oJds4L&YIw*c04Sc~6Y3n4`vL|U*x?0A2$ z=hv{DHJAM&Qq%AXCS`(UL>^x7|Jl}l^Cw4QL&))MNF_S=o80QxDox^OQ<`!` zX=-t^9#VEKR1(?RV=-suq};TOGQ>xFt~P3$ARzz<+fofF>L5=0E_IN(vcu(LNN0~q zu$<|ELKZG?+iOJ^y11wgSotLz3C-5Q?fz&lcoW}r`L~v@_Hy|L6>_UztSE1Kx&C=$ z5)ZF7>RTlKCJ2-rSfZjlTh*(1M|&cLj!M z^;Xg55@QWRb-|Ua)||(A+d@dLgEU|$6LeIwCp-1bVw;;&J+xNjw&#UfiluGw$D|*)gUI5JBNQj8&MI zg%FnuA$brxTYqc%NjBfT#t{6dD6Ukx(Y02Hbe|@)A_rk5V_VjlP_CV9yV;qrqTZAi zYSqg9AGMsePu1X*TQT;Nvn@Y$*Aw*^} z-JCJxYZN78FR$?}DFJ>IzvSYI%*R#e_p-UkX70hCFzy!ZR2|G))(@+B%$nAU#ec0~?O# zyzdXHGOH)CPy@PtwhV1q2h@-rzv+i;jVKf)`}d(0n3k2Y$R>rUX$XbdkI)mv=6AWj zVkk{(t?HZntZP#k+0JdnDG-%mHRwwoSH4y9jB%2@^If){dtd%QJA7CO32>83Cst;t z@i=M29LY>Q=TLlO4{nlK_FIpp6@OxSNK)XHl}|H_EljZ%Lv|)N1YgSiz0R2Hn>!4p zDh~8#OiZtytZ5j_XmMkJw=CvNUCgQ9>Im605EZ{;C`!~07ljZ^tEZ9yhI^JE(O-Ab z-%QoTo6OASNLH`TA}iD5(c^gBAf+-hwa2Bc6B`@0_Q}@Jm;~snVR)gB5}QBEy?j!A zvnVUb+=rP2$47pQ&6mZg%Vg$dM#6Ydd=nnjuik1ZJcELb&Au31k}HzrVIgEYh^;9Z z2xBXfBm@q;2gn?WQ+?DyCja3!7u9h=D#BrW?BsrM8gU3UY%P|yKf${0?--h##{C2# zFF5%scXAhUuU{zWX;~FnIRSOuJMtH%{FdVpC=Zdym+!wy6 zMHGTtUTs>;xq7)j4a~~=rgy`tuCO&p38JdvRN}vBXbpAgTfOsJnP*6H^V2tcQW{r( zD})qOUE}6OcNrN=R;CvIRa@>@y&3(vq(pzp)`{Nb_?LS@N- zfwdmQPSX|S!3>LXgkT7l+*x&HgQ5F5AjtG&o-y+RBHc?lZvju0w#IakPs-DEU{`Ye z<)UQTl20_sk^q#VMOxFLzPv5Jz_|>pu0OKYX6c_iyp8as#kOux4;w~ zVd4)*5)4uHqXeSc7?M$aKCDD$y|JNN{*0)m6n75wcEoxuC7dpHxgg7Y~6jU9_EL;zbc@g%i`%zg`NhCJF1hLQQdsG)iu+ z+TgoyWqzu{(0L(bP1Wn_vFrH|8C#emi642`!A6k3V95J*wzk8VWXt%0 zTm#W%O>TuwQrn%%trT1Rs6{9(u%Oer!bNck=PPPuFF`29S7f++r0wQUYg7NQDuhhy z0?T?3TU|@j&gknaA>udAGJ$$${&7q7UjA0qtjxvhYG^l}Ad{9FVYpyHsN{Na6aBmVbIjAp+Q)eQ*X{w;pNn3@-?f-PT#E=w1hO!Vc$sji9g?ifI zTzf}}=yHZye>+J~^L8f34wuO^H+1i05pU9qR4CGH(0OQErb}a!&HJRFN_jEneX$h} z;tU38sWrB@tj*^t&ekLvq`t6Jc>c12(1Es29L_*={$8?)peTf3C_V%o@a+b%+TGf{ z*jW1_){xk?Q-Y?=cHQHU)KCsHNC)}us@_D)bcrF0a3WV$R$WYOR*0>6%ED<;ddan^ zZC$}K&Uxp!*s^R6?O$o@+k)529K9`s*ytDtn&oV^HQS)?-QD%x1_Kh?ObJ=2j9_of z5lplVva)^J`^%H~V%qWNntz7+b z@=NZiY^Kp4u%Au-TJBe|^-n$%IsJr_Lpp$N+%mFS3~hc`sjchyYU7yVC{GMy^=y*u zD7FibbFwbjyWR0eJY1pq^PaqSq|Ylf;Zba8N^>EZR(CP8qPzQ!CgFDS!@T2zcOWE+S@)w} zD;EbdYMR{2{Z(w`Bl&-YN3Bk~@pyN%Y4NOA;Q&H|1=pI_I{N!nkJE&ckSn6yVF>lz z-3c$ap@3*h(r;H)_RduuhAu^imF&mlRyH2C5VG>rX)%_T3k)3ruiD{Q{&x%sTa=E< zDcbH^6|vgrTsBx25YxbWC+!x7cA8Po`rXtNRw2$x-#^Q(Y2_gmtC;}@q(5`}G-Q zaOdvz1JR*ec)$g%-!~4_J*b|oN)}q?zbH=~vgXqd>pIQQbm`ogFs+2-G#k=NrSm^x zzF8?>XTt_cmk@2w8*PyGy(#!x+vR9-fxa{mTHWGlgnOq%2Q*S~D2D{yR+Meh&f zeUd6miTdkONJA;jFto~S8)?rNM+Si55js@nN%yGSik7vmQ2Z!VO-wgkv#QIXo^iN1 zLSD}o)C^rw+hK4E(Rg2e| z^=K( zYpHqs5r%k7xFLugFr<5fA4&n?Xc*fAL$;*1pDk!Nqy3QF+N)@uX>B*clDU>=JAje> zQ!lmV^Rg$4vhO>X`(J~XWA3i60io@xL~CCN2jX!}iIS?!f^>~pJv z-Fp7pJ*$gNTb>uz&d(cL){)PTR3IvzqOOp8@TgzQ^0Nnap(KH!gb=6Q=klah=ltrQ zoye)Ip$CSZ7VK5&+$ zqGBGul9!3xc8$Q_J>u8T5;9TMGu7Q;J5a~1@DKKv%7P@We&yBE?Uzl}H5N9LrH=`w z6n>*&YlR>BxhK7F-LxYVAA0C$hL)~h1VAhX` z+l(?u^IIoY6dsGud%^!0hOBDwtjJF8Cc^oUvgZu3JG2vJqF#Zhm#Mq;xUzZo_+X(Y zhOqVYNf2!@q&LZQiWj|1{f}Zus}_&S>={`x>?;OSeIDx6LO)YKuw3hQ(-T+i9)7dF z*_n>_0X*MM7oq>0sk=VUC)3W6^&igu|2+rK<2IqCtGj`(bd>);`t+TbeLfI9Ds3*E-b`H3SH8dN5B1-nExQ5BiSQ?1rw2M+I?a;TpYRO| zRC5MQ)uSi3i#~9Q@>AV+(ns9z?FKm*Q-0w#N7wau`g1W}a6*K$_#}cqDfwY`!LuL0 zI;;TMe7H9M|BtfC$>DVAj9$g)BgW;ZiCY->iT#eCSrkQ3OL#sjubS`9#W*QrA@1W8 z<6(B;?BF`DH)<^o_g-Og_DAUuV@Jojae2?t#2!1Ue^!WN4C(iPKKi~av&JG`#o05P zb?0Kxy_{T3p3)JukBEMhU9cMT#tps&T@3MF<`4PbJy~`G3xvi`H1CIP>}6s1Pa2Wv zYY}K%mJbd?^7M`^ybVUbUaa`N2c5YX$>j-h0xK_&=7nDmq4QC8VW+YHK@mH!gC4dw z>pkZ8h3lm|j3BIq!=(c8s~-@FZYu<9>Fgh)+;jZFfzjhz+51qM~G5{Ny$zejW;r7SW@HD%Skv#6DU1qU^<8#nsaFS~x zm>Jy$k#>~;A)FQ-WvbK|>~1dv)x((>-5iAY<`KD*HunAXdL{gO+|$(qM7_Bf*~R7J z68M<}j^JuGTwHqK~XSSrQl?hpD% z_6q_cKB{5F5S5RRe&JrNknkVDK`o6Q_Oy}20fDh)U#3UveM0q`vk(k==myj={e zKVhf`yO28`<{88SR!~Z0*47GXJBDy+x8$*9bV;sXVz!Y=!VhIvzIUA~qU2?Ls$@%Z z0J)V=9AE~$z1z;g)0vB<2&5#-^9?KE7350Ch@sCo+&_V@1G|v(;S56j@FAd)QM`g) zJSk@r>_R_iw@Qj94}9>R7CU&5Cl?)iv39L!J$J=c7I^46aM1~37Z&?rqT+rSabsEv zrbHM>7#g+O=WL)5-wlUU+->OS>B_}0AR9(h>k=}HF%I)2=J0ng^enqbXQK#)#0(OZ zM@ebf*nAUdef<*FW$DV^1Vh6(TI6Jr^Nh{lKVWEbA*JC}=lNSA#s3Em@Wctw>()DR zF<=M;K%qH8XEMf#$o%}j_O33Rw0!H zh<z?|t{#blPdb5kPQ%teA0vuv!o{FzPqYx?n7JUtIN+y6j5_{iU3MWC z-0Dlltz5dcGrN%G5C}PZeLdpPchD|YnWZ$K12%D&w*-(~2}?ifWwKi%H1o#ReGZ){ zP`On%LqKM@N2eMWvnXc-hJZ|IgV0l$i?`AC+~KBG0qd{}eIUKLBY+57YI_OA_=Y^b zYq6KY=uVdIGX&f3K)W#Mn2}!Y5}?WMn9Y#fl%D$@4tkLdeX?)LQhkK6D^BeiCMWyE zD>mrFh3f`taj~Fl+1Vj%2yt>}+uFoh0Kxg>g3|J&b=ZYr2zR7ma~bxqIo37L0JKFn z8t|_C9A!@{?b^0AXQ^Dd0oCr(9T@Md{>=BV??v{<1^13dJa$}GyZi(QjWtg~bWtZ{ zAgaX0qOM7Hye=?r3{80s>Bii1S+*`FIqR^Cpaj)4zXjd?A&QU{XRn1`?wfR!yM-~# zqM+Sg*8>oN>vhcDL;kQ>S1^+fdSNm*k%7nd1#=fN_<2|apsw~}YnSMRB(+wJizT`q znTxYywI^8z!iuP%z z%(tt;E>sCo(~u6rKFpHS(rdTj%*QcvsHR;?v?~maFYr)o0+%Ds(mcYDUM-{J=Z`aw zo*!TamBSR-mnwhS*3n2kE|$oZH>Z5uW7FjF>m=P%eUtfi3A<3{E9Ae3T_hNan(lDO zWzn>#X6#8a8@bbX+-Vnkj(9~t25Nh?N#FS`-Hy=?kp-%l1cOch#t^>c?HAKj4?;&e zS#vS8p^%I1<(iWgiaR@_0di<<4D&u$ZeYIlJ=g_Dkr;3(yHF)0nr55H)5vtde!$^s zwvsszSbczv0j4PFJ%{|goKo;zl9FwL2vhS2RBw^2~M>OGwee=5#8fV z%bg_sK`-7Jk808|e0=~phc#jYc-6CsN70B9YYiIUM@zZbfvzb|j}RpcI1ibBz-G^U z?I%QLFBF<(?81})Z5r6wSor^uoJL?HG`h^26~jx?F*-f|G)BQQ@Hvd7QFa_ac)C?D zlis~;!3GK5^{0Lw# zmf0L>0T=re<#3n~RP9_rh`f`#V!odG%o;nf$}ibPGLiMKD-1;?U~MD&-H-A08NZ@L zyLv-f0O!5UicG6QFPdvw_u}9A{Ir++;T-8i1~Y?P*p{(EE_NhW6c-uZEJ;i%+YkAH z`3gY|#POP4tR`|u)@bxdI_iL-l8qx%;3W_cv^%JJp>;1DRyRZ^u%6!Ad?}VVv^OVCb!b~<-nkoVAK$XB+D7sdA!d2%I|4-AT zcqZOP0QJfSK9IrtC&(tDdMKoI+zUoO&-am<+b*LKqFY?-RIccn3N6rDF26IAn-J7F zlUWas1p10jOe$swWdSEh8Ga#l=qL za+o}HB~C;{0BQ}tYM2a;)59=K1Nd&%Zs)~=gHv`2j5dkqYF z3^%EN>V>yM@btgYOYUIoFXuYB-@!+@66NGdxnjQ4ZGuV^C-PbeSQf@!7e!Hy7RcgI z(6L0jlnP#CyNg=5yBK0)Mq@0#bJz>7DNUo3UTT7iWv@7R;E}?pKlS1f1PIMlSH@aJBYSYx zi*mXs?u_~764W@8lcaIK(!yfR(x&Qzm z07*naRAbIDa9X5jJ(IECveClgd{;xht}#ft>l}imGmVn?PnJu9p)ttq`>9O3;9H?! zpe#&{2fZw^nUH2);^1Dgcg$UnpePzCI`z`4DOY;;QP;&$&NcHz{E^8}cQcvSV0yhq zp-;8o)EDIHT59NyIH;tXcP2P=WNvv~jGc}AcfPA#9y!W@Stp~s`b{s`)a*hp`lJSx-Mb9gZZ4i@or|atQ458UC@HVhLj>*OQfikw8HS6F0)A`Q2zWfvop(MWhO3R#hr;Fa8bT^BH4Lj1AhF<-od)Lh6nlLY{& z7N}U)q;lhD(XP~zc(HpS-6-#(+|iYtQR7Z#{EHQaWaEqV(#Kv<&!V`OgMRaMG8aW7 z;W=mn&I7p?Yi7tTV7@|8HU7w)&aCQrHG+mP9#WCMVylweSOF7wQ@}vV&j+69Ho^hpwe2#gFsa zeJzQ0`$7x_p9&XjOG&iqP0l-S-@pP8_}77^SQ1wnXe!3I{wIs za>&d@LMg;vL@kK2OLeUjT865-rF2v3%r||oVx^f}2rktw+3tJkkHV3)&%KD+WmEEV z5N&m$oAW+IBl!_+pQc5Q<(BI@lld-@JmxdsWG1ie#a0XNWhv2hJijetH4DLVwFqbw z96?m5-K3LI$c`GWKpkD@GGFHknQuCiclILD0+DH9 z$kp{KA`9vzzN_KooQWkf5yN~(yGbV!2s%3%{jnG5esX0=goC!%iRPA1qmit6z0O#C zkXx$jEauw>hFTEiB;_8~_M%U;z+W1KvUQ!Rmx~uEhD5vXH>CQ-gVeM>4Yw$k;`rEKxZ_ud$~7sox^tumy@CwnLErxtC6s!KbI;eWH_XM0=%^OF|^o;>+YJLp>kxmig*^3Qw8ux|fx` zh*|*kf)YA#j|^SIM0BZ%=%GRX)!^m!L2h7=rrq2j^FQk4_DMCLM(a*`;mC)3c}S|G zPP*N^KGcco`dTG?4y8aYEbDlUp;pXy^HvgnfZZ^x1*ckwyY}*Q9ZZCIM;ZGh`PvtI z%5f`~Td{V14;HFeuNS@aX8sF$Ip{XgiAJ=Pqm%1t9qK1#$_)(lRB%(~3(Q1Lp~{Y0 zaN{~ppQ&pKCc=fxsN~mepYvXB@a-2vUUxHUeHeU{PR@E6NA2brh!UOfVO0tf`${L* z&lesgQ*Pw3XF`)VP@VY-Wk?OiTG=S&cuUu6BH{)RlH0l*7LZi#>i6#`Hy+vxow%%C z9%3k!h;7^-a5!gdUKTA)y2 zAao?7k_mLM?b~9PtT$n8#E#VTX=JtFyqBkZXd}=z44`P=u};(n73tbY>Y3a?E^aWXE@NU8P7rtZOl;UH5h6W+3(%%<(*!Q|*#S2@V? zHaL&qK1*e}QyGTb7Tu%WKq=YO`01-Ijp!Zf9E8J(1Nb2fn8#HLG~{wEj*sH(1NsVv?%j1?z?D)-R zo!l`P={IL`^9*Yv<|~?rc%d9cTQHJ6 zY5Odl!hGGlQ;4Qc_p{vR3H^TdGbb z_L)u!48~3_z$?s)N=4)Nk3YkH@SL3-7yZ-GpgmOD6?&n0# zeqxWGb@ISqsIot%E3yb)1Lg}+UhsP794q1yLmA`U*oa*97Bt$p)SU-t-jW;`D)Y*J zKivqJ9}%We^-uZITUC8$#Pto-J^C-c}P^{(2e~ zW=Dj=NbMq2g0fEpQx?B#!F)~f=J(ViJk`X;NI2Yx`B3Pli*3aMbW*jCivgq)JLu#j z1GbbnoiwOD6`ZkF_JR3ctpevoIpTCS-&9aHr-zb{lA&*mp(cG?3}8xTc1ARk8F;Sc zUMoAN0&p|tE5EfhS5ma+IfkNw#g+GM^@~2HuVTo*sKem%Y+mP#ZY!6SJwEl)&6ux@ zaB&CI`7s|#-WJMY)NqiW+c)~y^thYZ8F68TO7?QQtmN8dYs7rJwZV|)V3Hs6p;h7r z`fX_=z!ATQAxYV+Hn-Mcl6$$e1UF*7B4}fVMBmk^3r(%=>q+_;@7#TMzr*d!Mt=G6 z0CsZK5LF138!%rHw1E&-Yig?^-fYya(d9v_+8yp@_E=sgIZJL8aJFH-$6Zkag?|S_ z--Xa_X4i@gcNRkhKNNe!d@Hb)<0hp?tL%?RWU|D%B9Hs{Du{MTusZwOwiW~0t{5sq zY8~eLsXIUBd9J5%PN>(F__2@HI9;{%%&tczHx@%uBaa1thxsP``3Y6Fo99%Tq~6+M z=o@|P+G=KF`LYU?Tw9Do@vBAL|aceG#Z+b-57isypfy%y%Vz7YjZV*j~Ti#?a9S zTpNF?7j;z!QDx@4lV95qwb$>jV+b2a^OomJC0FIBZKb9i)Rp!H41J-G(&3u38c5K%+`lm41^7 z1kSkzM(+OxmH*8~j3FxusP(h@-tJ@@sjpy*2&mNiaq44eb5QFapuXFPhJYZa{}+XX zD4?@H`Mnl`63M?r=BR7ctsNlZ|Ng-VLj9uiSTt)D03H8f zfZFaSxY1mAzkJmWXDnz{o#IJj$jCaHtWy6@K;W=pka{@odV=L~dR%%xVjBK65OE#k zrAr~inq7XDMM{#IIeKV_RKZ;*B?f!hk zE*ZB|+I3HTCl&Snlrf^IA42_sVOSQthUL913?#m^UChygE?g*63cE%NUs^hf!5E+0 z42m4iobdOzI$C{T)UWHScxj52tNX|-9BwB1>Jhuo4Tb7@!4Q8Qq-HoUH2*MqxF*-4 z)aZ6EA!#?8gX802sVgqkJUu(y0P=W&`aqS*2&#Y!rk89`n(q)6fbv1P4D}! zf@8)p%zrlzZ$J93V_VO@@+}7)}O(ety|n3MW~%`nU>|gVO3NCVTf zgu;CQ&^+kWdA&QvkpG|aBPS@myAgIoFXmE&zW9g_0Q%tEiugmDr$6j{-)2gsT;^9^ zGHImQ3xHr|1<9R*zrHgD(hGv!&NG;C144ftL=i)dk-`81Zo1d|{d&D5OU;+M`IquD zu*oiu2=x8(wIdOs?)~=0i7yB28KHL%+Gr(k7n&~_5m~jFp<_-Ce z`RWA*(Uo3J5~TJKLN@O@@eF)Txo4>)k?pTTNZd?F19^~-BX-h_<4dm3LU|l-iiV%} zJogue%#XXU62Q!W`1)Ko!qj7)F+lnL(tGcD3KKvS2e-)9je)s5;UYz&&@fCax%uk( zN9GKuS@|GbwA@4T9HXS5*yPSe-+#Sv*$!;l_84+fPMuY2E*&pNQWDGD#{#AW9cV(a zXG4aoP6v6@Z?Vw)f`*^Fyjo?8;=)7~AL%<1*pE+B5*dS-K;*$DXV?tZyBI_~eDTA< zKLbDv9It@)yA#L2xjM$!EZzPILnM}#!GcTK#=&9mLCs>EFy`)#E3=Sd=GW{X+nf9! zCRw;R%Kzts#-1JIOW!gt-uUm?u(sdHBoUbt2t{B03T)5yU2zV~OXHjUyla-1 zZ{)%|2C!=`V8a;tR@89X^gP>u8>1{cl&YB)DU%G0(T(^X;pjYztry|?nl%d4yP3;h zhOQ0kbxkgHzYaCMV8G962 zuHEpGUoS?;Ai=Rj7*WkUzj|+y=Ajfq@&Z5&*$)nh@HgkDEo~szbBHd!tD?Jb6r|dmhJu2MFDJBNmni^?V51JKSWY6kr9+k06 zQZn}>ya`{4327AaDQf=?)-Xif;33I{f zfvsQ;nmK)Ui0XkMW}D6GFcdcdk^fWHKl$*V2asxcQA;5Uu7$hBXp*(oPOs2$OOS4x zsy%*KIEtGT-r#}UrzR{1sRyL~H*t|)V4TQ)y*Lv4iP~P#Js*xpy1HO23?hl(my`hL z`dKd;;Z>BsK&a6S91i{~1U0H9#KKX*f`ExHQ=%(nT~A&;|D^x#ATiY=2zT zlc|f;xZZr^S1_bt_H;5Oldl!s^Wn&kD=QNsmF^=$?ZU&!)`VFUQijUL4Yayr5Rt5m z;)7{BCW7IOE$1*X-lt{;HTq{_Hgq0R3U$d4Rs(F3L-W&bE0LEOlc>p&zZMk z$GiI`l+)>Pn-VM;OSiQ+fsn)3`4I~iWU!nt3`Vom0e5OV@er-+({@VC$YW?lCfyp# zvX?1yDE4czlOcUL{i*v=1hqI_RovnW<`gP?3JHe7CMw$%?1-{KKP6I~BxpMPYrVT> zUS{Q3y)J8Izu(TX>aqX{+8IflBFJ21dFK00BTX=>x|K}I2#xL`9LXF^ETxE3REzrm zrGsaXgqZyJFJOq$vxl-1MXRp484J$?wCsf0WDhtL_L9|e$qnf#4rb-9ALa!hT7EQn zORnJDk-cL^w-3e^rCI8LVdDrcvj57GHuf*d+(_u&3dEPN1z|i}YoQnNcNlMU z>zbTDE^AT=IeZv}>-eh0Sj z`pYejmOu2dD)JE2EZ$-4i{hJrA=3LGg;mRJl^eIQ-&XHThNUYR^<<;At;R;;<{`f2 z9_8qohh%CIS;21?=w1MGA8sbgPYz9pWp$CrImHm3PIMwf=nG6Qr^V57qZl`co&`hx z17oY`l+Bo8DBO0)v|*TSSAe_16-wv|vf|l`+>Z$87?cj~|d=7?Q(>VZ%J8CU5g&0P(37 zHc=&uS#i+e9pgw>bdds1IaelpsCi~6kO9(?`C!n-C-ri14OeoT2D9=Oovz&I6Y_s` z$M~f*M1( zd5vGR6o2S#(d5pK+AwH)$2hx}S|P0fQ7XH5Ec!=qt0S*-aB{jEqT3l@#>dRXWSb+R zmGlT!>Kj@-&Aq1TU`~p6peA94Uoaz_(Ky_KEX7K|(4ZjV#S$??W`%CH6^7Khak;-S zVhGmh3yHVjcFe6xtvD__k^p+{EjN~jp%~6;l?MA)bAV8TN4edM^@h>1K^D?G_~|4e zeQralU&{zhtWyAqaA=JQ3+E{jbU$(CV+6iM^?uACT1pkDLYb|+vFzL`VZNjg@)l9} zeXh$k%#1-RO38=$4?@Tyr!m10gJ@1k zgD*Dsl1;@@>0##EalH5^`%(jjC}JoH_rAGubl_G|$YKadc)+>jaRY|PE)u^zvA@zV zT#dfvd`)D%c@h*usQhJRHvpcbNDNTKOOeya?(7MM(v-mz*jEwoYqevy%6TCVqPr$z zl~xN{NyFK{)j~pyrxJ)YFqA!5<(jvi-N0CPmAT-~p?O_IHlXjvN@W18xLl*EQmE3NQsCSr)CwALr2@P=GahihVaO%h~;JE-q?6e_AaY49K8+!rVGGJL9CxbXv z5e#MSITK&07!sPErVMz@$U2D&nTrZ)+;2N-_QO|3U#6L%xQkp@v826pYX(YlNWAO6Q1D(07wp((#bn4jW(LuWoIwfjZ?$Aeu%cTf zHMD)0*WvDf`xCoi!w!!_2qB^2iLYC9cS^QUxne$ogJ>fP=&`f)YKM?B5a26@ z8iQzp$;ff5`6dIiWUG^>6T4wcFhnS@o`m1aBFsfG)Mtj@dPrFQIvHn&saxb=D<2RB zhF)rju&{QHp~?0EP?9vDea=7vzL5sQP{YLzSd0(U?6zBtD%oP_!NhLZK+!Nr`NHY` z8y|lAKVIw5DKn=<;UEYfXhkoe%(I*x{Ly9@0*LyHrEG}UU`P{NeVQ|PiL6C|pP4hb z)wUMnz+{M9UGhvum~TNPab7>2*iBj-usy%-woAhACGcJgzw>f9an4CpANZlikQQ1r z5WN!+G2qVR+ckW40Y_)sv4DFFnvZcA@MYAwgDz?*H#2Qtxz)=-&`u?Kt{|NwwO<^Blf~cqr3;j$pCZz(WLpy!q!&NoSmw(qhJ4p%h=zHRgNu_pW!1!I-9}Ab zYQ+cXZk02Lz@JNU1|^LSEXJ|P2yQha6|aT;lVu`Be>z8QPwW;gnbl(LZ8N*Y-Fb<90({EP8I-h`E8kg+ zE4O+oHZim^o9t*!oFoq>c8f+763U_wC4?dVmM!C6%S(uyP}{U=0o= zjm^*^rkq)}{;_Hr^UAfgflYs%pS+biXPbPMg76z=A zS^bTt{r*|mSn$;kYAPazN(*d`_8y#W3?)V;A(JADk+N4zHXi3-XxS$(W6nTI3vF@+ zt_g@J8ga207j8AvWNcK@XkyoFv{s4L1(*0cvR+Y$4fmX|B%)(BCqjs;*G~*0!B8JC zo&KTGf?2C@m|r!)9iL-F?Mb>n2d7}jq6#GOpBaR#_GaOMkzSNvy~|(4N_oJFf{0edY$}CYFnM!A*p%ZC4mw0PTQQp zdr3hwHNa4}wHUOu7^z#$OvQIwm24)-!-?Idd7UJ0SF$*WzcF43o%AhGnKs0@lUWuv zG#uqm^2je{MI$hjRLkNl(#5$w(%WbBY-^HtN^%Bq(8kqUU})=C^W5q+hHRGDXT?yB ziQT3Jo#=QqFntN}m9VCNp-RJ0iXjjNi!cBxslQ_>Ts(d94=h`KPKcDeF^DK8o?^~` zzf0mhbCFFj^x0x;-Kxb5mmmry3hgtl{HQarn=~ssVYAxw!SOeCFYu{#p}k~*Q|@p% zu1-!v3997ezD!Il1h)EpJxX-iK@c`MjIY;VI^+yi@!p_kc3WU*Z80cl`_Qf8>8|S( zP)N|ObW&$xH)`lzXSK~~9u)K5KJZ?bXe?7Qg#+UDSuB%OJHh|gPo9UNz=V)^vZMjV zY&nA%?+sy7Zhb-&ikj-<88th0tJ&1dexB;2+Qe?s>;(jj*iN~KA%T!4)Jn8rFXInNhQkZZ+HQ#Mc#c5+=#xiQTdVm5|2Y z96Px_<-J6DziT0eQq~NRFoq6ZWg&(vYasZJIfH=v!IpcQ)dw@bDwUFx3$l;hYN^ej zs1sLjVmEB260cgs-!J98ao!N7QNA{$v`2bMS!WH7?H!DP3NX}Z@{%Ot!*TvS?z=){ z7B(~DV=&?;Zq=As$sDBch_xnm!)Ezp#NddYtfaa9%2=cgvJJ3ZpzgHUX1)6-OObqY zlsq+K&R`esWkh=qTVgOy+$zjcfz6;X>ZIDlZrMTrpyziac$$a3GIF&M?1X%ndadwy zCV-YH149Z&iaS=wD1(2+%NqD!!_c)`Rj*p&%xs$AN{R{-yKOTJ$plC2G_PwT6(~bq z<~S{(bY0l+cV0@@8>0Sd+c8=8GGv}KuVn>bhqb3{>C2KXg0ermRrWhYHp4K~X&u<- z6T4|cow=YAExoeIc4aF=RyxKdG251LJikbazEJ8h;*e#ozdb>$1s6@`yZFmb6jFxmcH@+{~14371 zfb?dfE9reGZ=Lk#hQolbIfkNNJ$I{FiFj$LN}?fuII$ZxqG%&x6fO_He%i9pvMWnp z7iD3n%R9;!qsaIWJmrUN-ZG9mbJ&H=@j-#D!T97>gWtK5O3~Ez=@UMj*lk;w*U@wD z1BjxiiO-m=clw};=heT@Jvm|oBz`kZz1!I!`4NvhZ_uSJnSZIl_~2Hl$dd0=LPwie z{)8Elwp&ypq3~AS1Zxupz=w`mq)Vhwyj@1ajQi}LovjE z6)a+w?4Gb_VmEENI@)zz%8qv9E%Cdw39Xln9VrHpI#=yEN47gQWVMulI z5JT&*78t5whn!+)m3ykl*Gt>lUYxtt0>`tSCskC^v~4DK3!U)EhB}9OCGeS6Q=R03 z*xz}%(k}ncc)2C;}K8E2pzUdRqca{?(Mt~wMjlHVXq>*ux)i+$*r13 z5)A#`G|6hZx)na-3H~gGz^RkZEykPITG@-u@zGzMCYepn)uk9>_EI@$Bscf9(qXjp zYYa6QZ{9$*ezlgp*l6Szr%86BG-Qyqvaza8oLt{9&($#GR<5l2c=JcH@vGJB#STNi zHBItcs+D*eqU6G|Q|YplQV&Dx3t!`S>qswZ&FsZ05%8PSB)@Iybcs^toIPQgjd$@? z6Su!Km{4x_7f{s9UZfiN)oGI5wzb}{jjSGqVD>ACJ5W;n@#cQ^5j+)EDbrHE~VYpv^!w2@Fm0SI-M|{>c zQS@VBO&_0{qV!|02i&ZUy?~MX&ta&mx^<%MRvzi$Pi&`Y=vD8-vHfDrZ*9%p z>hC||zxIXpAN4u^+TUvbe9pi2y#4bz|JuLyueE9a1Ke#Ym&ypH3IG5A07*qoM6N<$ Ef`}TVegFUf literal 5130 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 cf6Xi@@54ZTQ_E-Enz5K6$1 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 OhB$CVO&D6 Date: Thu, 3 Apr 2025 01:42:36 +0200 Subject: [PATCH 07/33] [Bug][UI/UX] Make sure forms and evolutions are counted in challenges again (#5617) Making sure to actually count forms... --- src/data/challenge.ts | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 455421ffefd..868fc7d2e60 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -1272,23 +1272,22 @@ function checkSpeciesValidForChallenge(species: PokemonSpecies, props: DexAttrPr if (soft && isValidForChallenge.value) { return true; } - pokemonFormChanges[species.speciesId].forEach(f1 => { - // Exclude form changes that require the mon to be on the field to begin with, - // such as Castform - if (!("item" in f1)) { - return; + + const result = pokemonFormChanges[species.speciesId].some(f1 => { + // Exclude form changes that require the mon to be on the field to begin with + if (!("item" in f1.trigger)) { + return false; } - species.forms.forEach((f2, formIndex) => { + + return species.forms.some((f2, formIndex) => { if (f1.formKey === f2.formKey) { - const formProps = { ...props }; - formProps.formIndex = formIndex; + const formProps = { ...props, formIndex }; const isFormValidForChallenge = new Utils.BooleanHolder(true); applyChallenges(ChallengeType.STARTER_CHOICE, species, isFormValidForChallenge, formProps); - if (isFormValidForChallenge.value) { - return true; - } + return isFormValidForChallenge.value; } + return false; }); }); - return false; + return result; } From c6721521ab5f82a92a628b099a55abf3a65afa65 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 3 Apr 2025 01:59:31 +0200 Subject: [PATCH 08/33] [BUG] Fix broken forms of Pichu starter (#5616) * Unlock base Pichu form when catching a Pikachu form * Implementing migrator for broken Pichu forms --- package-lock.json | 4 +-- package.json | 2 +- src/system/game-data.ts | 8 ++++- .../version_migration/version_converter.ts | 9 ++++++ .../version_migration/versions/v1_8_3.ts | 30 +++++++++++++++++++ 5 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 src/system/version_migration/versions/v1_8_3.ts diff --git a/package-lock.json b/package-lock.json index 64b62996dc8..971715d241a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pokemon-rogue-battle", - "version": "1.8.2", + "version": "1.8.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pokemon-rogue-battle", - "version": "1.8.2", + "version": "1.8.3", "hasInstallScript": true, "dependencies": { "@material/material-color-utilities": "^0.2.7", diff --git a/package.json b/package.json index e395e8d2a54..199a77449a2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pokemon-rogue-battle", "private": true, - "version": "1.8.2", + "version": "1.8.3", "type": "module", "scripts": { "start": "vite", diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 2388918dca2..391ceec503d 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -1793,7 +1793,9 @@ export class GameData { const dexEntry = this.dexData[species.speciesId]; const caughtAttr = dexEntry.caughtAttr; const formIndex = pokemon.formIndex; - const dexAttr = pokemon.getDexAttr(); + + // This makes sure that we do not try to unlock data which cannot be unlocked + const dexAttr = pokemon.getDexAttr() & species.getFullUnlocksData(); // Mark as caught dexEntry.caughtAttr |= dexAttr; @@ -1803,6 +1805,10 @@ export class GameData { // always true except for the case of Urshifu. const formKey = pokemon.getFormKey(); if (formIndex > 0) { + // In case a Pikachu with formIndex > 0 was unlocked, base form Pichu is also unlocked + if (pokemon.species.speciesId === Species.PIKACHU && species.speciesId === Species.PICHU) { + dexEntry.caughtAttr |= globalScene.gameData.getFormAttr(0); + } if (pokemon.species.speciesId === Species.URSHIFU) { if (formIndex === 2) { dexEntry.caughtAttr |= globalScene.gameData.getFormAttr(0); diff --git a/src/system/version_migration/version_converter.ts b/src/system/version_migration/version_converter.ts index 3c5abc2cc18..074f60c2c5d 100644 --- a/src/system/version_migration/version_converter.ts +++ b/src/system/version_migration/version_converter.ts @@ -10,6 +10,9 @@ import * as v1_1_0 from "./versions/v1_1_0"; // --- v1.7.0 PATCHES --- // import * as v1_7_0 from "./versions/v1_7_0"; +// --- v1.8.3 PATCHES --- // +import * as v1_8_3 from "./versions/v1_8_3"; + const LATEST_VERSION = version.split(".").map(value => Number.parseInt(value)); /** @@ -174,6 +177,12 @@ class SystemVersionConverter extends VersionConverter { console.log("Applying v1.7.0 system data migration!"); this.callMigrators(data, v1_7_0.systemMigrators); } + if (curMinor === 8) { + if (curPatch <= 2) { + console.log("Applying v1.8.3 system data migration!"); + this.callMigrators(data, v1_8_3.systemMigrators); + } + } } console.log(`System data successfully migrated to v${version}!`); diff --git a/src/system/version_migration/versions/v1_8_3.ts b/src/system/version_migration/versions/v1_8_3.ts new file mode 100644 index 00000000000..d35530c28e9 --- /dev/null +++ b/src/system/version_migration/versions/v1_8_3.ts @@ -0,0 +1,30 @@ +import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { DexAttr, type SystemSaveData } from "#app/system/game-data"; +import { Species } from "#enums/species"; + +export const systemMigrators = [ + /** + * If a starter is caught, but the only forms registered as caught are not starterSelectable, + * unlock the default form. + * @param data {@linkcode SystemSaveData} + */ + function migratePichuForms(data: SystemSaveData) { + if (data.starterData && data.dexData) { + // This is Pichu's Pokédex number + const sd = 172; + const caughtAttr = data.dexData[sd]?.caughtAttr; + const species = getPokemonSpecies(sd); + // An extra check because you never know + if (species.speciesId === Species.PICHU && caughtAttr) { + // Ensuring that only existing forms are unlocked + data.dexData[sd].caughtAttr &= species.getFullUnlocksData(); + // If no forms are unlocked now, since Pichu is caught, we unlock form 0 + data.dexData[sd].caughtAttr |= DexAttr.DEFAULT_FORM; + } + } + }, +] as const; + +export const settingsMigrators = [] as const; + +export const sessionMigrators = [] as const; From 4c8f81bb09ba17bc3a1c962d72096df5ef8c6d51 Mon Sep 17 00:00:00 2001 From: Dean <69436131+emdeann@users.noreply.github.com> Date: Wed, 2 Apr 2025 17:28:58 -0700 Subject: [PATCH 09/33] [Bug] Fix uses of getAlly() (#5618) * Fix plus/minus crash * Update getAlly() uses --- src/data/ability.ts | 42 ++++++++++---------- src/data/moves/move.ts | 57 +++++++++++++++------------ src/field/pokemon.ts | 11 +++--- src/phases/enemy-command-phase.ts | 2 +- src/phases/faint-phase.ts | 4 +- src/phases/revival-blessing-phase.ts | 8 +++- src/phases/stat-stage-change-phase.ts | 4 +- 7 files changed, 70 insertions(+), 58 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index eaf77f376f4..eea24c791b0 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -854,7 +854,8 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr { } if (this.allOthers) { - const otherPokemon = pokemon.getAlly() ? pokemon.getOpponents().concat([ pokemon.getAlly() ]) : pokemon.getOpponents(); + const ally = pokemon.getAlly(); + const otherPokemon = !Utils.isNullOrUndefined(ally) ? pokemon.getOpponents().concat([ ally ]) : pokemon.getOpponents(); for (const other of otherPokemon) { globalScene.unshiftPhase(new StatStageChangePhase((other).getBattlerIndex(), false, [ this.stat ], this.stages)); } @@ -2460,12 +2461,12 @@ export class PostSummonAllyHealAbAttr extends PostSummonAbAttr { } override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return pokemon.getAlly()?.isActive(true); + return pokemon.getAlly()?.isActive(true) ?? false; } override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { const target = pokemon.getAlly(); - if (!simulated) { + if (!simulated && !Utils.isNullOrUndefined(target)) { globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), Utils.toDmgValue(pokemon.getMaxHp() / this.healRatio), i18next.t("abilityTriggers:postSummonAllyHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(target), pokemonName: pokemon.name }), true, !this.showAnim)); } @@ -2486,12 +2487,12 @@ export class PostSummonClearAllyStatStagesAbAttr extends PostSummonAbAttr { } override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return pokemon.getAlly()?.isActive(true); + return pokemon.getAlly()?.isActive(true) ?? false; } override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { const target = pokemon.getAlly(); - if (!simulated) { + if (!simulated && !Utils.isNullOrUndefined(target)) { for (const s of BATTLE_STATS) { target.setStatStage(s, 0); } @@ -2712,7 +2713,7 @@ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { } const ally = pokemon.getAlly(); - if (!ally || ally.getStatStages().every(s => s === 0)) { + if (Utils.isNullOrUndefined(ally) || ally.getStatStages().every(s => s === 0)) { return false; } @@ -2721,7 +2722,7 @@ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { const ally = pokemon.getAlly(); - if (!simulated) { + if (!simulated && !Utils.isNullOrUndefined(ally)) { for (const s of BATTLE_STATS) { pokemon.setStatStage(s, ally.getStatStage(s)); } @@ -2866,8 +2867,9 @@ export class CommanderAbAttr extends AbAttr { // another Pokemon, this effect cannot apply. // TODO: Should this work with X + Dondozo fusions? - return globalScene.currentBattle?.double && pokemon.getAlly()?.species.speciesId === Species.DONDOZO - && !(pokemon.getAlly().isFainted() || pokemon.getAlly().getTag(BattlerTagType.COMMANDED)); + const ally = pokemon.getAlly(); + return globalScene.currentBattle?.double && !Utils.isNullOrUndefined(ally) && ally.species.speciesId === Species.DONDOZO + && !(ally.isFainted() || ally.getTag(BattlerTagType.COMMANDED)); } override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: null, args: any[]): void { @@ -2877,7 +2879,7 @@ export class CommanderAbAttr extends AbAttr { // Play an animation of the source jumping into the ally Dondozo's mouth globalScene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.COMMANDER_APPLY); // Apply boosts from this effect to the ally Dondozo - pokemon.getAlly().addTag(BattlerTagType.COMMANDED, 0, Moves.NONE, pokemon.id); + pokemon.getAlly()?.addTag(BattlerTagType.COMMANDED, 0, Moves.NONE, pokemon.id); // Cancel the source Pokemon's next move (if a move is queued) globalScene.tryRemovePhase((phase) => phase instanceof MovePhase && phase.pokemon === pokemon); } @@ -4077,7 +4079,7 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr { */ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr { private allyTarget: boolean; - private target: Pokemon; + private target: Pokemon | undefined; constructor(allyTarget = false) { super(true); @@ -4090,11 +4092,11 @@ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr { } else { this.target = pokemon; } - return !Utils.isNullOrUndefined(this.target.status); + return !Utils.isNullOrUndefined(this.target?.status); } override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { - if (!simulated && this.target.status) { + if (!simulated && this.target?.status) { globalScene.queueMessage(getStatusEffectHealText(this.target.status?.effect, getPokemonNameWithAffix(this.target))); this.target.resetStatus(false); this.target.updateInfo(); @@ -5440,6 +5442,8 @@ class ForceSwitchOutHelper { * It will not flee if it is a Mystery Encounter with fleeing disabled (checked in `getSwitchOutCondition()`) or if it is a wave 10x wild boss */ } else { + const allyPokemon = switchOutTarget.getAlly(); + if (!globalScene.currentBattle.waveIndex || globalScene.currentBattle.waveIndex % 10 === 0) { return false; } @@ -5447,14 +5451,12 @@ class ForceSwitchOutHelper { if (switchOutTarget.hp > 0) { switchOutTarget.leaveField(false); globalScene.queueMessage(i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }), null, true, 500); - - if (globalScene.currentBattle.double) { - const allyPokemon = switchOutTarget.getAlly(); + if (globalScene.currentBattle.double && !Utils.isNullOrUndefined(allyPokemon)) { globalScene.redirectPokemonMoves(switchOutTarget, allyPokemon); } } - if (!switchOutTarget.getAlly()?.isActive(true)) { + if (!allyPokemon?.isActive(true)) { globalScene.clearEnemyHeldItemModifiers(); if (switchOutTarget.hp) { @@ -6440,9 +6442,9 @@ export function initAbilities() { new Ability(Abilities.CUTE_CHARM, 3) .attr(PostDefendContactApplyTagChanceAbAttr, 30, BattlerTagType.INFATUATED), new Ability(Abilities.PLUS, 3) - .conditionalAttr(p => globalScene.currentBattle.double && [ Abilities.PLUS, Abilities.MINUS ].some(a => p.getAlly().hasAbility(a)), StatMultiplierAbAttr, Stat.SPATK, 1.5), + .conditionalAttr(p => globalScene.currentBattle.double && [ Abilities.PLUS, Abilities.MINUS ].some(a => (p.getAlly()?.hasAbility(a) ?? false)), StatMultiplierAbAttr, Stat.SPATK, 1.5), new Ability(Abilities.MINUS, 3) - .conditionalAttr(p => globalScene.currentBattle.double && [ Abilities.PLUS, Abilities.MINUS ].some(a => p.getAlly().hasAbility(a)), StatMultiplierAbAttr, Stat.SPATK, 1.5), + .conditionalAttr(p => globalScene.currentBattle.double && [ Abilities.PLUS, Abilities.MINUS ].some(a => (p.getAlly()?.hasAbility(a) ?? false)), StatMultiplierAbAttr, Stat.SPATK, 1.5), new Ability(Abilities.FORECAST, 3) .uncopiable() .unreplaceable() @@ -6669,7 +6671,7 @@ export function initAbilities() { .attr(PostDefendMoveDisableAbAttr, 30) .bypassFaint(), new Ability(Abilities.HEALER, 5) - .conditionalAttr(pokemon => pokemon.getAlly() && Utils.randSeedInt(10) < 3, PostTurnResetStatusAbAttr, true), + .conditionalAttr(pokemon => !Utils.isNullOrUndefined(pokemon.getAlly()) && Utils.randSeedInt(10) < 3, PostTurnResetStatusAbAttr, true), new Ability(Abilities.FRIEND_GUARD, 5) .attr(AlliedFieldDamageReductionAbAttr, 0.75) .ignorable(), diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 80e8f2dae55..2624fe6cda9 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -788,9 +788,9 @@ export default class Move implements Localizable { } applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, target, this, simulated, power); - - if (source.getAlly()) { - applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, source.getAlly(), target, this, simulated, power); + const ally = source.getAlly(); + if (!Utils.isNullOrUndefined(ally)) { + applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, ally, target, this, simulated, power); } const fieldAuras = new Set( @@ -911,7 +911,8 @@ export default class Move implements Localizable { ]; // ...and cannot enhance Pollen Puff when targeting an ally. - const exceptPollenPuffAlly: boolean = this.id === Moves.POLLEN_PUFF && targets.includes(user.getAlly()?.getBattlerIndex()) + const ally = user.getAlly(); + const exceptPollenPuffAlly: boolean = this.id === Moves.POLLEN_PUFF && !Utils.isNullOrUndefined(ally) && targets.includes(ally.getBattlerIndex()) return (!restrictSpread || !isMultiTarget) && !this.isChargingMove() @@ -1946,7 +1947,7 @@ export class FlameBurstAttr extends MoveEffectAttr { const targetAlly = target.getAlly(); const cancelled = new Utils.BooleanHolder(false); - if (targetAlly) { + if (!Utils.isNullOrUndefined(targetAlly)) { applyAbAttrs(BlockNonDirectDamageAbAttr, targetAlly, cancelled); } @@ -1959,7 +1960,7 @@ export class FlameBurstAttr extends MoveEffectAttr { } getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { - return target.getAlly() ? -5 : 0; + return !Utils.isNullOrUndefined(target.getAlly()) ? -5 : 0; } } @@ -4357,10 +4358,10 @@ export class LastMoveDoublePowerAttr extends VariablePowerAttr { const userAlly = user.getAlly(); const enemyAlly = enemy?.getAlly(); - if (userAlly && userAlly.turnData.acted) { + if (!Utils.isNullOrUndefined(userAlly) && userAlly.turnData.acted) { pokemonActed.push(userAlly); } - if (enemyAlly && enemyAlly.turnData.acted) { + if (!Utils.isNullOrUndefined(enemyAlly) && enemyAlly.turnData.acted) { pokemonActed.push(enemyAlly); } } @@ -6162,9 +6163,8 @@ export class RevivalBlessingAttr extends MoveEffectAttr { pokemon.resetStatus(); pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp())); globalScene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: getPokemonNameWithAffix(pokemon) }), 0, true); - - if (globalScene.currentBattle.double && globalScene.getEnemyParty().length > 1) { - const allyPokemon = user.getAlly(); + const allyPokemon = user.getAlly(); + if (globalScene.currentBattle.double && globalScene.getEnemyParty().length > 1 && !Utils.isNullOrUndefined(allyPokemon)) { // Handle cases where revived pokemon needs to get switched in on same turn if (allyPokemon.isFainted() || allyPokemon === pokemon) { // Enemy switch phase should be removed and replaced with the revived pkmn switching in @@ -6343,18 +6343,19 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { return false; } + const allyPokemon = switchOutTarget.getAlly(); + if (switchOutTarget.hp > 0) { switchOutTarget.leaveField(false); globalScene.queueMessage(i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }), null, true, 500); // in double battles redirect potential moves off fled pokemon - if (globalScene.currentBattle.double) { - const allyPokemon = switchOutTarget.getAlly(); + if (globalScene.currentBattle.double && !Utils.isNullOrUndefined(allyPokemon)) { globalScene.redirectPokemonMoves(switchOutTarget, allyPokemon); } } - if (!switchOutTarget.getAlly()?.isActive(true)) { + if (!allyPokemon?.isActive(true)) { globalScene.clearEnemyHeldItemModifiers(); if (switchOutTarget.hp) { @@ -7030,7 +7031,7 @@ export class RepeatMoveAttr extends MoveEffectAttr { const firstTarget = globalScene.getField()[moveTargets[0]]; if (globalScene.currentBattle.double && moveTargets.length === 1 && firstTarget.isFainted() && firstTarget !== target.getAlly()) { const ally = firstTarget.getAlly(); - if (ally.isActive()) { // ally exists, is not dead and can sponge the blast + if (!Utils.isNullOrUndefined(ally) && ally.isActive()) { // ally exists, is not dead and can sponge the blast moveTargets = [ ally.getBattlerIndex() ]; } } @@ -7404,10 +7405,11 @@ export class AbilityCopyAttr extends MoveEffectAttr { globalScene.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name })); user.setTempAbility(target.getAbility()); + const ally = user.getAlly(); - if (this.copyToPartner && globalScene.currentBattle?.double && user.getAlly().hp) { - globalScene.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", { pokemonName: getPokemonNameWithAffix(user.getAlly()), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name })); - user.getAlly().setTempAbility(target.getAbility()); + if (this.copyToPartner && globalScene.currentBattle?.double && !Utils.isNullOrUndefined(ally) && ally.hp) { // TODO is this the best way to check that the ally is active? + globalScene.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", { pokemonName: getPokemonNameWithAffix(ally), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name })); + ally.setTempAbility(target.getAbility()); } return true; @@ -7415,9 +7417,10 @@ export class AbilityCopyAttr extends MoveEffectAttr { getCondition(): MoveConditionFunc { return (user, target, move) => { + const ally = user.getAlly(); let ret = target.getAbility().isCopiable && user.getAbility().isReplaceable; if (this.copyToPartner && globalScene.currentBattle?.double) { - ret = ret && (!user.getAlly().hp || user.getAlly().getAbility().isReplaceable); + ret = ret && (!ally?.hp || ally?.getAbility().isReplaceable); } else { ret = ret && user.getAbility().id !== target.getAbility().id; } @@ -8188,6 +8191,7 @@ export function getMoveTargets(user: Pokemon, move: Moves, replaceTarget?: MoveT let set: Pokemon[] = []; let multiple = false; + const ally: Pokemon | undefined = user.getAlly(); switch (moveTarget) { case MoveTarget.USER: @@ -8198,7 +8202,7 @@ export function getMoveTargets(user: Pokemon, move: Moves, replaceTarget?: MoveT case MoveTarget.OTHER: case MoveTarget.ALL_NEAR_OTHERS: case MoveTarget.ALL_OTHERS: - set = (opponents.concat([ user.getAlly() ])); + set = !Utils.isNullOrUndefined(ally) ? (opponents.concat([ ally ])) : opponents; multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS; break; case MoveTarget.NEAR_ENEMY: @@ -8215,21 +8219,22 @@ export function getMoveTargets(user: Pokemon, move: Moves, replaceTarget?: MoveT return { targets: [ -1 as BattlerIndex ], multiple: false }; case MoveTarget.NEAR_ALLY: case MoveTarget.ALLY: - set = [ user.getAlly() ]; + set = !Utils.isNullOrUndefined(ally) ? [ ally ] : []; break; case MoveTarget.USER_OR_NEAR_ALLY: case MoveTarget.USER_AND_ALLIES: case MoveTarget.USER_SIDE: - set = [ user, user.getAlly() ]; + set = !Utils.isNullOrUndefined(ally) ? [ user, ally ] : [ user ]; multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY; break; case MoveTarget.ALL: case MoveTarget.BOTH_SIDES: - set = [ user, user.getAlly() ].concat(opponents); + set = (!Utils.isNullOrUndefined(ally) ? [ user, ally ] : [ user ]).concat(opponents); multiple = true; break; case MoveTarget.CURSE: - set = user.getTypes(true).includes(PokemonType.GHOST) ? (opponents.concat([ user.getAlly() ])) : [ user ]; + const extraTargets = !Utils.isNullOrUndefined(ally) ? [ ally ] : []; + set = user.getTypes(true).includes(PokemonType.GHOST) ? (opponents.concat(extraTargets)) : [ user ]; break; } @@ -10123,7 +10128,7 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], 1, false, { condition: (user, target, move) => !![ Abilities.PLUS, Abilities.MINUS ].find(a => target.hasAbility(a, false)) }) .ignoresSubstitute() .target(MoveTarget.USER_AND_ALLIES) - .condition((user, target, move) => !![ user, user.getAlly() ].filter(p => p?.isActive()).find(p => !![ Abilities.PLUS, Abilities.MINUS ].find(a => p.hasAbility(a, false)))), + .condition((user, target, move) => !![ user, user.getAlly() ].filter(p => p?.isActive()).find(p => !![ Abilities.PLUS, Abilities.MINUS ].find(a => p?.hasAbility(a, false)))), new StatusMove(Moves.HAPPY_HOUR, PokemonType.NORMAL, -1, 30, -1, 0, 6) // No animation .attr(AddArenaTagAttr, ArenaTagType.HAPPY_HOUR, null, true) .target(MoveTarget.USER_SIDE), @@ -10313,7 +10318,7 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, false, { condition: (user, target, move) => !![ Abilities.PLUS, Abilities.MINUS ].find(a => target.hasAbility(a, false)) }) .ignoresSubstitute() .target(MoveTarget.USER_AND_ALLIES) - .condition((user, target, move) => !![ user, user.getAlly() ].filter(p => p?.isActive()).find(p => !![ Abilities.PLUS, Abilities.MINUS ].find(a => p.hasAbility(a, false)))), + .condition((user, target, move) => !![ user, user.getAlly() ].filter(p => p?.isActive()).find(p => !![ Abilities.PLUS, Abilities.MINUS ].find(a => p?.hasAbility(a, false)))), new AttackMove(Moves.THROAT_CHOP, PokemonType.DARK, MoveCategory.PHYSICAL, 80, 100, 15, 100, 0, 7) .attr(AddBattlerTagAttr, BattlerTagType.THROAT_CHOPPED), new AttackMove(Moves.POLLEN_PUFF, PokemonType.BUG, MoveCategory.SPECIAL, 90, 100, 15, -1, 0, 7) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 20a8855fa55..f89319a6e30 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1447,7 +1447,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } const ally = this.getAlly(); - if (ally) { + if (!Utils.isNullOrUndefined(ally)) { applyAllyStatMultiplierAbAttrs(AllyStatMultiplierAbAttr, ally, stat, statValue, simulated, this, move?.hasFlag(MoveFlags.IGNORE_ABILITIES) || ignoreAllyAbility); } @@ -3714,7 +3714,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { : i18next.t("arenaTag:yourTeam"); } - getAlly(): Pokemon { + getAlly(): Pokemon | undefined { return ( this.isPlayer() ? globalScene.getPlayerField() @@ -3900,7 +3900,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ); const ally = this.getAlly(); - if (ally) { + if (!isNullOrUndefined(ally)) { const ignore = this.hasAbilityWithAttr(MoveAbilityBypassAbAttr) || sourceMove.hasFlag(MoveFlags.IGNORE_ABILITIES); applyAllyStatMultiplierAbAttrs(AllyStatMultiplierAbAttr, ally, Stat.ACC, accuracyMultiplier, false, this, ignore); applyAllyStatMultiplierAbAttrs(AllyStatMultiplierAbAttr, ally, Stat.EVA, evasionMultiplier, false, this, ignore); @@ -4336,11 +4336,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { damage, ); + const ally = this.getAlly(); /** Additionally apply friend guard damage reduction if ally has it. */ - if (globalScene.currentBattle.double && this.getAlly()?.isActive(true)) { + if (globalScene.currentBattle.double && !isNullOrUndefined(ally) && ally.isActive(true)) { applyPreDefendAbAttrs( AlliedFieldDamageReductionAbAttr, - this.getAlly(), + ally, source, move, cancelled, diff --git a/src/phases/enemy-command-phase.ts b/src/phases/enemy-command-phase.ts index 2e4861aacfc..166b8c1ae2d 100644 --- a/src/phases/enemy-command-phase.ts +++ b/src/phases/enemy-command-phase.ts @@ -39,7 +39,7 @@ export class EnemyCommandPhase extends FieldPhase { if ( battle.double && enemyPokemon.hasAbility(Abilities.COMMANDER) && - enemyPokemon.getAlly().getTag(BattlerTagType.COMMANDED) + enemyPokemon.getAlly()?.getTag(BattlerTagType.COMMANDED) ) { this.skipTurn = true; } diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index dfc0e0653a5..7e1ae4ec07b 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -209,8 +209,8 @@ export class FaintPhase extends PokemonPhase { } // in double battles redirect potential moves off fainted pokemon - if (globalScene.currentBattle.double) { - const allyPokemon = pokemon.getAlly(); + const allyPokemon = pokemon.getAlly(); + if (globalScene.currentBattle.double && !isNullOrUndefined(allyPokemon)) { globalScene.redirectPokemonMoves(pokemon, allyPokemon); } diff --git a/src/phases/revival-blessing-phase.ts b/src/phases/revival-blessing-phase.ts index 1ed63f76b64..e650d714abc 100644 --- a/src/phases/revival-blessing-phase.ts +++ b/src/phases/revival-blessing-phase.ts @@ -42,8 +42,12 @@ export class RevivalBlessingPhase extends BattlePhase { true, ); - if (globalScene.currentBattle.double && globalScene.getPlayerParty().length > 1) { - const allyPokemon = this.user.getAlly(); + const allyPokemon = this.user.getAlly(); + if ( + globalScene.currentBattle.double && + globalScene.getPlayerParty().length > 1 && + !Utils.isNullOrUndefined(allyPokemon) + ) { if (slotIndex <= 1) { // Revived ally pokemon globalScene.unshiftPhase( diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index f58744ef5ce..4c82661a3bb 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -17,7 +17,7 @@ import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { ResetNegativeStatStageModifier } from "#app/modifier/modifier"; import { handleTutorial, Tutorial } from "#app/tutorial"; -import { NumberHolder, BooleanHolder } from "#app/utils"; +import { NumberHolder, BooleanHolder, isNullOrUndefined } from "#app/utils"; import i18next from "i18next"; import { PokemonPhase } from "./pokemon-phase"; import { Stat, type BattleStat, getStatKey, getStatStageChangeDescriptionKey } from "#enums/stat"; @@ -161,7 +161,7 @@ export class StatStageChangePhase extends PokemonPhase { pokemon, ); const ally = pokemon.getAlly(); - if (ally) { + if (!isNullOrUndefined(ally)) { applyPreStatStageChangeAbAttrs( ConditionalUserFieldProtectStatAbAttr, ally, From 7b38596a12df6678dcd8fc726510b72e02d0807c Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Wed, 2 Apr 2025 19:37:48 -0700 Subject: [PATCH 10/33] Sync locales and update version to 1.8.4 (#5620) --- package-lock.json | 4 ++-- package.json | 2 +- public/locales | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 971715d241a..33e9dd08104 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pokemon-rogue-battle", - "version": "1.8.3", + "version": "1.8.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pokemon-rogue-battle", - "version": "1.8.3", + "version": "1.8.4", "hasInstallScript": true, "dependencies": { "@material/material-color-utilities": "^0.2.7", diff --git a/package.json b/package.json index 199a77449a2..6b1c73db158 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pokemon-rogue-battle", "private": true, - "version": "1.8.3", + "version": "1.8.4", "type": "module", "scripts": { "start": "vite", diff --git a/public/locales b/public/locales index 213701f8047..e98f0eb9c20 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 213701f80471d38142827dec2d798f047db471d1 +Subproject commit e98f0eb9c2022bc78b53f0444424c636498e725a From b364bb18991475d8622af07955efe0580edf0393 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Fri, 4 Apr 2025 16:39:53 -0500 Subject: [PATCH 11/33] [Bug][Ability] Fix wimp out and emergency exit skipping waves in double battles (#5261) Fix wimp out causing battles to skip --- src/phases/battle-end-phase.ts | 17 +++++++++++++++++ src/phases/new-battle-phase.ts | 5 +++++ test/abilities/wimp_out.test.ts | 26 ++++++++++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index a7158264ab7..e6a0c66548e 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -17,6 +17,23 @@ export class BattleEndPhase extends BattlePhase { start() { super.start(); + // cull any extra `BattleEnd` phases from the queue. + globalScene.phaseQueue = globalScene.phaseQueue.filter(phase => { + if (phase instanceof BattleEndPhase) { + this.isVictory ||= phase.isVictory; + return false; + } + return true; + }); + // `phaseQueuePrepend` is private, so we have to use this inefficient loop. + while (globalScene.tryRemoveUnshiftedPhase(phase => { + if (phase instanceof BattleEndPhase) { + this.isVictory ||= phase.isVictory; + return true; + } + return false; + })) {} + globalScene.gameData.gameStats.battles++; if ( globalScene.gameMode.isEndless && diff --git a/src/phases/new-battle-phase.ts b/src/phases/new-battle-phase.ts index 8cdbdc5891a..09b8ab1d335 100644 --- a/src/phases/new-battle-phase.ts +++ b/src/phases/new-battle-phase.ts @@ -5,6 +5,11 @@ export class NewBattlePhase extends BattlePhase { start() { super.start(); + // cull any extra `NewBattle` phases from the queue. + globalScene.phaseQueue = globalScene.phaseQueue.filter(phase => !(phase instanceof NewBattlePhase)); + // `phaseQueuePrepend` is private, so we have to use this inefficient loop. + while (globalScene.tryRemoveUnshiftedPhase(phase => phase instanceof NewBattlePhase)) {} + globalScene.newBattle(); this.end(); diff --git a/test/abilities/wimp_out.test.ts b/test/abilities/wimp_out.test.ts index ef201cbf8dd..294025a10e7 100644 --- a/test/abilities/wimp_out.test.ts +++ b/test/abilities/wimp_out.test.ts @@ -498,6 +498,7 @@ describe("Abilities - Wimp Out", () => { const hasFled = enemyPokemon.switchOutStatus; expect(isVisible && !hasFled).toBe(true); }); + it("wimp out will not skip battles when triggered in a double battle", async () => { const wave = 2; game.override @@ -525,4 +526,29 @@ describe("Abilities - Wimp Out", () => { await game.toNextWave(); expect(game.scene.currentBattle.waveIndex).toBe(wave + 1); }); + + it("wimp out should not skip battles when triggering the same turn as another enemy faints", async () => { + const wave = 2; + game.override + .enemySpecies(Species.WIMPOD) + .enemyAbility(Abilities.WIMP_OUT) + .startingLevel(50) + .enemyLevel(1) + .enemyMoveset([ Moves.SPLASH, Moves.ENDURE ]) + .battleType("double") + .moveset([ Moves.DRAGON_ENERGY, Moves.SPLASH ]) + .startingWave(wave); + + await game.classicMode.startBattle([ Species.REGIDRAGO, Species.MAGIKARP ]); + + // turn 1 + game.move.select(Moves.DRAGON_ENERGY, 0); + game.move.select(Moves.SPLASH, 1); + await game.forceEnemyMove(Moves.SPLASH); + await game.forceEnemyMove(Moves.ENDURE); + + await game.phaseInterceptor.to("SelectModifierPhase"); + expect(game.scene.currentBattle.waveIndex).toBe(wave + 1); + + }); }); From e31bf912231709c42b3044d7ff5f3a244f60f209 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Fri, 4 Apr 2025 16:57:39 -0500 Subject: [PATCH 12/33] [Bug][Ability] Fix mold breaker effect lingering if the user's move runs out of PP (#5265) * Fix mold breaker pp bug * Update Dancer test to account for changed phase behavior * Update doc comment for move-phase's `end` method * Add null handling for pokemon in `move-end` phase --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/phases/move-end-phase.ts | 9 ++++- src/phases/move-phase.ts | 7 ++-- test/abilities/dancer.test.ts | 20 ++++++----- test/abilities/mold_breaker.test.ts | 51 +++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 test/abilities/mold_breaker.test.ts diff --git a/src/phases/move-end-phase.ts b/src/phases/move-end-phase.ts index 46e266a32b7..f3a40ce69bd 100644 --- a/src/phases/move-end-phase.ts +++ b/src/phases/move-end-phase.ts @@ -1,13 +1,20 @@ import { globalScene } from "#app/global-scene"; import { BattlerTagLapseType } from "#app/data/battler-tags"; import { PokemonPhase } from "./pokemon-phase"; +import type { BattlerIndex } from "#app/battle"; export class MoveEndPhase extends PokemonPhase { + private wasFollowUp: boolean; + constructor(battlerIndex: BattlerIndex, wasFollowUp: boolean = false) { + super(battlerIndex); + this.wasFollowUp = wasFollowUp; + } + start() { super.start(); const pokemon = this.getPokemon(); - if (pokemon.isActive(true)) { + if (!this.wasFollowUp && pokemon?.isActive(true)) { pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE); } diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index e04f48c2880..cab02174605 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -465,13 +465,10 @@ export class MovePhase extends BattlePhase { } /** - * Queues a {@linkcode MoveEndPhase} if the move wasn't a {@linkcode followUp} and {@linkcode canMove()} returns `true`, - * then ends the phase. + * Queues a {@linkcode MoveEndPhase} and then ends the phase */ public end(): void { - if (!this.followUp && this.canMove()) { - globalScene.unshiftPhase(new MoveEndPhase(this.pokemon.getBattlerIndex())); - } + globalScene.unshiftPhase(new MoveEndPhase(this.pokemon.getBattlerIndex(), this.followUp)); super.end(); } diff --git a/test/abilities/dancer.test.ts b/test/abilities/dancer.test.ts index 56c357b2212..c296329473d 100644 --- a/test/abilities/dancer.test.ts +++ b/test/abilities/dancer.test.ts @@ -39,20 +39,24 @@ describe("Abilities - Dancer", () => { game.move.select(Moves.SPLASH); game.move.select(Moves.SWORDS_DANCE, 1); await game.setTurnOrder([BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2]); - await game.phaseInterceptor.to("MovePhase"); - // immediately copies ally move - await game.phaseInterceptor.to("MovePhase", false); + await game.phaseInterceptor.to("MovePhase"); // feebas uses swords dance + await game.phaseInterceptor.to("MovePhase", false); // oricorio copies swords dance + let currentPhase = game.scene.getCurrentPhase() as MovePhase; expect(currentPhase.pokemon).toBe(oricorio); expect(currentPhase.move.moveId).toBe(Moves.SWORDS_DANCE); - await game.phaseInterceptor.to("MoveEndPhase"); - await game.phaseInterceptor.to("MovePhase"); - // immediately copies enemy move - await game.phaseInterceptor.to("MovePhase", false); + + await game.phaseInterceptor.to("MoveEndPhase"); // end oricorio's move + await game.phaseInterceptor.to("MovePhase"); // magikarp 1 copies swords dance + await game.phaseInterceptor.to("MovePhase"); // magikarp 2 copies swords dance + await game.phaseInterceptor.to("MovePhase"); // magikarp (left) uses victory dance + await game.phaseInterceptor.to("MovePhase", false); // oricorio copies magikarp's victory dance + currentPhase = game.scene.getCurrentPhase() as MovePhase; expect(currentPhase.pokemon).toBe(oricorio); expect(currentPhase.move.moveId).toBe(Moves.VICTORY_DANCE); - await game.phaseInterceptor.to("BerryPhase"); + + await game.phaseInterceptor.to("BerryPhase"); // finish the turn // doesn't use PP if copied move is also in moveset expect(oricorio.moveset[0]?.ppUsed).toBe(0); diff --git a/test/abilities/mold_breaker.test.ts b/test/abilities/mold_breaker.test.ts new file mode 100644 index 00000000000..8f050a68d76 --- /dev/null +++ b/test/abilities/mold_breaker.test.ts @@ -0,0 +1,51 @@ +import { BattlerIndex } from "#app/battle"; +import { globalScene } from "#app/global-scene"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Mold Breaker", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH ]) + .ability(Abilities.MOLD_BREAKER) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should turn off the ignore abilities arena variable after the user's move", async () => { + game.override.enemyMoveset(Moves.SPLASH) + .ability(Abilities.MOLD_BREAKER) + .moveset([ Moves.ERUPTION ]) + .startingLevel(100) + .enemyLevel(2); + await game.classicMode.startBattle([ Species.MAGIKARP ]); + const enemy = game.scene.getEnemyPokemon()!; + + expect(enemy.isFainted()).toBe(false); + game.move.select(Moves.SPLASH); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("MoveEndPhase", true); + expect(globalScene.arena.ignoreAbilities).toBe(false); + }); +}); From 420c2e37c21d123e1fb88bb64e68070305efd7c5 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Fri, 4 Apr 2025 17:13:21 -0500 Subject: [PATCH 13/33] [GitHub] Add path filters to avoid unnecessarily re-running tests (#5497) * Add path filters to avoid unnecessarily re-running tests * Apply suggestions from kev's review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update .github/workflows/tests.yml Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Don't ignore image files for tests --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- .github/workflows/tests.yml | 48 +++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 167a108e58c..ccc8604ff7e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,18 +5,58 @@ on: # but only for the main branch push: branches: - - main # Trigger on push events to the main branch + - main # Trigger on push events to the main branch - beta # Trigger on push events to the beta branch + # go upvote https://github.com/actions/runner/issues/1182 and yell at microsoft until they fix this or ditch yml for workflows + paths: + # src and test files + - "src/**" + - "test/**" + - "public/**" + # Workflows that can impact tests + - ".github/workflows/test*.yml" + # top-level files + - "package*.json" + - ".nvrmc" # Updates to node version can break tests + - "vite.*.ts" # vite.config.ts, vite.vitest.config.ts, vitest.workspace.ts + - "tsconfig*.json" # tsconfig.json tweaking can impact compilation + - "global.d.ts" + - ".env.*" + # Blanket negations for files that cannot impact tests + - "!**/*.py" # No .py files + - "!**/*.sh" # No .sh files + - "!**/*.md" # No .md files + - "!**/.git*" # .gitkeep and family + pull_request: branches: - - main # Trigger on pull request events targeting the main branch + - main # Trigger on pull request events targeting the main branch - beta # Trigger on pull request events targeting the beta branch + paths: # go upvote https://github.com/actions/runner/issues/1182 and yell at microsoft because until then we have to duplicate this + # src and test files + - "src/**" + - "test/**" + - "public/**" + # Workflows that can impact tests + - ".github/workflows/test*.yml" + # top-level files + - "package*.json" + - ".nvrmc" # Updates to node version can break tests + - "vite*" # vite.config.ts, vite.vitest.config.ts, vitest.workspace.ts + - "tsconfig*.json" # tsconfig.json tweaking can impact compilation + - "global.d.ts" + - ".env.*" + # Blanket negations for files that cannot impact tests + - "!**/*.py" # No .py files + - "!**/*.sh" # No .sh files + - "!**/*.md" # No .md files + - "!**/.git*" # .gitkeep and family merge_group: types: [checks_requested] jobs: run-tests: - name: Run Tests + name: Run Tests strategy: matrix: shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] @@ -24,4 +64,4 @@ jobs: with: project: main shard: ${{ matrix.shard }} - totalShards: 10 \ No newline at end of file + totalShards: 10 From 9e4162d4299466d15a04185f6e86eb0de0189671 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Fri, 4 Apr 2025 19:40:25 -0500 Subject: [PATCH 14/33] [Bug] Fix super luck implementation (#5625) * Fix super luck implementation * Use numberholder instead of booleanholder * Update tsdoc for getCritStage --- src/data/ability.ts | 14 ++++++++-- src/field/pokemon.ts | 14 +++------- test/abilities/super_luck.test.ts | 43 +++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 12 deletions(-) create mode 100644 test/abilities/super_luck.test.ts diff --git a/src/data/ability.ts b/src/data/ability.ts index eea24c791b0..790bff2290d 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -3499,8 +3499,18 @@ export class BonusCritAbAttr extends AbAttr { constructor() { super(false); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { - (args[0] as Utils.BooleanHolder).value = true; + + /** + * Apply the bonus crit ability by increasing the value in the provided number holder by 1 + * + * @param pokemon The pokemon with the BonusCrit ability (unused) + * @param passive Unused + * @param simulated Unused + * @param cancelled Unused + * @param args Args[0] is a number holder containing the crit stage. + */ + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: [Utils.NumberHolder, ...any]): void { + (args[0] as Utils.NumberHolder).value += 1; } } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index f89319a6e30..f3e758e4efd 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1340,8 +1340,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * Retrieves the critical-hit stage considering the move used and the Pokemon - * who used it. + * Calculate the critical-hit stage of a move used against this pokemon by + * the given source + * * @param source the {@linkcode Pokemon} who using the move * @param move the {@linkcode Move} being used * @returns the final critical-hit stage value @@ -1360,14 +1361,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { source.isPlayer(), critStage, ); - const bonusCrit = new Utils.BooleanHolder(false); - //@ts-ignore - if (applyAbAttrs(BonusCritAbAttr, source, null, false, bonusCrit)) { - // TODO: resolve ts-ignore. This is a promise. Checking a promise is bogus. - if (bonusCrit.value) { - critStage.value += 1; - } - } + applyAbAttrs(BonusCritAbAttr, source, null, false, critStage) const critBoostTag = source.getTag(CritBoostTag); if (critBoostTag) { if (critBoostTag instanceof DragonCheerTag) { diff --git a/test/abilities/super_luck.test.ts b/test/abilities/super_luck.test.ts new file mode 100644 index 00000000000..bc9524de801 --- /dev/null +++ b/test/abilities/super_luck.test.ts @@ -0,0 +1,43 @@ +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Abilities - Super Luck", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([Moves.TACKLE]) + .ability(Abilities.SUPER_LUCK) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should increase the crit stage of a user by 1", async () => { + await game.classicMode.startBattle([Species.MAGIKARP]); + const enemy = game.scene.getEnemyPokemon()!; + const fn = vi.spyOn(enemy, "getCritStage"); + game.move.select(Moves.TACKLE); + await game.phaseInterceptor.to("BerryPhase"); + expect(fn).toHaveReturnedWith(1); + fn.mockRestore(); + }); +}); From 5318d717b3035f0e047533961d6a3e22505693dd Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Fri, 4 Apr 2025 19:43:46 -0500 Subject: [PATCH 15/33] [Refactor] [Docs] Minor refactor of `move.checkFlags` into `move.doesFlagEffectApply` (#5264) * Refactor Move.checkFlags * Improve jsdoc clarity * Fix improper recursive call for the IGNORE_PROTECT check * Fix improper placement of followUp check * Get rid of unnecssary break * Fix last import * Remove latent checkFlag call in move-effect-phase * Remedy perish body oversight * Apply kev's suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/ability.ts | 21 ++++++++++----------- src/data/moves/move.ts | 32 ++++++++++++++++++++++++++------ src/phases/move-effect-phase.ts | 5 +++-- src/phases/move-phase.ts | 2 +- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 790bff2290d..a7107ce2e9d 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -998,7 +998,7 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr { override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; - return move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status + return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && !attacker.status && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !move.hitsSubstitute(attacker, pokemon) && attacker.canSetStatus(effect, true, false, pokemon); } @@ -1038,7 +1038,7 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr { } override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { - return move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance + return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && pokemon.randSeedInt(100) < this.chance && !move.hitsSubstitute(attacker, pokemon) && attacker.canAddTag(this.tagType); } @@ -1085,7 +1085,7 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr { } override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { - return !simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) + return !simulated && move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !move.hitsSubstitute(attacker, pokemon); } @@ -1118,8 +1118,7 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr { } override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { - return (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !move.hitsSubstitute(attacker, pokemon)) - && !attacker.getTag(BattlerTagType.PERISH_SONG); + return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && !attacker.getTag(BattlerTagType.PERISH_SONG); } override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { @@ -1163,7 +1162,7 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr { } override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { - return move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) + return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && attacker.getAbility().isSwappable && !move.hitsSubstitute(attacker, pokemon); } @@ -1189,7 +1188,7 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr { } override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { - return move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && attacker.getAbility().isSuppressable + return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && attacker.getAbility().isSuppressable && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr) && !move.hitsSubstitute(attacker, pokemon); } @@ -1220,7 +1219,7 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { return attacker.getTag(BattlerTagType.DISABLED) === null && !move.hitsSubstitute(attacker, pokemon) - && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance); + && move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance); } override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { @@ -1925,7 +1924,7 @@ export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr { super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args) && !(pokemon !== attacker && move.hitsSubstitute(attacker, pokemon)) && (simulated || !attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker - && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) + && (!this.contactRequired || move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon})) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) ) { const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; return simulated || attacker.canSetStatus(effect, true, false, pokemon); @@ -1964,7 +1963,7 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr { /**Battler tags inflicted by abilities post attacking are also considered additional effects.*/ return super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args) && !attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker && - (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && + (!this.contactRequired || move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon})) && pokemon.randSeedInt(100) < this.chance(attacker, pokemon, move) && !pokemon.status; } @@ -4761,7 +4760,7 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr { } override canApplyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): boolean { - const diedToDirectDamage = move !== undefined && attacker !== undefined && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon); + const diedToDirectDamage = move !== undefined && attacker !== undefined && move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}); const cancelled = new Utils.BooleanHolder(false); globalScene.getField(true).map(p => applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled, simulated)); if (!diedToDirectDamage || cancelled.value || attacker!.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 2624fe6cda9..421314b1945 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -346,7 +346,7 @@ export default class Move implements Localizable { * @param target The {@linkcode Pokemon} targeted by this move * @returns `true` if the move can bypass the target's Substitute; `false` otherwise. */ - hitsSubstitute(user: Pokemon, target: Pokemon | null): boolean { + hitsSubstitute(user: Pokemon, target?: Pokemon): boolean { if ([ MoveTarget.USER, MoveTarget.USER_SIDE, MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES ].includes(this.moveTarget) || !target?.getTag(BattlerTagType.SUBSTITUTE)) { return false; @@ -618,12 +618,30 @@ export default class Move implements Localizable { /** * Checks if the move flag applies to the pokemon(s) using/receiving the move + * + * This method will take the `user`'s ability into account when reporting flags, e.g. + * calling this method for {@linkcode MoveFlags.MAKES_CONTACT | MAKES_CONTACT} + * will return `false` if the user has a {@linkcode Abilities.LONG_REACH} that is not being suppressed. + * + * **Note:** This method only checks if the move should have effectively have the flag applied to its use. + * It does *not* check whether the flag will trigger related effects. + * For example using this method to check {@linkcode MoveFlags.WIND_MOVE} + * will not consider {@linkcode Abilities.WIND_RIDER | Wind Rider }. + * + * To simply check whether the move has a flag, use {@linkcode hasFlag}. * @param flag {@linkcode MoveFlags} MoveFlag to check on user and/or target * @param user {@linkcode Pokemon} the Pokemon using the move * @param target {@linkcode Pokemon} the Pokemon receiving the move + * @param isFollowUp (defaults to `false`) `true` if the move was used as a follow up * @returns boolean + * @see {@linkcode hasFlag} */ - checkFlag(flag: MoveFlags, user: Pokemon, target: Pokemon | null): boolean { + doesFlagEffectApply({ flag, user, target, isFollowUp = false }: { + flag: MoveFlags; + user: Pokemon; + target?: Pokemon; + isFollowUp?: boolean; + }): boolean { // special cases below, eg: if the move flag is MAKES_CONTACT, and the user pokemon has an ability that ignores contact (like "Long Reach"), then overrides and move does not make contact switch (flag) { case MoveFlags.MAKES_CONTACT: @@ -633,16 +651,18 @@ export default class Move implements Localizable { break; case MoveFlags.IGNORE_ABILITIES: if (user.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) { - const abilityEffectsIgnored = new Utils.BooleanHolder(false); + const abilityEffectsIgnored = new Utils.BooleanHolder(false); applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, false, this); if (abilityEffectsIgnored.value) { return true; } + // Sunsteel strike, Moongeist beam, and photon geyser will not ignore abilities if invoked + // by another move, such as via metronome. } - break; + return this.hasFlag(MoveFlags.IGNORE_ABILITIES) && !isFollowUp; case MoveFlags.IGNORE_PROTECT: if (user.hasAbilityWithAttr(IgnoreProtectOnContactAbAttr) - && this.checkFlag(MoveFlags.MAKES_CONTACT, user, null)) { + && this.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user })) { return true; } break; @@ -1214,7 +1234,7 @@ export class MoveEffectAttr extends MoveAttr { canApply(user: Pokemon, target: Pokemon, move: Move, args?: any[]) { return !! (this.selfTarget ? user.hp && !user.getTag(BattlerTagType.FRENZY) : target.hp) && (this.selfTarget || !target.getTag(BattlerTagType.PROTECTED) || - move.checkFlag(MoveFlags.IGNORE_PROTECT, user, target)); + move.doesFlagEffectApply({ flag: MoveFlags.IGNORE_PROTECT, user, target })); } /** Applies move effects so long as they are able based on {@linkcode canApply} */ diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index bd1c9caad96..7cc389651dd 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -289,7 +289,8 @@ export class MoveEffectPhase extends PokemonPhase { /** Is the target protected by Protect, etc. or a relevant conditional protection effect? */ const isProtected = ![MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES].includes(this.move.getMove().moveTarget) && - (bypassIgnoreProtect.value || !this.move.getMove().checkFlag(MoveFlags.IGNORE_PROTECT, user, target)) && + (bypassIgnoreProtect.value || + !this.move.getMove().doesFlagEffectApply({ flag: MoveFlags.IGNORE_PROTECT, user, target })) && (hasConditionalProtectApplied.value || (!target.findTags(t => t instanceof DamageProtectedTag).length && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType))) || @@ -307,7 +308,7 @@ export class MoveEffectPhase extends PokemonPhase { /** Is the target's magic bounce ability not ignored and able to reflect this move? */ const canMagicBounce = !isReflecting && - !move.checkFlag(MoveFlags.IGNORE_ABILITIES, user, target) && + !move.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user, target }) && target.hasAbilityWithAttr(ReflectStatusMoveAbAttr); const semiInvulnerableTag = target.getTag(SemiInvulnerableTag); diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index cab02174605..33e772eb2ea 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -168,7 +168,7 @@ export class MovePhase extends BattlePhase { // Check move to see if arena.ignoreAbilities should be true. if (!this.followUp || this.reflected) { - if (this.move.getMove().checkFlag(MoveFlags.IGNORE_ABILITIES, this.pokemon, null)) { + if (this.move.getMove().doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user: this.pokemon, isFollowUp: this.followUp })) { globalScene.arena.setIgnoreAbilities(true, this.pokemon.getBattlerIndex()); } } From 1e6ceb55812fe00dde450ecdbc31d8876e5c4066 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sat, 5 Apr 2025 20:10:52 -0700 Subject: [PATCH 16/33] [Misc] Clean up various phases (part 1) (#4797) * Clean up various phases Remove redundant code, utilize default parameters, clean up some leftover `strict-null` `TODO`s, replace `integer` with `number` * Replace `* as Utils` imports with named imports * Apply Biome --- src/battle-scene.ts | 2 - src/phases/add-enemy-buff-modifier-phase.ts | 4 -- src/phases/attempt-run-phase.ts | 10 +-- src/phases/battle-end-phase.ts | 16 +++-- src/phases/battle-phase.ts | 12 ++-- src/phases/berry-phase.ts | 6 +- src/phases/common-anim-phase.ts | 11 +++- src/phases/damage-anim-phase.ts | 9 ++- src/phases/egg-hatch-phase.ts | 73 ++++++++++----------- src/phases/encounter-phase.ts | 4 +- src/phases/evolution-phase.ts | 28 ++++---- src/phases/exp-phase.ts | 4 +- src/phases/form-change-phase.ts | 8 +-- src/phases/game-over-phase.ts | 4 +- src/phases/login-phase.ts | 16 ++--- src/phases/message-phase.ts | 12 ++-- src/phases/money-reward-phase.ts | 4 +- src/phases/move-anim-test-phase.ts | 50 -------------- src/phases/move-end-phase.ts | 2 +- src/phases/move-phase.ts | 3 +- src/phases/mystery-encounter-phases.ts | 5 +- src/phases/new-biome-encounter-phase.ts | 4 -- src/phases/next-encounter-phase.ts | 4 -- src/phases/party-heal-phase.ts | 4 +- src/phases/pokemon-anim-phase.ts | 10 +-- src/phases/pokemon-heal-phase.ts | 6 +- src/phases/pokemon-phase.ts | 11 ++-- src/phases/post-game-over-phase.ts | 4 +- src/phases/post-turn-status-effect-phase.ts | 6 +- src/phases/reload-session-phase.ts | 8 +-- src/phases/scan-ivs-phase.ts | 4 +- src/phases/select-biome-phase.ts | 12 ++-- src/phases/select-challenge-phase.ts | 4 -- src/phases/select-gender-phase.ts | 4 -- src/phases/select-modifier-phase.ts | 3 +- src/phases/select-starter-phase.ts | 4 -- src/phases/select-target-phase.ts | 1 + src/phases/shiny-sparkle-phase.ts | 1 + src/phases/show-party-exp-bar-phase.ts | 4 +- src/phases/show-trainer-phase.ts | 4 -- src/phases/summon-missing-phase.ts | 4 -- src/phases/switch-summon-phase.ts | 10 +-- src/phases/test-message-phase.ts | 7 -- src/phases/title-phase.ts | 14 ++-- src/phases/trainer-message-test-phase.ts | 47 ------------- src/phases/trainer-victory-phase.ts | 8 +-- src/phases/turn-end-phase.ts | 4 -- src/phases/turn-init-phase.ts | 4 -- src/phases/turn-start-phase.ts | 14 ++-- src/phases/unavailable-phase.ts | 4 -- src/phases/weather-effect-phase.ts | 13 ++-- 51 files changed, 178 insertions(+), 332 deletions(-) delete mode 100644 src/phases/move-anim-test-phase.ts delete mode 100644 src/phases/test-message-phase.ts delete mode 100644 src/phases/trainer-message-test-phase.ts diff --git a/src/battle-scene.ts b/src/battle-scene.ts index a759cbb84c2..f676ba63306 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1526,8 +1526,6 @@ export default class BattleScene extends SceneBase { this.currentBattle.mysteryEncounterType = mysteryEncounterType; } - //this.pushPhase(new TrainerMessageTestPhase(this, TrainerType.RIVAL, TrainerType.RIVAL_2, TrainerType.RIVAL_3, TrainerType.RIVAL_4, TrainerType.RIVAL_5, TrainerType.RIVAL_6)); - if (!waveIndex && lastBattle) { const isWaveIndexMultipleOfTen = !(lastBattle.waveIndex % 10); const isEndlessOrDaily = this.gameMode.hasShortBiomes || this.gameMode.isDaily; diff --git a/src/phases/add-enemy-buff-modifier-phase.ts b/src/phases/add-enemy-buff-modifier-phase.ts index 7d91e64382a..16ed78e6d0d 100644 --- a/src/phases/add-enemy-buff-modifier-phase.ts +++ b/src/phases/add-enemy-buff-modifier-phase.ts @@ -9,10 +9,6 @@ import { Phase } from "#app/phase"; import { globalScene } from "#app/global-scene"; export class AddEnemyBuffModifierPhase extends Phase { - constructor() { - super(); - } - start() { super.start(); diff --git a/src/phases/attempt-run-phase.ts b/src/phases/attempt-run-phase.ts index dab5b8789da..e5691f5fb8e 100644 --- a/src/phases/attempt-run-phase.ts +++ b/src/phases/attempt-run-phase.ts @@ -1,10 +1,10 @@ import { applyAbAttrs, applyPreLeaveFieldAbAttrs, PreLeaveFieldAbAttr, RunSuccessAbAttr } from "#app/data/ability"; -import { Stat } from "#app/enums/stat"; -import { StatusEffect } from "#app/enums/status-effect"; +import { Stat } from "#enums/stat"; +import { StatusEffect } from "#enums/status-effect"; import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import i18next from "i18next"; -import * as Utils from "#app/utils"; +import { NumberHolder } from "#app/utils"; import { BattleEndPhase } from "./battle-end-phase"; import { NewBattlePhase } from "./new-battle-phase"; import { PokemonPhase } from "./pokemon-phase"; @@ -22,7 +22,7 @@ export class AttemptRunPhase extends PokemonPhase { const playerPokemon = this.getPokemon(); - const escapeChance = new Utils.NumberHolder(0); + const escapeChance = new NumberHolder(0); this.attemptRunAway(playerField, enemyField, escapeChance); @@ -63,7 +63,7 @@ export class AttemptRunPhase extends PokemonPhase { this.end(); } - attemptRunAway(playerField: PlayerPokemon[], enemyField: EnemyPokemon[], escapeChance: Utils.NumberHolder) { + attemptRunAway(playerField: PlayerPokemon[], enemyField: EnemyPokemon[], escapeChance: NumberHolder) { /** Sum of the speed of all enemy pokemon on the field */ const enemySpeed = enemyField.reduce( (total: number, enemyPokemon: Pokemon) => total + enemyPokemon.getStat(Stat.SPD), diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index e6a0c66548e..ff17b17ab8b 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -26,13 +26,15 @@ export class BattleEndPhase extends BattlePhase { return true; }); // `phaseQueuePrepend` is private, so we have to use this inefficient loop. - while (globalScene.tryRemoveUnshiftedPhase(phase => { - if (phase instanceof BattleEndPhase) { - this.isVictory ||= phase.isVictory; - return true; - } - return false; - })) {} + while ( + globalScene.tryRemoveUnshiftedPhase(phase => { + if (phase instanceof BattleEndPhase) { + this.isVictory ||= phase.isVictory; + return true; + } + return false; + }) + ) {} globalScene.gameData.gameStats.battles++; if ( diff --git a/src/phases/battle-phase.ts b/src/phases/battle-phase.ts index 72bcc85bc62..d70b3909639 100644 --- a/src/phases/battle-phase.ts +++ b/src/phases/battle-phase.ts @@ -3,13 +3,13 @@ import { TrainerSlot } from "#enums/trainer-slot"; import { Phase } from "#app/phase"; export class BattlePhase extends Phase { - constructor() { - super(); - } - showEnemyTrainer(trainerSlot: TrainerSlot = TrainerSlot.NONE): void { - const sprites = globalScene.currentBattle.trainer?.getSprites()!; // TODO: is this bang correct? - const tintSprites = globalScene.currentBattle.trainer?.getTintSprites()!; // TODO: is this bang correct? + if (!globalScene.currentBattle.trainer) { + console.warn("Enemy trainer is missing!"); + return; + } + const sprites = globalScene.currentBattle.trainer.getSprites(); + const tintSprites = globalScene.currentBattle.trainer.getTintSprites(); for (let i = 0; i < sprites.length; i++) { const visible = !trainerSlot || !i === (trainerSlot === TrainerSlot.TRAINER) || sprites.length < 2; [sprites[i], tintSprites[i]].map(sprite => { diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index 0048f8cd2f2..e5614739903 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -4,7 +4,7 @@ import { BerryUsedEvent } from "#app/events/battle-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { BerryModifier } from "#app/modifier/modifier"; import i18next from "i18next"; -import * as Utils from "#app/utils"; +import { BooleanHolder } from "#app/utils"; import { FieldPhase } from "./field-phase"; import { CommonAnimPhase } from "./common-anim-phase"; import { globalScene } from "#app/global-scene"; @@ -20,7 +20,7 @@ export class BerryPhase extends FieldPhase { }, pokemon.isPlayer()); if (hasUsableBerry) { - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); pokemon.getOpponents().map(opp => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled)); if (cancelled.value) { @@ -44,7 +44,7 @@ export class BerryPhase extends FieldPhase { globalScene.updateModifiers(pokemon.isPlayer()); - applyAbAttrs(HealFromBerryUseAbAttr, pokemon, new Utils.BooleanHolder(false)); + applyAbAttrs(HealFromBerryUseAbAttr, pokemon, new BooleanHolder(false)); } } }); diff --git a/src/phases/common-anim-phase.ts b/src/phases/common-anim-phase.ts index d32e93ea6aa..5be5e112389 100644 --- a/src/phases/common-anim-phase.ts +++ b/src/phases/common-anim-phase.ts @@ -6,13 +6,18 @@ import { PokemonPhase } from "./pokemon-phase"; export class CommonAnimPhase extends PokemonPhase { private anim: CommonAnim | null; - private targetIndex: number | undefined; + private targetIndex?: BattlerIndex; private playOnEmptyField: boolean; - constructor(battlerIndex?: BattlerIndex, targetIndex?: BattlerIndex, anim?: CommonAnim, playOnEmptyField = false) { + constructor( + battlerIndex?: BattlerIndex, + targetIndex?: BattlerIndex, + anim: CommonAnim | null = null, + playOnEmptyField = false, + ) { super(battlerIndex); - this.anim = anim!; // TODO: is this bang correct? + this.anim = anim; this.targetIndex = targetIndex; this.playOnEmptyField = playOnEmptyField; } diff --git a/src/phases/damage-anim-phase.ts b/src/phases/damage-anim-phase.ts index 703cd3d160e..696a2e55b6f 100644 --- a/src/phases/damage-anim-phase.ts +++ b/src/phases/damage-anim-phase.ts @@ -10,11 +10,16 @@ export class DamageAnimPhase extends PokemonPhase { private damageResult: DamageResult; private critical: boolean; - constructor(battlerIndex: BattlerIndex, amount: number, damageResult?: DamageResult, critical = false) { + constructor( + battlerIndex: BattlerIndex, + amount: number, + damageResult: DamageResult = HitResult.EFFECTIVE, + critical = false, + ) { super(battlerIndex); this.amount = amount; - this.damageResult = damageResult || HitResult.EFFECTIVE; + this.damageResult = damageResult; this.critical = critical; } diff --git a/src/phases/egg-hatch-phase.ts b/src/phases/egg-hatch-phase.ts index 49a408e8699..07eeeb0f8ae 100644 --- a/src/phases/egg-hatch-phase.ts +++ b/src/phases/egg-hatch-phase.ts @@ -11,7 +11,7 @@ import PokemonInfoContainer from "#app/ui/pokemon-info-container"; import { Mode } from "#app/ui/ui"; import i18next from "i18next"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; -import * as Utils from "#app/utils"; +import { fixedInt, getFrameMs, randInt } from "#app/utils"; import type { EggLapsePhase } from "./egg-lapse-phase"; import type { EggHatchData } from "#app/data/egg-hatch-data"; import { doShinySparkleAnim } from "#app/field/anims"; @@ -306,17 +306,17 @@ export class EggHatchPhase extends Phase { this.canSkip = false; this.hatched = true; if (this.evolutionBgm) { - SoundFade.fadeOut(globalScene, this.evolutionBgm, Utils.fixedInt(100)); + SoundFade.fadeOut(globalScene, this.evolutionBgm, fixedInt(100)); } for (let e = 0; e < 5; e++) { - globalScene.time.delayedCall(Utils.fixedInt(375 * e), () => + globalScene.time.delayedCall(fixedInt(375 * e), () => globalScene.playSound("se/egg_hatch", { volume: 1 - e * 0.2 }), ); } this.eggLightraysOverlay.setVisible(true); this.eggLightraysOverlay.play("egg_lightrays"); globalScene.tweens.add({ - duration: Utils.fixedInt(125), + duration: fixedInt(125), targets: this.eggHatchOverlay, alpha: 1, ease: "Cubic.easeIn", @@ -325,7 +325,7 @@ export class EggHatchPhase extends Phase { this.canSkip = true; }, }); - globalScene.time.delayedCall(Utils.fixedInt(1500), () => { + globalScene.time.delayedCall(fixedInt(1500), () => { this.canSkip = false; if (!this.skipped) { this.doReveal(); @@ -363,46 +363,43 @@ export class EggHatchPhase extends Phase { this.pokemonSprite.setPipelineData("shiny", this.pokemon.shiny); this.pokemonSprite.setPipelineData("variant", this.pokemon.variant); this.pokemonSprite.setVisible(true); - globalScene.time.delayedCall(Utils.fixedInt(250), () => { + globalScene.time.delayedCall(fixedInt(250), () => { this.eggsToHatchCount--; this.eggHatchHandler.eventTarget.dispatchEvent(new EggCountChangedEvent(this.eggsToHatchCount)); this.pokemon.cry(); if (isShiny) { - globalScene.time.delayedCall(Utils.fixedInt(500), () => { + globalScene.time.delayedCall(fixedInt(500), () => { doShinySparkleAnim(this.pokemonShinySparkle, this.pokemon.variant); }); } - globalScene.time.delayedCall( - Utils.fixedInt(!this.skipped ? (!isShiny ? 1250 : 1750) : !isShiny ? 250 : 750), - () => { - this.infoContainer.show(this.pokemon, false, this.skipped ? 2 : 1); + globalScene.time.delayedCall(fixedInt(!this.skipped ? (!isShiny ? 1250 : 1750) : !isShiny ? 250 : 750), () => { + this.infoContainer.show(this.pokemon, false, this.skipped ? 2 : 1); - globalScene.playSoundWithoutBgm("evolution_fanfare"); + globalScene.playSoundWithoutBgm("evolution_fanfare"); - globalScene.ui.showText( - i18next.t("egg:hatchFromTheEgg", { - pokemonName: this.pokemon.species.getExpandedSpeciesName(), - }), - null, - () => { - globalScene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs); - globalScene.gameData.setPokemonCaught(this.pokemon, true, true).then(() => { - globalScene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex).then(value => { - this.eggHatchData.setEggMoveUnlocked(value); - globalScene.ui.showText("", 0); - this.end(); - }); + globalScene.ui.showText( + i18next.t("egg:hatchFromTheEgg", { + pokemonName: this.pokemon.species.getExpandedSpeciesName(), + }), + null, + () => { + globalScene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs); + globalScene.gameData.setPokemonCaught(this.pokemon, true, true).then(() => { + globalScene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex).then(value => { + this.eggHatchData.setEggMoveUnlocked(value); + globalScene.ui.showText("", 0); + this.end(); }); - }, - null, - true, - 3000, - ); - }, - ); + }); + }, + null, + true, + 3000, + ); + }); }); globalScene.tweens.add({ - duration: Utils.fixedInt(this.skipped ? 500 : 3000), + duration: fixedInt(this.skipped ? 500 : 3000), targets: this.eggHatchOverlay, alpha: 0, ease: "Cubic.easeOut", @@ -427,9 +424,9 @@ export class EggHatchPhase extends Phase { doSpray(intensity: number, offsetY?: number) { globalScene.tweens.addCounter({ repeat: intensity, - duration: Utils.getFrameMs(1), + duration: getFrameMs(1), onRepeat: () => { - this.doSprayParticle(Utils.randInt(8), offsetY || 0); + this.doSprayParticle(randInt(8), offsetY || 0); }, }); } @@ -448,12 +445,12 @@ export class EggHatchPhase extends Phase { let f = 0; let yOffset = 0; - const speed = 3 - Utils.randInt(8); - const amp = 24 + Utils.randInt(32); + const speed = 3 - randInt(8); + const amp = 24 + randInt(32); const particleTimer = globalScene.tweens.addCounter({ repeat: -1, - duration: Utils.getFrameMs(1), + duration: getFrameMs(1), onRepeat: () => { updateParticle(); }, diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index ad2bf689e38..9e5edf3e1d9 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -43,10 +43,10 @@ import { getNatureName } from "#app/data/nature"; export class EncounterPhase extends BattlePhase { private loaded: boolean; - constructor(loaded?: boolean) { + constructor(loaded = false) { super(); - this.loaded = !!loaded; + this.loaded = loaded; } start() { diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index bb283fa8139..203c7542eff 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -5,7 +5,7 @@ import { globalScene } from "#app/global-scene"; import type { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; import { FusionSpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; import type EvolutionSceneHandler from "#app/ui/evolution-scene-handler"; -import * as Utils from "#app/utils"; +import { fixedInt, getFrameMs, randInt } from "#app/utils"; import { Mode } from "#app/ui/ui"; import { cos, sin } from "#app/field/anims"; import type { PlayerPokemon } from "#app/field/pokemon"; @@ -332,9 +332,9 @@ export class EvolutionPhase extends Phase { () => this.end(), null, true, - Utils.fixedInt(4000), + fixedInt(4000), ); - globalScene.time.delayedCall(Utils.fixedInt(4250), () => globalScene.playBgm()); + globalScene.time.delayedCall(fixedInt(4250), () => globalScene.playBgm()); }); }); }; @@ -392,7 +392,7 @@ export class EvolutionPhase extends Phase { globalScene.tweens.addCounter({ repeat: 64, - duration: Utils.getFrameMs(1), + duration: getFrameMs(1), onRepeat: () => { if (f < 64) { if (!(f & 7)) { @@ -411,7 +411,7 @@ export class EvolutionPhase extends Phase { globalScene.tweens.addCounter({ repeat: 96, - duration: Utils.getFrameMs(1), + duration: getFrameMs(1), onRepeat: () => { if (f < 96) { if (f < 6) { @@ -461,7 +461,7 @@ export class EvolutionPhase extends Phase { globalScene.tweens.addCounter({ repeat: 48, - duration: Utils.getFrameMs(1), + duration: getFrameMs(1), onRepeat: () => { if (!f) { for (let i = 0; i < 16; i++) { @@ -482,14 +482,14 @@ export class EvolutionPhase extends Phase { globalScene.tweens.addCounter({ repeat: 48, - duration: Utils.getFrameMs(1), + duration: getFrameMs(1), onRepeat: () => { if (!f) { for (let i = 0; i < 8; i++) { this.doSprayParticle(i); } } else if (f < 50) { - this.doSprayParticle(Utils.randInt(8)); + this.doSprayParticle(randInt(8)); } f++; }, @@ -506,7 +506,7 @@ export class EvolutionPhase extends Phase { const particleTimer = globalScene.tweens.addCounter({ repeat: -1, - duration: Utils.getFrameMs(1), + duration: getFrameMs(1), onRepeat: () => { updateParticle(); }, @@ -543,7 +543,7 @@ export class EvolutionPhase extends Phase { const particleTimer = globalScene.tweens.addCounter({ repeat: -1, - duration: Utils.getFrameMs(1), + duration: getFrameMs(1), onRepeat: () => { updateParticle(); }, @@ -575,7 +575,7 @@ export class EvolutionPhase extends Phase { const particleTimer = globalScene.tweens.addCounter({ repeat: -1, - duration: Utils.getFrameMs(1), + duration: getFrameMs(1), onRepeat: () => { updateParticle(); }, @@ -605,12 +605,12 @@ export class EvolutionPhase extends Phase { let f = 0; let yOffset = 0; - const speed = 3 - Utils.randInt(8); - const amp = 48 + Utils.randInt(64); + const speed = 3 - randInt(8); + const amp = 48 + randInt(64); const particleTimer = globalScene.tweens.addCounter({ repeat: -1, - duration: Utils.getFrameMs(1), + duration: getFrameMs(1), onRepeat: () => { updateParticle(); }, diff --git a/src/phases/exp-phase.ts b/src/phases/exp-phase.ts index 092482d4c18..b7d62c92bcf 100644 --- a/src/phases/exp-phase.ts +++ b/src/phases/exp-phase.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { ExpBoosterModifier } from "#app/modifier/modifier"; import i18next from "i18next"; -import * as Utils from "#app/utils"; +import { NumberHolder } from "#app/utils"; import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; import { LevelUpPhase } from "./level-up-phase"; @@ -19,7 +19,7 @@ export class ExpPhase extends PlayerPartyMemberPokemonPhase { super.start(); const pokemon = this.getPokemon(); - const exp = new Utils.NumberHolder(this.expValue); + const exp = new NumberHolder(this.expValue); globalScene.applyModifiers(ExpBoosterModifier, true, exp); exp.value = Math.floor(exp.value); globalScene.ui.showText( diff --git a/src/phases/form-change-phase.ts b/src/phases/form-change-phase.ts index e0ec4e87600..bf94284b117 100644 --- a/src/phases/form-change-phase.ts +++ b/src/phases/form-change-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import * as Utils from "../utils"; +import { fixedInt } from "#app/utils"; import { achvs } from "../system/achv"; import type { SpeciesFormChange } from "../data/pokemon-forms"; import { getSpeciesFormChangeMessage } from "../data/pokemon-forms"; @@ -9,7 +9,7 @@ import type PartyUiHandler from "../ui/party-ui-handler"; import { getPokemonNameWithAffix } from "../messages"; import { EndEvolutionPhase } from "./end-evolution-phase"; import { EvolutionPhase } from "./evolution-phase"; -import { BattlerTagType } from "#app/enums/battler-tag-type"; +import { BattlerTagType } from "#enums/battler-tag-type"; import { SpeciesFormKey } from "#enums/species-form-key"; export class FormChangePhase extends EvolutionPhase { @@ -151,9 +151,9 @@ export class FormChangePhase extends EvolutionPhase { () => this.end(), null, true, - Utils.fixedInt(delay), + fixedInt(delay), ); - globalScene.time.delayedCall(Utils.fixedInt(delay + 250), () => + globalScene.time.delayedCall(fixedInt(delay + 250), () => globalScene.playBgm(), ); }); diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts index f105b625cc8..1ccdc9c7106 100644 --- a/src/phases/game-over-phase.ts +++ b/src/phases/game-over-phase.ts @@ -20,7 +20,7 @@ import { UnlockPhase } from "#app/phases/unlock-phase"; import { achvs, ChallengeAchv } from "#app/system/achv"; import { Unlockables } from "#app/system/unlockables"; import { Mode } from "#app/ui/ui"; -import * as Utils from "#app/utils"; +import { isLocal, isLocalServerConnected } from "#app/utils"; import { PlayerGender } from "#enums/player-gender"; import { TrainerType } from "#enums/trainer-type"; import i18next from "i18next"; @@ -219,7 +219,7 @@ export class GameOverPhase extends BattlePhase { /* Added a local check to see if the game is running offline If Online, execute apiFetch as intended If Offline, execute offlineNewClear() only for victory, a localStorage implementation of newClear daily run checks */ - if (!Utils.isLocal || Utils.isLocalServerConnected) { + if (!isLocal || isLocalServerConnected) { pokerogueApi.savedata.session .newclear({ slot: globalScene.sessionSlotId, diff --git a/src/phases/login-phase.ts b/src/phases/login-phase.ts index 5cce6ca0298..846482ff726 100644 --- a/src/phases/login-phase.ts +++ b/src/phases/login-phase.ts @@ -5,26 +5,26 @@ import { Phase } from "#app/phase"; import { handleTutorial, Tutorial } from "#app/tutorial"; import { Mode } from "#app/ui/ui"; import i18next, { t } from "i18next"; -import * as Utils from "#app/utils"; +import { getCookie, sessionIdKey, executeIf, removeCookie } from "#app/utils"; import { SelectGenderPhase } from "./select-gender-phase"; import { UnavailablePhase } from "./unavailable-phase"; export class LoginPhase extends Phase { private showText: boolean; - constructor(showText?: boolean) { + constructor(showText = true) { super(); - this.showText = showText === undefined || !!showText; + this.showText = showText; } start(): void { super.start(); - const hasSession = !!Utils.getCookie(Utils.sessionIdKey); + const hasSession = !!getCookie(sessionIdKey); globalScene.ui.setMode(Mode.LOADING, { buttonActions: [] }); - Utils.executeIf(bypassLogin || hasSession, updateUserInfo).then(response => { + executeIf(bypassLogin || hasSession, updateUserInfo).then(response => { const success = response ? response[0] : false; const statusCode = response ? response[1] : null; if (!success) { @@ -38,7 +38,7 @@ export class LoginPhase extends Phase { const loadData = () => { updateUserInfo().then(success => { if (!success[0]) { - Utils.removeCookie(Utils.sessionIdKey); + removeCookie(sessionIdKey); globalScene.reset(true, true); return; } @@ -60,7 +60,7 @@ export class LoginPhase extends Phase { globalScene.ui.playSelect(); updateUserInfo().then(success => { if (!success[0]) { - Utils.removeCookie(Utils.sessionIdKey); + removeCookie(sessionIdKey); globalScene.reset(true, true); return; } @@ -89,7 +89,7 @@ export class LoginPhase extends Phase { ], }); } else if (statusCode === 401) { - Utils.removeCookie(Utils.sessionIdKey); + removeCookie(sessionIdKey); globalScene.reset(true, true); } else { globalScene.unshiftPhase(new UnavailablePhase()); diff --git a/src/phases/message-phase.ts b/src/phases/message-phase.ts index cff7249fcfa..f6777579857 100644 --- a/src/phases/message-phase.ts +++ b/src/phases/message-phase.ts @@ -3,9 +3,9 @@ import { Phase } from "#app/phase"; export class MessagePhase extends Phase { private text: string; - private callbackDelay: number | null; - private prompt: boolean | null; - private promptDelay: number | null; + private callbackDelay?: number | null; + private prompt?: boolean | null; + private promptDelay?: number | null; private speaker?: string; constructor( @@ -18,9 +18,9 @@ export class MessagePhase extends Phase { super(); this.text = text; - this.callbackDelay = callbackDelay!; // TODO: is this bang correct? - this.prompt = prompt!; // TODO: is this bang correct? - this.promptDelay = promptDelay!; // TODO: is this bang correct? + this.callbackDelay = callbackDelay; + this.prompt = prompt; + this.promptDelay = promptDelay; this.speaker = speaker; } diff --git a/src/phases/money-reward-phase.ts b/src/phases/money-reward-phase.ts index 56f46d25f77..ae8dc90616d 100644 --- a/src/phases/money-reward-phase.ts +++ b/src/phases/money-reward-phase.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import { MoneyMultiplierModifier } from "#app/modifier/modifier"; import i18next from "i18next"; -import * as Utils from "#app/utils"; +import { NumberHolder } from "#app/utils"; import { BattlePhase } from "./battle-phase"; export class MoneyRewardPhase extends BattlePhase { @@ -15,7 +15,7 @@ export class MoneyRewardPhase extends BattlePhase { } start() { - const moneyAmount = new Utils.NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); + const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); diff --git a/src/phases/move-anim-test-phase.ts b/src/phases/move-anim-test-phase.ts deleted file mode 100644 index e8b7c0c8fa7..00000000000 --- a/src/phases/move-anim-test-phase.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { globalScene } from "#app/global-scene"; -import { initMoveAnim, loadMoveAnimAssets, MoveAnim } from "#app/data/battle-anims"; -import { allMoves, SelfStatusMove } from "#app/data/moves/move"; -import { Moves } from "#app/enums/moves"; -import * as Utils from "#app/utils"; -import { BattlePhase } from "./battle-phase"; - -export class MoveAnimTestPhase extends BattlePhase { - private moveQueue: Moves[]; - - constructor(moveQueue?: Moves[]) { - super(); - - this.moveQueue = moveQueue || Utils.getEnumValues(Moves).slice(1); - } - - start() { - const moveQueue = this.moveQueue.slice(0); - this.playMoveAnim(moveQueue, true); - } - - playMoveAnim(moveQueue: Moves[], player: boolean) { - const moveId = player ? moveQueue[0] : moveQueue.shift(); - if (moveId === undefined) { - this.playMoveAnim(this.moveQueue.slice(0), true); - return; - } - if (player) { - console.log(Moves[moveId]); - } - - initMoveAnim(moveId).then(() => { - loadMoveAnimAssets([moveId], true).then(() => { - const user = player ? globalScene.getPlayerPokemon()! : globalScene.getEnemyPokemon()!; - const target = - player !== allMoves[moveId] instanceof SelfStatusMove - ? globalScene.getEnemyPokemon()! - : globalScene.getPlayerPokemon()!; - new MoveAnim(moveId, user, target.getBattlerIndex()).play(allMoves[moveId].hitsSubstitute(user, target), () => { - // TODO: are the bangs correct here? - if (player) { - this.playMoveAnim(moveQueue, false); - } else { - this.playMoveAnim(moveQueue, true); - } - }); - }); - }); - } -} diff --git a/src/phases/move-end-phase.ts b/src/phases/move-end-phase.ts index f3a40ce69bd..53856956401 100644 --- a/src/phases/move-end-phase.ts +++ b/src/phases/move-end-phase.ts @@ -5,7 +5,7 @@ import type { BattlerIndex } from "#app/battle"; export class MoveEndPhase extends PokemonPhase { private wasFollowUp: boolean; - constructor(battlerIndex: BattlerIndex, wasFollowUp: boolean = false) { + constructor(battlerIndex: BattlerIndex, wasFollowUp = false) { super(battlerIndex); this.wasFollowUp = wasFollowUp; } diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 33e772eb2ea..82b73f681a0 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -227,7 +227,7 @@ export class MovePhase extends BattlePhase { (!this.pokemon.randSeedInt(4) || Overrides.STATUS_ACTIVATION_OVERRIDE === true) && Overrides.STATUS_ACTIVATION_OVERRIDE !== false; break; - case StatusEffect.SLEEP: + case StatusEffect.SLEEP: { applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove()); const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0); applyAbAttrs( @@ -242,6 +242,7 @@ export class MovePhase extends BattlePhase { healed = this.pokemon.status.sleepTurnsRemaining <= 0; activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP); break; + } case StatusEffect.FREEZE: healed = !!this.move diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index eb187617e69..f42290ff872 100644 --- a/src/phases/mystery-encounter-phases.ts +++ b/src/phases/mystery-encounter-phases.ts @@ -26,8 +26,7 @@ import { TrainerSlot } from "#enums/trainer-slot"; import { IvScannerModifier } from "../modifier/modifier"; import { Phase } from "../phase"; import { Mode } from "../ui/ui"; -import * as Utils from "../utils"; -import { isNullOrUndefined } from "../utils"; +import { isNullOrUndefined, randSeedItem } from "#app/utils"; /** * Will handle (in order): @@ -387,7 +386,7 @@ export class MysteryEncounterBattlePhase extends Phase { const trainer = globalScene.currentBattle.trainer; let message: string; globalScene.executeWithSeedOffset( - () => (message = Utils.randSeedItem(encounterMessages)), + () => (message = randSeedItem(encounterMessages)), globalScene.currentBattle.mysteryEncounter?.getSeedOffset(), ); message = message!; // tell TS compiler it's defined now diff --git a/src/phases/new-biome-encounter-phase.ts b/src/phases/new-biome-encounter-phase.ts index bb1fe54fe9f..3449a562c4a 100644 --- a/src/phases/new-biome-encounter-phase.ts +++ b/src/phases/new-biome-encounter-phase.ts @@ -4,10 +4,6 @@ import { getRandomWeatherType } from "#app/data/weather"; import { NextEncounterPhase } from "./next-encounter-phase"; export class NewBiomeEncounterPhase extends NextEncounterPhase { - constructor() { - super(); - } - doEncounter(): void { globalScene.playBgm(undefined, true); diff --git a/src/phases/next-encounter-phase.ts b/src/phases/next-encounter-phase.ts index e53f775f083..e5e61312c3b 100644 --- a/src/phases/next-encounter-phase.ts +++ b/src/phases/next-encounter-phase.ts @@ -2,10 +2,6 @@ import { globalScene } from "#app/global-scene"; import { EncounterPhase } from "./encounter-phase"; export class NextEncounterPhase extends EncounterPhase { - constructor() { - super(); - } - start() { super.start(); } diff --git a/src/phases/party-heal-phase.ts b/src/phases/party-heal-phase.ts index a9b24309e24..137af9f3a2d 100644 --- a/src/phases/party-heal-phase.ts +++ b/src/phases/party-heal-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import * as Utils from "#app/utils"; +import { fixedInt } from "#app/utils"; import { BattlePhase } from "./battle-phase"; export class PartyHealPhase extends BattlePhase { @@ -28,7 +28,7 @@ export class PartyHealPhase extends BattlePhase { pokemon.updateInfo(true); } const healSong = globalScene.playSoundWithoutBgm("heal"); - globalScene.time.delayedCall(Utils.fixedInt(healSong.totalDuration * 1000), () => { + globalScene.time.delayedCall(fixedInt(healSong.totalDuration * 1000), () => { healSong.destroy(); if (this.resumeBgm && bgmPlaying) { globalScene.playBgm(); diff --git a/src/phases/pokemon-anim-phase.ts b/src/phases/pokemon-anim-phase.ts index b9c91508b5a..f0693a52aaa 100644 --- a/src/phases/pokemon-anim-phase.ts +++ b/src/phases/pokemon-anim-phase.ts @@ -8,18 +8,18 @@ import { Species } from "#enums/species"; export class PokemonAnimPhase extends BattlePhase { /** The type of animation to play in this phase */ - private key: PokemonAnimType; + protected key: PokemonAnimType; /** The Pokemon to which this animation applies */ - private pokemon: Pokemon; + protected pokemon: Pokemon; /** Any other field sprites affected by this animation */ - private fieldAssets: Phaser.GameObjects.Sprite[]; + protected fieldAssets: Phaser.GameObjects.Sprite[]; - constructor(key: PokemonAnimType, pokemon: Pokemon, fieldAssets?: Phaser.GameObjects.Sprite[]) { + constructor(key: PokemonAnimType, pokemon: Pokemon, fieldAssets: Phaser.GameObjects.Sprite[] = []) { super(); this.key = key; this.pokemon = pokemon; - this.fieldAssets = fieldAssets ?? []; + this.fieldAssets = fieldAssets; } start(): void { diff --git a/src/phases/pokemon-heal-phase.ts b/src/phases/pokemon-heal-phase.ts index ecfe99389eb..651c625b23a 100644 --- a/src/phases/pokemon-heal-phase.ts +++ b/src/phases/pokemon-heal-phase.ts @@ -8,7 +8,7 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { HealingBoosterModifier } from "#app/modifier/modifier"; import { HealAchv } from "#app/system/achv"; import i18next from "i18next"; -import * as Utils from "#app/utils"; +import { NumberHolder } from "#app/utils"; import { CommonAnimPhase } from "./common-anim-phase"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import type { HealBlockTag } from "#app/data/battler-tags"; @@ -72,11 +72,11 @@ export class PokemonHealPhase extends CommonAnimPhase { return super.end(); } if (healOrDamage) { - const hpRestoreMultiplier = new Utils.NumberHolder(1); + const hpRestoreMultiplier = new NumberHolder(1); if (!this.revive) { globalScene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier); } - const healAmount = new Utils.NumberHolder(Math.floor(this.hpHealed * hpRestoreMultiplier.value)); + const healAmount = new NumberHolder(Math.floor(this.hpHealed * hpRestoreMultiplier.value)); if (healAmount.value < 0) { pokemon.damageAndUpdate(healAmount.value * -1, { result: HitResult.INDIRECT }); healAmount.value = 0; diff --git a/src/phases/pokemon-phase.ts b/src/phases/pokemon-phase.ts index 3ca5f09f953..8c30512cdc4 100644 --- a/src/phases/pokemon-phase.ts +++ b/src/phases/pokemon-phase.ts @@ -11,11 +11,14 @@ export abstract class PokemonPhase extends FieldPhase { constructor(battlerIndex?: BattlerIndex | number) { super(); - if (battlerIndex === undefined) { - battlerIndex = globalScene + battlerIndex = + battlerIndex ?? + globalScene .getField() - .find(p => p?.isActive())! - .getBattlerIndex(); // TODO: is the bang correct here? + .find(p => p?.isActive())! // TODO: is the bang correct here? + .getBattlerIndex(); + if (battlerIndex === undefined) { + console.warn("There are no Pokemon on the field!"); // TODO: figure out a suitable fallback behavior } this.battlerIndex = battlerIndex; diff --git a/src/phases/post-game-over-phase.ts b/src/phases/post-game-over-phase.ts index f86ec8496e0..753251e992f 100644 --- a/src/phases/post-game-over-phase.ts +++ b/src/phases/post-game-over-phase.ts @@ -4,12 +4,12 @@ import type { EndCardPhase } from "./end-card-phase"; import { TitlePhase } from "./title-phase"; export class PostGameOverPhase extends Phase { - private endCardPhase: EndCardPhase | null; + private endCardPhase?: EndCardPhase; constructor(endCardPhase?: EndCardPhase) { super(); - this.endCardPhase = endCardPhase!; // TODO: is this bang correct? + this.endCardPhase = endCardPhase; } start() { diff --git a/src/phases/post-turn-status-effect-phase.ts b/src/phases/post-turn-status-effect-phase.ts index f6341666e7e..619ef22d01e 100644 --- a/src/phases/post-turn-status-effect-phase.ts +++ b/src/phases/post-turn-status-effect-phase.ts @@ -13,7 +13,7 @@ import { getStatusEffectActivationText } from "#app/data/status-effect"; import { BattleSpec } from "#app/enums/battle-spec"; import { StatusEffect } from "#app/enums/status-effect"; import { getPokemonNameWithAffix } from "#app/messages"; -import * as Utils from "#app/utils"; +import { BooleanHolder, NumberHolder } from "#app/utils"; import { PokemonPhase } from "./pokemon-phase"; export class PostTurnStatusEffectPhase extends PokemonPhase { @@ -26,7 +26,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { const pokemon = this.getPokemon(); if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn() && !pokemon.switchOutStatus) { pokemon.status.incrementTurn(); - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs(BlockStatusDamageAbAttr, pokemon, cancelled); @@ -34,7 +34,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { globalScene.queueMessage( getStatusEffectActivationText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)), ); - const damage = new Utils.NumberHolder(0); + const damage = new NumberHolder(0); switch (pokemon.status.effect) { case StatusEffect.POISON: damage.value = Math.max(pokemon.getMaxHp() >> 3, 1); diff --git a/src/phases/reload-session-phase.ts b/src/phases/reload-session-phase.ts index 3a4a4e0e3a5..a7ac0002b03 100644 --- a/src/phases/reload-session-phase.ts +++ b/src/phases/reload-session-phase.ts @@ -1,15 +1,15 @@ import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; import { Mode } from "#app/ui/ui"; -import * as Utils from "#app/utils"; +import { fixedInt } from "#app/utils"; export class ReloadSessionPhase extends Phase { - private systemDataStr: string | null; + private systemDataStr?: string; constructor(systemDataStr?: string) { super(); - this.systemDataStr = systemDataStr ?? null; + this.systemDataStr = systemDataStr; } start(): void { @@ -18,7 +18,7 @@ export class ReloadSessionPhase extends Phase { let delayElapsed = false; let loaded = false; - globalScene.time.delayedCall(Utils.fixedInt(1500), () => { + globalScene.time.delayedCall(fixedInt(1500), () => { if (loaded) { this.end(); } else { diff --git a/src/phases/scan-ivs-phase.ts b/src/phases/scan-ivs-phase.ts index 2a2d68591ca..aaeeb7f84f8 100644 --- a/src/phases/scan-ivs-phase.ts +++ b/src/phases/scan-ivs-phase.ts @@ -8,6 +8,7 @@ import i18next from "i18next"; import { PokemonPhase } from "./pokemon-phase"; export class ScanIvsPhase extends PokemonPhase { + // biome-ignore lint/complexity/noUselessConstructor: This changes `battlerIndex` to be required constructor(battlerIndex: BattlerIndex) { super(battlerIndex); } @@ -24,7 +25,8 @@ export class ScanIvsPhase extends PokemonPhase { const uiTheme = globalScene.uiTheme; // Assuming uiTheme is accessible for (let e = 0; e < enemyField.length; e++) { enemyIvs = enemyField[e].ivs; - const currentIvs = globalScene.gameData.dexData[enemyField[e].species.getRootSpeciesId()].ivs; // we are using getRootSpeciesId() here because we want to check against the baby form, not the mid form if it exists + // we are using getRootSpeciesId() here because we want to check against the baby form, not the mid form if it exists + const currentIvs = globalScene.gameData.dexData[enemyField[e].species.getRootSpeciesId()].ivs; statsContainer = enemyField[e].getBattleInfo().getStatsValueContainer().list as Phaser.GameObjects.Sprite[]; statsContainerLabels = statsContainer.filter(m => m.name.indexOf("icon_stat_label") >= 0); for (let s = 0; s < statsContainerLabels.length; s++) { diff --git a/src/phases/select-biome-phase.ts b/src/phases/select-biome-phase.ts index 6a11967832a..2d67cb87405 100644 --- a/src/phases/select-biome-phase.ts +++ b/src/phases/select-biome-phase.ts @@ -5,15 +5,11 @@ import { MoneyInterestModifier, MapModifier } from "#app/modifier/modifier"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { Mode } from "#app/ui/ui"; import { BattlePhase } from "./battle-phase"; -import * as Utils from "#app/utils"; +import { randSeedInt } from "#app/utils"; import { PartyHealPhase } from "./party-heal-phase"; import { SwitchBiomePhase } from "./switch-biome-phase"; export class SelectBiomePhase extends BattlePhase { - constructor() { - super(); - } - start() { super.start(); @@ -40,7 +36,7 @@ export class SelectBiomePhase extends BattlePhase { let biomes: Biome[] = []; globalScene.executeWithSeedOffset(() => { biomes = (biomeLinks[currentBiome] as (Biome | [Biome, number])[]) - .filter(b => !Array.isArray(b) || !Utils.randSeedInt(b[1])) + .filter(b => !Array.isArray(b) || !randSeedInt(b[1])) .map(b => (!Array.isArray(b) ? b : b[0])); }, globalScene.currentBattle.waveIndex); if (biomes.length > 1 && globalScene.findModifier(m => m instanceof MapModifier)) { @@ -51,7 +47,7 @@ export class SelectBiomePhase extends BattlePhase { ? [biomeLinks[currentBiome] as Biome] : (biomeLinks[currentBiome] as (Biome | [Biome, number])[]) ) - .filter((b, _i) => !Array.isArray(b) || !Utils.randSeedInt(b[1])) + .filter(b => !Array.isArray(b) || !randSeedInt(b[1])) .map(b => (Array.isArray(b) ? b[0] : b)); }, globalScene.currentBattle.waveIndex); const biomeSelectItems = biomeChoices.map(b => { @@ -70,7 +66,7 @@ export class SelectBiomePhase extends BattlePhase { delay: 1000, }); } else { - setNextBiome(biomes[Utils.randSeedInt(biomes.length)]); + setNextBiome(biomes[randSeedInt(biomes.length)]); } } else if (biomeLinks.hasOwnProperty(currentBiome)) { setNextBiome(biomeLinks[currentBiome] as Biome); diff --git a/src/phases/select-challenge-phase.ts b/src/phases/select-challenge-phase.ts index 2a6797d3556..5e6f20f93ee 100644 --- a/src/phases/select-challenge-phase.ts +++ b/src/phases/select-challenge-phase.ts @@ -3,10 +3,6 @@ import { Phase } from "#app/phase"; import { Mode } from "#app/ui/ui"; export class SelectChallengePhase extends Phase { - constructor() { - super(); - } - start() { super.start(); diff --git a/src/phases/select-gender-phase.ts b/src/phases/select-gender-phase.ts index 1c86536de53..4da60b38aa1 100644 --- a/src/phases/select-gender-phase.ts +++ b/src/phases/select-gender-phase.ts @@ -6,10 +6,6 @@ import { Mode } from "#app/ui/ui"; import i18next from "i18next"; export class SelectGenderPhase extends Phase { - constructor() { - super(); - } - start(): void { super.start(); diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 11d448876d3..27ab7e374a2 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -26,7 +26,6 @@ import { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler"; import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler"; import { Mode } from "#app/ui/ui"; import i18next from "i18next"; -import * as Utils from "#app/utils"; import { BattlePhase } from "./battle-phase"; import Overrides from "#app/overrides"; import type { CustomModifierSettings } from "#app/modifier/modifier-type"; @@ -67,7 +66,7 @@ export class SelectModifierPhase extends BattlePhase { if (!this.isCopy) { regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount); } - const modifierCount = new Utils.NumberHolder(3); + const modifierCount = new NumberHolder(3); if (this.isPlayer()) { globalScene.applyModifiers(ExtraModifierModifier, true, modifierCount); globalScene.applyModifiers(TempExtraModifierModifier, true, modifierCount); diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index b3ebe6731c9..c6ded6be7af 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -15,10 +15,6 @@ import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import * as Utils from "../utils"; export class SelectStarterPhase extends Phase { - constructor() { - super(); - } - start() { super.start(); diff --git a/src/phases/select-target-phase.ts b/src/phases/select-target-phase.ts index 2042d0a3fcf..035eaff41fa 100644 --- a/src/phases/select-target-phase.ts +++ b/src/phases/select-target-phase.ts @@ -8,6 +8,7 @@ import i18next from "#app/plugins/i18n"; import { allMoves } from "#app/data/moves/move"; export class SelectTargetPhase extends PokemonPhase { + // biome-ignore lint/complexity/noUselessConstructor: This makes `fieldIndex` required constructor(fieldIndex: number) { super(fieldIndex); } diff --git a/src/phases/shiny-sparkle-phase.ts b/src/phases/shiny-sparkle-phase.ts index 2540d98fb79..87a7db29cf6 100644 --- a/src/phases/shiny-sparkle-phase.ts +++ b/src/phases/shiny-sparkle-phase.ts @@ -3,6 +3,7 @@ import type { BattlerIndex } from "#app/battle"; import { PokemonPhase } from "./pokemon-phase"; export class ShinySparklePhase extends PokemonPhase { + // biome-ignore lint/complexity/noUselessConstructor: This makes `battlerIndex` required constructor(battlerIndex: BattlerIndex) { super(battlerIndex); } diff --git a/src/phases/show-party-exp-bar-phase.ts b/src/phases/show-party-exp-bar-phase.ts index 568b8b615c8..139f4efcc49 100644 --- a/src/phases/show-party-exp-bar-phase.ts +++ b/src/phases/show-party-exp-bar-phase.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import { ExpGainsSpeed } from "#app/enums/exp-gains-speed"; import { ExpNotification } from "#app/enums/exp-notification"; import { ExpBoosterModifier } from "#app/modifier/modifier"; -import * as Utils from "#app/utils"; +import { NumberHolder } from "#app/utils"; import { HidePartyExpBarPhase } from "./hide-party-exp-bar-phase"; import { LevelUpPhase } from "./level-up-phase"; import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; @@ -20,7 +20,7 @@ export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { super.start(); const pokemon = this.getPokemon(); - const exp = new Utils.NumberHolder(this.expValue); + const exp = new NumberHolder(this.expValue); globalScene.applyModifiers(ExpBoosterModifier, true, exp); exp.value = Math.floor(exp.value); diff --git a/src/phases/show-trainer-phase.ts b/src/phases/show-trainer-phase.ts index 740c11f5c5d..b6c1e345c70 100644 --- a/src/phases/show-trainer-phase.ts +++ b/src/phases/show-trainer-phase.ts @@ -3,10 +3,6 @@ import { PlayerGender } from "#app/enums/player-gender"; import { BattlePhase } from "./battle-phase"; export class ShowTrainerPhase extends BattlePhase { - constructor() { - super(); - } - start() { super.start(); diff --git a/src/phases/summon-missing-phase.ts b/src/phases/summon-missing-phase.ts index 32bc7495dce..a692455ce47 100644 --- a/src/phases/summon-missing-phase.ts +++ b/src/phases/summon-missing-phase.ts @@ -4,10 +4,6 @@ import { SummonPhase } from "./summon-phase"; import { globalScene } from "#app/global-scene"; export class SummonMissingPhase extends SummonPhase { - constructor(fieldIndex: number) { - super(fieldIndex); - } - preSummon(): void { globalScene.ui.showText( i18next.t("battle:sendOutPokemon", { diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index 16868bf9bc0..e0903ada275 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -23,11 +23,11 @@ export class SwitchSummonPhase extends SummonPhase { /** * Constructor for creating a new SwitchSummonPhase - * @param switchType the type of switch behavior - * @param fieldIndex integer representing position on the battle field - * @param slotIndex integer for the index of pokemon (in party of 6) to switch into - * @param doReturn boolean whether to render "comeback" dialogue - * @param player boolean if the switch is from the player + * @param switchType - The type of switch behavior + * @param fieldIndex - Position on the battle field + * @param slotIndex - The index of pokemon (in party of 6) to switch into + * @param doReturn - Whether to render "comeback" dialogue + * @param player - (Optional) `true` if the switch is from the player */ constructor(switchType: SwitchType, fieldIndex: number, slotIndex: number, doReturn: boolean, player?: boolean) { super(fieldIndex, player !== undefined ? player : true); diff --git a/src/phases/test-message-phase.ts b/src/phases/test-message-phase.ts deleted file mode 100644 index d5e74efd490..00000000000 --- a/src/phases/test-message-phase.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { MessagePhase } from "./message-phase"; - -export class TestMessagePhase extends MessagePhase { - constructor(message: string) { - super(message, null, true); - } -} diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index dc455a0a62a..108366d4774 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -18,7 +18,7 @@ import { vouchers } from "#app/system/voucher"; import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { SaveSlotUiMode } from "#app/ui/save-slot-select-ui-handler"; import { Mode } from "#app/ui/ui"; -import * as Utils from "#app/utils"; +import { isLocal, isLocalServerConnected, isNullOrUndefined } from "#app/utils"; import i18next from "i18next"; import { CheckSwitchPhase } from "./check-switch-phase"; import { EncounterPhase } from "./encounter-phase"; @@ -29,16 +29,10 @@ import { globalScene } from "#app/global-scene"; import Overrides from "#app/overrides"; export class TitlePhase extends Phase { - private loaded: boolean; + private loaded = false; private lastSessionData: SessionSaveData; public gameMode: GameModes; - constructor() { - super(); - - this.loaded = false; - } - start(): void { super.start(); @@ -282,7 +276,7 @@ export class TitlePhase extends Phase { }; // If Online, calls seed fetch from db to generate daily run. If Offline, generates a daily run based on current date. - if (!Utils.isLocal || Utils.isLocalServerConnected) { + if (!isLocal || isLocalServerConnected) { fetchDailyRunSeed() .then(seed => { if (seed) { @@ -296,7 +290,7 @@ export class TitlePhase extends Phase { }); } else { let seed: string = btoa(new Date().toISOString().substring(0, 10)); - if (!Utils.isNullOrUndefined(Overrides.DAILY_RUN_SEED_OVERRIDE)) { + if (!isNullOrUndefined(Overrides.DAILY_RUN_SEED_OVERRIDE)) { seed = Overrides.DAILY_RUN_SEED_OVERRIDE; } generateDaily(seed); diff --git a/src/phases/trainer-message-test-phase.ts b/src/phases/trainer-message-test-phase.ts deleted file mode 100644 index 23c2c86361c..00000000000 --- a/src/phases/trainer-message-test-phase.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { globalScene } from "#app/global-scene"; -import { trainerConfigs } from "#app/data/trainers/trainer-config"; -import type { TrainerType } from "#app/enums/trainer-type"; -import { BattlePhase } from "./battle-phase"; -import { TestMessagePhase } from "./test-message-phase"; - -export class TrainerMessageTestPhase extends BattlePhase { - private trainerTypes: TrainerType[]; - - constructor(...trainerTypes: TrainerType[]) { - super(); - - this.trainerTypes = trainerTypes; - } - - start() { - super.start(); - - const testMessages: string[] = []; - - for (const t of Object.keys(trainerConfigs)) { - const type = Number.parseInt(t); - if (this.trainerTypes.length && !this.trainerTypes.find(tt => tt === (type as TrainerType))) { - continue; - } - const config = trainerConfigs[type]; - [ - config.encounterMessages, - config.femaleEncounterMessages, - config.victoryMessages, - config.femaleVictoryMessages, - config.defeatMessages, - config.femaleDefeatMessages, - ].map(messages => { - if (messages?.length) { - testMessages.push(...messages); - } - }); - } - - for (const message of testMessages) { - globalScene.pushPhase(new TestMessagePhase(message)); - } - - this.end(); - } -} diff --git a/src/phases/trainer-victory-phase.ts b/src/phases/trainer-victory-phase.ts index a024885121f..637ddea8b56 100644 --- a/src/phases/trainer-victory-phase.ts +++ b/src/phases/trainer-victory-phase.ts @@ -3,7 +3,7 @@ import { TrainerType } from "#app/enums/trainer-type"; import { modifierTypes } from "#app/modifier/modifier-type"; import { vouchers } from "#app/system/voucher"; import i18next from "i18next"; -import * as Utils from "#app/utils"; +import { randSeedItem } from "#app/utils"; import { BattlePhase } from "./battle-phase"; import { ModifierRewardPhase } from "./modifier-reward-phase"; import { MoneyRewardPhase } from "./money-reward-phase"; @@ -14,10 +14,6 @@ import { achvs } from "#app/system/achv"; import { timedEventManager } from "#app/global-event-manager"; export class TrainerVictoryPhase extends BattlePhase { - constructor() { - super(); - } - start() { globalScene.disableMenu = true; @@ -82,7 +78,7 @@ export class TrainerVictoryPhase extends BattlePhase { const victoryMessages = globalScene.currentBattle.trainer?.getVictoryMessages()!; // TODO: is this bang correct? let message: string; globalScene.executeWithSeedOffset( - () => (message = Utils.randSeedItem(victoryMessages)), + () => (message = randSeedItem(victoryMessages)), globalScene.currentBattle.waveIndex, ); message = message!; // tell TS compiler it's defined now diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index ddfc0955508..9b84ea05e58 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -18,10 +18,6 @@ import { PokemonHealPhase } from "./pokemon-heal-phase"; import { globalScene } from "#app/global-scene"; export class TurnEndPhase extends FieldPhase { - constructor() { - super(); - } - start() { super.start(); diff --git a/src/phases/turn-init-phase.ts b/src/phases/turn-init-phase.ts index 3104b65eb3f..0c110024af7 100644 --- a/src/phases/turn-init-phase.ts +++ b/src/phases/turn-init-phase.ts @@ -15,10 +15,6 @@ import { TurnStartPhase } from "./turn-start-phase"; import { globalScene } from "#app/global-scene"; export class TurnInitPhase extends FieldPhase { - constructor() { - super(); - } - start() { super.start(); diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index 34dd7df3e89..d5b4160fe1b 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -6,7 +6,7 @@ import type Pokemon from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon"; import { BypassSpeedChanceModifier } from "#app/modifier/modifier"; import { Command } from "#app/ui/command-ui-handler"; -import * as Utils from "#app/utils"; +import { randSeedShuffle, BooleanHolder } from "#app/utils"; import { AttemptCapturePhase } from "./attempt-capture-phase"; import { AttemptRunPhase } from "./attempt-run-phase"; import { BerryPhase } from "./berry-phase"; @@ -24,10 +24,6 @@ import { globalScene } from "#app/global-scene"; import { TeraPhase } from "./tera-phase"; export class TurnStartPhase extends FieldPhase { - constructor() { - super(); - } - /** * This orders the active Pokemon on the field by speed into an BattlerIndex array and returns that array. * It also checks for Trick Room and reverses the array if it is present. @@ -43,14 +39,14 @@ export class TurnStartPhase extends FieldPhase { // was varying based on how long since you last reloaded globalScene.executeWithSeedOffset( () => { - orderedTargets = Utils.randSeedShuffle(orderedTargets); + orderedTargets = randSeedShuffle(orderedTargets); }, globalScene.currentBattle.turn, globalScene.waveSeed, ); // Next, a check for Trick Room is applied to determine sort order. - const speedReversed = new Utils.BooleanHolder(false); + const speedReversed = new BooleanHolder(false); globalScene.arena.applyTags(TrickRoomTag, false, speedReversed); // Adjust the sort function based on whether Trick Room is active. @@ -80,8 +76,8 @@ export class TurnStartPhase extends FieldPhase { .getField(true) .filter(p => p.summonData) .map(p => { - const bypassSpeed = new Utils.BooleanHolder(false); - const canCheckHeldItems = new Utils.BooleanHolder(true); + const bypassSpeed = new BooleanHolder(false); + const canCheckHeldItems = new BooleanHolder(true); applyAbAttrs(BypassSpeedChanceAbAttr, p, null, false, bypassSpeed); applyAbAttrs(PreventBypassSpeedChanceAbAttr, p, null, false, bypassSpeed, canCheckHeldItems); if (canCheckHeldItems.value) { diff --git a/src/phases/unavailable-phase.ts b/src/phases/unavailable-phase.ts index c0b5d4224c5..33042739971 100644 --- a/src/phases/unavailable-phase.ts +++ b/src/phases/unavailable-phase.ts @@ -4,10 +4,6 @@ import { Mode } from "#app/ui/ui"; import { LoginPhase } from "./login-phase"; export class UnavailablePhase extends Phase { - constructor() { - super(); - } - start(): void { globalScene.ui.setMode(Mode.UNAVAILABLE, () => { globalScene.unshiftPhase(new LoginPhase(true)); diff --git a/src/phases/weather-effect-phase.ts b/src/phases/weather-effect-phase.ts index d7a1f193029..5284c9fba85 100644 --- a/src/phases/weather-effect-phase.ts +++ b/src/phases/weather-effect-phase.ts @@ -15,7 +15,7 @@ import { BattlerTagType } from "#app/enums/battler-tag-type"; import { WeatherType } from "#app/enums/weather-type"; import type Pokemon from "#app/field/pokemon"; import { HitResult } from "#app/field/pokemon"; -import * as Utils from "#app/utils"; +import { BooleanHolder, toDmgValue } from "#app/utils"; import { CommonAnimPhase } from "./common-anim-phase"; export class WeatherEffectPhase extends CommonAnimPhase { @@ -35,14 +35,13 @@ export class WeatherEffectPhase extends CommonAnimPhase { this.weather = globalScene?.arena?.weather; if (!this.weather) { - this.end(); - return; + return this.end(); } this.setAnimation(CommonAnim.SUNNY + (this.weather.weatherType - 1)); if (this.weather.isDamaging()) { - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); this.executeForAll((pokemon: Pokemon) => applyPreWeatherEffectAbAttrs(SuppressWeatherEffectAbAttr, pokemon, this.weather, cancelled), @@ -50,7 +49,7 @@ export class WeatherEffectPhase extends CommonAnimPhase { if (!cancelled.value) { const inflictDamage = (pokemon: Pokemon) => { - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyPreWeatherEffectAbAttrs(PreWeatherDamageAbAttr, pokemon, this.weather, cancelled); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); @@ -63,9 +62,9 @@ export class WeatherEffectPhase extends CommonAnimPhase { return; } - const damage = Utils.toDmgValue(pokemon.getMaxHp() / 16); + const damage = toDmgValue(pokemon.getMaxHp() / 16); - globalScene.queueMessage(getWeatherDamageMessage(this.weather?.weatherType!, pokemon)!); // TODO: are those bangs correct? + globalScene.queueMessage(getWeatherDamageMessage(this.weather!.weatherType, pokemon) ?? ""); pokemon.damageAndUpdate(damage, { result: HitResult.INDIRECT, ignoreSegments: true }); }; From 0479b9dfcc951969fec368f938ac3419673536e6 Mon Sep 17 00:00:00 2001 From: Diogo Cruz Diniz <50275496+didas72@users.noreply.github.com> Date: Mon, 7 Apr 2025 14:50:52 +0100 Subject: [PATCH 17/33] Fix #2735: Hazard moves incorrectly require targets (#5635) * Fix #2735: Hazard moves incorrectly require targets Hazard moves should no longer require targets to successfully execute * Apply suggestions from code review made by NightKev Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/battle-anims.ts | 3 +- src/data/moves/move.ts | 4 +- src/phases/move-effect-phase.ts | 415 ++++++++++++++++---------------- src/phases/move-phase.ts | 6 +- test/moves/spikes.test.ts | 14 ++ 5 files changed, 236 insertions(+), 206 deletions(-) diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index 341976b388d..511c80bee72 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -1428,7 +1428,8 @@ export class MoveAnim extends BattleAnim { public move: Moves; constructor(move: Moves, user: Pokemon, target: BattlerIndex, playOnEmptyField = false) { - super(user, globalScene.getField()[target], playOnEmptyField); + // Set target to the user pokemon if no target is found to avoid crashes + super(user, globalScene.getField()[target] ?? user, playOnEmptyField); this.move = move; } diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 421314b1945..7a820d984d0 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -5928,7 +5928,7 @@ export class AddArenaTagAttr extends MoveEffectAttr { } if ((move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) { - const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + const side = ((this.selfSideTarget ? user : target).isPlayer() !== (move.hasAttr(AddArenaTrapTagAttr) && target === user)) ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; globalScene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, side); return true; } @@ -5977,7 +5977,7 @@ export class RemoveArenaTagsAttr extends MoveEffectAttr { export class AddArenaTrapTagAttr extends AddArenaTagAttr { getCondition(): MoveConditionFunc { return (user, target, move) => { - const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + const side = (this.selfSideTarget !== user.isPlayer()) ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER; const tag = globalScene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag; if (!tag) { return true; diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 7cc389651dd..6c46f7ff8c0 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -26,6 +26,7 @@ import { } from "#app/data/battler-tags"; import type { MoveAttr } from "#app/data/moves/move"; import { + AddArenaTrapTagAttr, applyFilteredMoveAttrs, applyMoveAttrs, AttackMove, @@ -209,12 +210,12 @@ export class MoveEffectPhase extends PokemonPhase { targets.some(t => t.hasAbilityWithAttr(ReflectStatusMoveAbAttr) || !!t.getTag(BattlerTagType.MAGIC_COAT)); /** - * If no targets are left for the move to hit (FAIL), or the invoked move is non-reflectable, single-target + * If no targets are left for the move to hit and it is not a hazard move (FAIL), or the invoked move is non-reflectable, single-target * (and not random target) and failed the hit check against its target (MISS), log the move * as FAILed or MISSed (depending on the conditions above) and end this phase. */ if ( - !hasActiveTargets || + (!hasActiveTargets && !move.hasAttr(AddArenaTrapTagAttr)) || (!mayBounce && !move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && @@ -239,18 +240,28 @@ export class MoveEffectPhase extends PokemonPhase { return this.end(); } - const playOnEmptyField = globalScene.currentBattle?.mysteryEncounter?.hasBattleAnimationsWithoutTargets ?? false; - // Move animation only needs one target - new MoveAnim(move.id as Moves, user, this.getFirstTarget()!.getBattlerIndex(), playOnEmptyField).play( - move.hitsSubstitute(user, this.getFirstTarget()!), - () => { - /** Has the move successfully hit a target (for damage) yet? */ - let hasHit = false; + const playOnEmptyField = + (globalScene.currentBattle?.mysteryEncounter?.hasBattleAnimationsWithoutTargets ?? false) || + (!hasActiveTargets && move.hasAttr(AddArenaTrapTagAttr)); + // Move animation only needs one target. The attacker is used as a fallback. + new MoveAnim( + move.id as Moves, + user, + this.getFirstTarget()?.getBattlerIndex() ?? BattlerIndex.ATTACKER, + playOnEmptyField, + ).play(move.hitsSubstitute(user, this.getFirstTarget()!), () => { + /** Has the move successfully hit a target (for damage) yet? */ + let hasHit = false; - // Prevent ENEMY_SIDE targeted moves from occurring twice in double battles - // and check which target will magic bounce. - const trueTargets: Pokemon[] = - move.moveTarget !== MoveTarget.ENEMY_SIDE + // Prevent ENEMY_SIDE targeted moves from occurring twice in double battles + // and check which target will magic bounce. + // In the event that the move is a hazard move, there may be no target and the move should still succeed. + // In this case, the user is used as the "target" to prevent a crash. + // This should not affect normal execution of the move otherwise. + const trueTargets: Pokemon[] = + !hasActiveTargets && move.hasAttr(AddArenaTrapTagAttr) + ? [user] + : move.moveTarget !== MoveTarget.ENEMY_SIDE ? targets : (() => { const magicCoatTargets = targets.filter( @@ -264,27 +275,27 @@ export class MoveEffectPhase extends PokemonPhase { return [magicCoatTargets[0]]; })(); - const queuedPhases: Phase[] = []; - for (const target of trueTargets) { - /** The {@linkcode ArenaTagSide} to which the target belongs */ - const targetSide = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - /** Has the invoked move been cancelled by conditional protection (e.g Quick Guard)? */ - const hasConditionalProtectApplied = new BooleanHolder(false); - /** Does the applied conditional protection bypass Protect-ignoring effects? */ - const bypassIgnoreProtect = new BooleanHolder(false); - /** If the move is not targeting a Pokemon on the user's side, try to apply conditional protection effects */ - if (!this.move.getMove().isAllyTarget()) { - globalScene.arena.applyTagsForSide( - ConditionalProtectTag, - targetSide, - false, - hasConditionalProtectApplied, - user, - target, - move.id, - bypassIgnoreProtect, - ); - } + const queuedPhases: Phase[] = []; + for (const target of trueTargets) { + /** The {@linkcode ArenaTagSide} to which the target belongs */ + const targetSide = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + /** Has the invoked move been cancelled by conditional protection (e.g Quick Guard)? */ + const hasConditionalProtectApplied = new BooleanHolder(false); + /** Does the applied conditional protection bypass Protect-ignoring effects? */ + const bypassIgnoreProtect = new BooleanHolder(false); + /** If the move is not targeting a Pokemon on the user's side, try to apply conditional protection effects */ + if (!this.move.getMove().isAllyTarget()) { + globalScene.arena.applyTagsForSide( + ConditionalProtectTag, + targetSide, + false, + hasConditionalProtectApplied, + user, + target, + move.id, + bypassIgnoreProtect, + ); + } /** Is the target protected by Protect, etc. or a relevant conditional protection effect? */ const isProtected = @@ -297,13 +308,13 @@ export class MoveEffectPhase extends PokemonPhase { (this.move.getMove().category !== MoveCategory.STATUS && target.findTags(t => t instanceof DamageProtectedTag).find(t => target.lapseTag(t.tagType)))); - /** Is the target hidden by the effects of its Commander ability? */ - const isCommanding = - globalScene.currentBattle.double && - target.getAlly()?.getTag(BattlerTagType.COMMANDED)?.getSourcePokemon() === target; + /** Is the target hidden by the effects of its Commander ability? */ + const isCommanding = + globalScene.currentBattle.double && + target.getAlly()?.getTag(BattlerTagType.COMMANDED)?.getSourcePokemon() === target; - /** Is the target reflecting status moves from the magic coat move? */ - const isReflecting = !!target.getTag(BattlerTagType.MAGIC_COAT); + /** Is the target reflecting status moves from the magic coat move? */ + const isReflecting = !!target.getTag(BattlerTagType.MAGIC_COAT); /** Is the target's magic bounce ability not ignored and able to reflect this move? */ const canMagicBounce = @@ -311,16 +322,16 @@ export class MoveEffectPhase extends PokemonPhase { !move.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user, target }) && target.hasAbilityWithAttr(ReflectStatusMoveAbAttr); - const semiInvulnerableTag = target.getTag(SemiInvulnerableTag); + const semiInvulnerableTag = target.getTag(SemiInvulnerableTag); - /** Is the target reflecting the effect, not protected, and not in an semi-invulnerable state?*/ - const willBounce = - !isProtected && - !this.reflected && - !isCommanding && - move.hasFlag(MoveFlags.REFLECTABLE) && - (isReflecting || canMagicBounce) && - !semiInvulnerableTag; + /** Is the target reflecting the effect, not protected, and not in an semi-invulnerable state?*/ + const willBounce = + !isProtected && + !this.reflected && + !isCommanding && + move.hasFlag(MoveFlags.REFLECTABLE) && + (isReflecting || canMagicBounce) && + !semiInvulnerableTag; // If the move will bounce, then queue the bounce and move on to the next target if (!target.switchOutStatus && willBounce) { @@ -338,171 +349,171 @@ export class MoveEffectPhase extends PokemonPhase { queuedPhases.push(new HideAbilityPhase()); } - queuedPhases.push( - new MovePhase(target, newTargets, new PokemonMove(move.id, 0, 0, true), true, true, true), + queuedPhases.push(new MovePhase(target, newTargets, new PokemonMove(move.id, 0, 0, true), true, true, true)); + continue; + } + + /** Is the pokemon immune due to an ablility, and also not in a semi invulnerable state? */ + const isImmune = + target.hasAbilityWithAttr(TypeImmunityAbAttr) && + target.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move) && + !semiInvulnerableTag; + + /** + * If the move missed a target, stop all future hits against that target + * and move on to the next target (if there is one). + */ + if ( + target.switchOutStatus || + isCommanding || + (!isImmune && + !isProtected && + !targetHitChecks[target.getBattlerIndex()] && + !move.hasAttr(AddArenaTrapTagAttr)) + ) { + this.stopMultiHit(target); + if (!target.switchOutStatus) { + globalScene.queueMessage( + i18next.t("battle:attackMissed", { + pokemonNameWithAffix: getPokemonNameWithAffix(target), + }), ); - continue; } - - /** Is the pokemon immune due to an ablility, and also not in a semi invulnerable state? */ - const isImmune = - target.hasAbilityWithAttr(TypeImmunityAbAttr) && - target.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move) && - !semiInvulnerableTag; - - /** - * If the move missed a target, stop all future hits against that target - * and move on to the next target (if there is one). - */ - if ( - target.switchOutStatus || - isCommanding || - (!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()]) - ) { - this.stopMultiHit(target); - if (!target.switchOutStatus) { - globalScene.queueMessage( - i18next.t("battle:attackMissed", { - pokemonNameWithAffix: getPokemonNameWithAffix(target), - }), - ); - } - if (moveHistoryEntry.result === MoveResult.PENDING) { - moveHistoryEntry.result = MoveResult.MISS; - } - user.pushMoveHistory(moveHistoryEntry); - applyMoveAttrs(MissEffectAttr, user, null, move); - continue; - } - - /** Does this phase represent the invoked move's first strike? */ - const firstHit = user.turnData.hitsLeft === user.turnData.hitCount; - - // Only log the move's result on the first strike - if (firstHit) { - user.pushMoveHistory(moveHistoryEntry); - } - - /** - * Since all fail/miss checks have applied, the move is considered successfully applied. - * It's worth noting that if the move has no effect or is protected against, this assignment - * is overwritten and the move is logged as a FAIL. - */ - moveHistoryEntry.result = MoveResult.SUCCESS; - - /** - * Stores the result of applying the invoked move to the target. - * If the target is protected, the result is always `NO_EFFECT`. - * Otherwise, the hit result is based on type effectiveness, immunities, - * and other factors that may negate the attack or status application. - * - * Internally, the call to {@linkcode Pokemon.apply} is where damage is calculated - * (for attack moves) and the target's HP is updated. However, this isn't - * made visible to the user until the resulting {@linkcode DamagePhase} - * is invoked. - */ - const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT; - - /** Does {@linkcode hitResult} indicate that damage was dealt to the target? */ - const dealsDamage = [ - HitResult.EFFECTIVE, - HitResult.SUPER_EFFECTIVE, - HitResult.NOT_VERY_EFFECTIVE, - HitResult.ONE_HIT_KO, - ].includes(hitResult); - - /** Is this target the first one hit by the move on its current strike? */ - const firstTarget = dealsDamage && !hasHit; - if (firstTarget) { - hasHit = true; - } - - /** - * If the move has no effect on the target (i.e. the target is protected or immune), - * change the logged move result to FAIL. - */ - if (hitResult === HitResult.NO_EFFECT) { - moveHistoryEntry.result = MoveResult.FAIL; - } - - /** Does this phase represent the invoked move's last strike? */ - const lastHit = user.turnData.hitsLeft === 1 || !this.getFirstTarget()?.isActive(); - - /** - * If the user can change forms by using the invoked move, - * it only changes forms after the move's last hit - * (see Relic Song's interaction with Parental Bond when used by Meloetta). - */ - if (lastHit) { - globalScene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); - /** - * Multi-Lens, Multi Hit move and Parental Bond check for PostDamageAbAttr - * other damage source are calculated in damageAndUpdate in pokemon.ts - */ - if (user.turnData.hitCount > 1) { - applyPostDamageAbAttrs(PostDamageAbAttr, target, 0, target.hasPassive(), false, [], user); - } - } - - applyFilteredMoveAttrs( - (attr: MoveAttr) => - attr instanceof MoveEffectAttr && - attr.trigger === MoveEffectTrigger.PRE_APPLY && - (!attr.firstHitOnly || firstHit) && - (!attr.lastHitOnly || lastHit) && - hitResult !== HitResult.NO_EFFECT, - user, - target, - move, - ); - - if (hitResult !== HitResult.FAIL) { - this.applySelfTargetEffects(user, target, firstHit, lastHit); - - if (hitResult !== HitResult.NO_EFFECT) { - this.applyPostApplyEffects(user, target, firstHit, lastHit); - this.applyHeldItemFlinchCheck(user, target, dealsDamage); - this.applySuccessfulAttackEffects(user, target, firstHit, lastHit, !!isProtected, hitResult, firstTarget); - } else { - applyMoveAttrs(NoEffectAttr, user, null, move); - } + if (moveHistoryEntry.result === MoveResult.PENDING) { + moveHistoryEntry.result = MoveResult.MISS; } + user.pushMoveHistory(moveHistoryEntry); + applyMoveAttrs(MissEffectAttr, user, null, move); + continue; } - // Apply queued phases - if (queuedPhases.length) { - globalScene.appendToPhase(queuedPhases, MoveEndPhase); - } - // Apply the move's POST_TARGET effects on the move's last hit, after all targeted effects have resolved - if (user.turnData.hitsLeft === 1 || !this.getFirstTarget()?.isActive()) { - applyFilteredMoveAttrs( - (attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET, - user, - null, - move, - ); + /** Does this phase represent the invoked move's first strike? */ + const firstHit = user.turnData.hitsLeft === user.turnData.hitCount; + + // Only log the move's result on the first strike + if (firstHit) { + user.pushMoveHistory(moveHistoryEntry); } /** - * Remove the target's substitute (if it exists and has expired) - * after all targeted effects have applied. - * This prevents blocked effects from applying until after this hit resolves. + * Since all fail/miss checks have applied, the move is considered successfully applied. + * It's worth noting that if the move has no effect or is protected against, this assignment + * is overwritten and the move is logged as a FAIL. */ - targets.forEach(target => { - const substitute = target.getTag(SubstituteTag); - if (substitute && substitute.hp <= 0) { - target.lapseTag(BattlerTagType.SUBSTITUTE); - } - }); + moveHistoryEntry.result = MoveResult.SUCCESS; - const moveType = user.getMoveType(move, true); - if (move.category !== MoveCategory.STATUS && !user.stellarTypesBoosted.includes(moveType)) { - user.stellarTypesBoosted.push(moveType); + /** + * Stores the result of applying the invoked move to the target. + * If the target is protected, the result is always `NO_EFFECT`. + * Otherwise, the hit result is based on type effectiveness, immunities, + * and other factors that may negate the attack or status application. + * + * Internally, the call to {@linkcode Pokemon.apply} is where damage is calculated + * (for attack moves) and the target's HP is updated. However, this isn't + * made visible to the user until the resulting {@linkcode DamagePhase} + * is invoked. + */ + const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT; + + /** Does {@linkcode hitResult} indicate that damage was dealt to the target? */ + const dealsDamage = [ + HitResult.EFFECTIVE, + HitResult.SUPER_EFFECTIVE, + HitResult.NOT_VERY_EFFECTIVE, + HitResult.ONE_HIT_KO, + ].includes(hitResult); + + /** Is this target the first one hit by the move on its current strike? */ + const firstTarget = dealsDamage && !hasHit; + if (firstTarget) { + hasHit = true; } - this.end(); - }, - ); + /** + * If the move has no effect on the target (i.e. the target is protected or immune), + * change the logged move result to FAIL. + */ + if (hitResult === HitResult.NO_EFFECT) { + moveHistoryEntry.result = MoveResult.FAIL; + } + + /** Does this phase represent the invoked move's last strike? */ + const lastHit = user.turnData.hitsLeft === 1 || !this.getFirstTarget()?.isActive(); + + /** + * If the user can change forms by using the invoked move, + * it only changes forms after the move's last hit + * (see Relic Song's interaction with Parental Bond when used by Meloetta). + */ + if (lastHit) { + globalScene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); + /** + * Multi-Lens, Multi Hit move and Parental Bond check for PostDamageAbAttr + * other damage source are calculated in damageAndUpdate in pokemon.ts + */ + if (user.turnData.hitCount > 1) { + applyPostDamageAbAttrs(PostDamageAbAttr, target, 0, target.hasPassive(), false, [], user); + } + } + + applyFilteredMoveAttrs( + (attr: MoveAttr) => + attr instanceof MoveEffectAttr && + attr.trigger === MoveEffectTrigger.PRE_APPLY && + (!attr.firstHitOnly || firstHit) && + (!attr.lastHitOnly || lastHit) && + hitResult !== HitResult.NO_EFFECT, + user, + target, + move, + ); + + if (hitResult !== HitResult.FAIL) { + this.applySelfTargetEffects(user, target, firstHit, lastHit); + + if (hitResult !== HitResult.NO_EFFECT) { + this.applyPostApplyEffects(user, target, firstHit, lastHit); + this.applyHeldItemFlinchCheck(user, target, dealsDamage); + this.applySuccessfulAttackEffects(user, target, firstHit, lastHit, !!isProtected, hitResult, firstTarget); + } else { + applyMoveAttrs(NoEffectAttr, user, null, move); + } + } + } + + // Apply queued phases + if (queuedPhases.length) { + globalScene.appendToPhase(queuedPhases, MoveEndPhase); + } + // Apply the move's POST_TARGET effects on the move's last hit, after all targeted effects have resolved + if (user.turnData.hitsLeft === 1 || !this.getFirstTarget()?.isActive()) { + applyFilteredMoveAttrs( + (attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET, + user, + null, + move, + ); + } + + /** + * Remove the target's substitute (if it exists and has expired) + * after all targeted effects have applied. + * This prevents blocked effects from applying until after this hit resolves. + */ + targets.forEach(target => { + const substitute = target.getTag(SubstituteTag); + if (substitute && substitute.hp <= 0) { + target.lapseTag(BattlerTagType.SUBSTITUTE); + } + }); + + const moveType = user.getMoveType(move, true); + if (move.category !== MoveCategory.STATUS && !user.stellarTypesBoosted.includes(moveType)) { + user.stellarTypesBoosted.push(moveType); + } + + this.end(); + }); } public override end(): void { diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 82b73f681a0..5232dfee8ba 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -15,6 +15,7 @@ import type { DelayedAttackTag } from "#app/data/arena-tag"; import { CommonAnim } from "#app/data/battle-anims"; import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags"; import { + AddArenaTrapTagAttr, allMoves, applyMoveAttrs, BypassRedirectAttr, @@ -201,7 +202,10 @@ export class MovePhase extends BattlePhase { const targets = this.getActiveTargetPokemon(); const moveQueue = this.pokemon.getMoveQueue(); - if (targets.length === 0 || (moveQueue.length && moveQueue[0].move === Moves.NONE)) { + if ( + (targets.length === 0 && !this.move.getMove().hasAttr(AddArenaTrapTagAttr)) || + (moveQueue.length && moveQueue[0].move === Moves.NONE) + ) { this.showMoveText(); this.showFailedText(); this.cancel(); diff --git a/test/moves/spikes.test.ts b/test/moves/spikes.test.ts index 9bf0e5e1437..76af15777bb 100644 --- a/test/moves/spikes.test.ts +++ b/test/moves/spikes.test.ts @@ -4,6 +4,7 @@ import { Species } from "#enums/species"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; describe("Moves - Spikes", () => { let phaserGame: Phaser.Game; @@ -77,4 +78,17 @@ describe("Moves - Spikes", () => { const enemy = game.scene.getEnemyParty()[0]; expect(enemy.hp).toBeLessThan(enemy.getMaxHp()); }, 20000); + + it("should work when all targets fainted", async () => { + game.override.enemySpecies(Species.DIGLETT); + game.override.battleType("double"); + game.override.startingLevel(50); + await game.classicMode.startBattle([Species.RAYQUAZA, Species.ROWLET]); + + game.move.select(Moves.EARTHQUAKE); + game.move.select(Moves.SPIKES, 1); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(game.scene.arena.getTagOnSide(ArenaTrapTag, ArenaTagSide.ENEMY)).toBeDefined(); + }, 20000); }); From 1b79d1f832b2163b265beb4bff2ec0ae45a0fb28 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Mon, 7 Apr 2025 15:53:35 -0700 Subject: [PATCH 18/33] [Refactor] Re-implement save migration system (#5634) --- package-lock.json | 7 + package.json | 1 + src/@types/SessionSaveMigrator.ts | 6 + src/@types/SettingsSaveMigrator.ts | 5 + src/@types/SystemSaveMigrator.ts | 6 + .../version_migration/version_converter.ts | 273 +++++++----------- .../version_migration/versions/v1_0_4.ts | 104 ++++--- .../version_migration/versions/v1_1_0.ts | 5 - .../version_migration/versions/v1_7_0.ts | 36 ++- .../version_migration/versions/v1_8_3.ts | 22 +- 10 files changed, 227 insertions(+), 238 deletions(-) create mode 100644 src/@types/SessionSaveMigrator.ts create mode 100644 src/@types/SettingsSaveMigrator.ts create mode 100644 src/@types/SystemSaveMigrator.ts delete mode 100644 src/system/version_migration/versions/v1_1_0.ts diff --git a/package-lock.json b/package-lock.json index 33e9dd08104..6b880370f0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "hasInstallScript": true, "dependencies": { "@material/material-color-utilities": "^0.2.7", + "compare-versions": "^6.1.1", "crypto-js": "^4.2.0", "i18next": "^24.2.2", "i18next-browser-languagedetector": "^8.0.4", @@ -3605,6 +3606,12 @@ "node": ">=18" } }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", diff --git a/package.json b/package.json index 6b1c73db158..c84e926fc35 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ }, "dependencies": { "@material/material-color-utilities": "^0.2.7", + "compare-versions": "^6.1.1", "crypto-js": "^4.2.0", "i18next": "^24.2.2", "i18next-browser-languagedetector": "^8.0.4", diff --git a/src/@types/SessionSaveMigrator.ts b/src/@types/SessionSaveMigrator.ts new file mode 100644 index 00000000000..c4b0ad8dda4 --- /dev/null +++ b/src/@types/SessionSaveMigrator.ts @@ -0,0 +1,6 @@ +import type { SessionSaveData } from "#app/system/game-data"; + +export interface SessionSaveMigrator { + version: string; + migrate: (data: SessionSaveData) => void; +} diff --git a/src/@types/SettingsSaveMigrator.ts b/src/@types/SettingsSaveMigrator.ts new file mode 100644 index 00000000000..aae3df7cc60 --- /dev/null +++ b/src/@types/SettingsSaveMigrator.ts @@ -0,0 +1,5 @@ +export interface SettingsSaveMigrator { + version: string; + // biome-ignore lint/complexity/noBannedTypes: TODO - refactor settings + migrate: (data: Object) => void; +} diff --git a/src/@types/SystemSaveMigrator.ts b/src/@types/SystemSaveMigrator.ts new file mode 100644 index 00000000000..a22b5f6c93d --- /dev/null +++ b/src/@types/SystemSaveMigrator.ts @@ -0,0 +1,6 @@ +import type { SystemSaveData } from "#app/system/game-data"; + +export interface SystemSaveMigrator { + version: string; + migrate: (data: SystemSaveData) => void; +} diff --git a/src/system/version_migration/version_converter.ts b/src/system/version_migration/version_converter.ts index 074f60c2c5d..4b712609819 100644 --- a/src/system/version_migration/version_converter.ts +++ b/src/system/version_migration/version_converter.ts @@ -1,19 +1,104 @@ -import type { SessionSaveData, SystemSaveData } from "../game-data"; +import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator"; +import type { SettingsSaveMigrator } from "#app/@types/SettingsSaveMigrator"; +import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; +import type { SessionSaveData, SystemSaveData } from "#app/system/game-data"; +import { compareVersions } from "compare-versions"; import { version } from "../../../package.json"; +/* +// template for save migrator creation +// versions/vA_B_C.ts + +// The version for each migrator should match the filename, ie: `vA_B_C.ts` -> `version: "A.B.C" +// This is the target version (aka the version we're ending up on after the migrators are run) + +// The name for each migrator should match its purpose. For example, if you're fixing +// the ability index of a pokemon, it might be called `migratePokemonAbilityIndex` + +const systemMigratorA: SystemSaveMigrator = { + version: "A.B.C", + migrate: (data: SystemSaveData): void => { + // migration code goes here + }, +}; + +export const systemMigrators: Readonly = [systemMigratorA] as const; + +const sessionMigratorA: SessionSaveMigrator = { + version: "A.B.C", + migrate: (data: SessionSaveData): void => { + // migration code goes here + }, +}; + +export const sessionMigrators: Readonly = [sessionMigratorA] as const; + +const settingsMigratorA: SettingsSaveMigrator = { + version: "A.B.C", + // biome-ignore lint/complexity/noBannedTypes: TODO - refactor settings + migrate: (data: Object): void => { + // migration code goes here + }, +}; + +export const settingsMigrators: Readonly = [settingsMigratorA] as const; +*/ + +// --- vA.B.C PATCHES --- // +// import * as vA_B_C from "./versions/vA_B_C"; + // --- v1.0.4 (and below) PATCHES --- // import * as v1_0_4 from "./versions/v1_0_4"; -// --- v1.1.0 PATCHES --- // -import * as v1_1_0 from "./versions/v1_1_0"; - // --- v1.7.0 PATCHES --- // import * as v1_7_0 from "./versions/v1_7_0"; // --- v1.8.3 PATCHES --- // import * as v1_8_3 from "./versions/v1_8_3"; -const LATEST_VERSION = version.split(".").map(value => Number.parseInt(value)); +/** Current game version */ +const LATEST_VERSION = version; + +type SaveMigrator = SystemSaveMigrator | SessionSaveMigrator | SettingsSaveMigrator; + +// biome-ignore lint/complexity/noBannedTypes: TODO - refactor settings +type SaveData = SystemSaveData | SessionSaveData | Object; + +// To add a new set of migrators, create a new `.push()` line like so: +// `systemMigrators.push(...vA_B_C.systemMigrators);` + +/** All system save migrators */ +const systemMigrators: SystemSaveMigrator[] = []; +systemMigrators.push(...v1_0_4.systemMigrators); +systemMigrators.push(...v1_7_0.systemMigrators); +systemMigrators.push(...v1_8_3.systemMigrators); + +/** All session save migrators */ +const sessionMigrators: SessionSaveMigrator[] = []; +sessionMigrators.push(...v1_0_4.sessionMigrators); +sessionMigrators.push(...v1_7_0.sessionMigrators); + +/** All settings migrators */ +const settingsMigrators: SettingsSaveMigrator[] = []; +settingsMigrators.push(...v1_0_4.settingsMigrators); + +/** Sorts migrators by their stated version, ensuring they are applied in order from oldest to newest */ +const sortMigrators = (migrators: SaveMigrator[]): void => { + migrators.sort((a, b) => compareVersions(a.version, b.version)); +}; + +sortMigrators(systemMigrators); +sortMigrators(sessionMigrators); +sortMigrators(settingsMigrators); + +const applyMigrators = (migrators: readonly SaveMigrator[], data: SaveData, saveVersion: string) => { + for (const migrator of migrators) { + const isMigratorVersionHigher = compareVersions(saveVersion, migrator.version) === -1; + if (isMigratorVersionHigher) { + migrator.migrate(data as any); + } + } +}; /** * Converts incoming {@linkcode SystemSaveData} that has a version below the @@ -26,12 +111,12 @@ const LATEST_VERSION = version.split(".").map(value => Number.parseInt(value)); * @see {@link SystemVersionConverter} */ export function applySystemVersionMigration(data: SystemSaveData) { - const curVersion = data.gameVersion.split(".").map(value => Number.parseInt(value)); + const prevVersion = data.gameVersion; + const isCurrentVersionHigher = compareVersions(prevVersion, LATEST_VERSION) === -1; - if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) { - const converter = new SystemVersionConverter(); - converter.applyStaticPreprocessors(data); - converter.applyMigration(data, curVersion); + if (isCurrentVersionHigher) { + applyMigrators(systemMigrators, data, prevVersion); + console.log(`System data successfully migrated to v${LATEST_VERSION}!`); } } @@ -46,12 +131,15 @@ export function applySystemVersionMigration(data: SystemSaveData) { * @see {@link SessionVersionConverter} */ export function applySessionVersionMigration(data: SessionSaveData) { - const curVersion = data.gameVersion.split(".").map(value => Number.parseInt(value)); + const prevVersion = data.gameVersion; + const isCurrentVersionHigher = compareVersions(prevVersion, LATEST_VERSION) === -1; - if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) { - const converter = new SessionVersionConverter(); - converter.applyStaticPreprocessors(data); - converter.applyMigration(data, curVersion); + if (isCurrentVersionHigher) { + // Always sanitize money as a safeguard + data.money = Math.floor(data.money); + + applyMigrators(sessionMigrators, data, prevVersion); + console.log(`Session data successfully migrated to v${LATEST_VERSION}!`); } } @@ -65,156 +153,13 @@ export function applySessionVersionMigration(data: SessionSaveData) { * @param data Settings data object * @see {@link SettingsVersionConverter} */ +// biome-ignore lint/complexity/noBannedTypes: TODO - refactor settings export function applySettingsVersionMigration(data: Object) { - const gameVersion: string = data.hasOwnProperty("gameVersion") ? data["gameVersion"] : "1.0.0"; - const curVersion = gameVersion.split(".").map(value => Number.parseInt(value)); + const prevVersion: string = data.hasOwnProperty("gameVersion") ? data["gameVersion"] : "1.0.0"; + const isCurrentVersionHigher = compareVersions(prevVersion, LATEST_VERSION) === -1; - if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) { - const converter = new SettingsVersionConverter(); - converter.applyStaticPreprocessors(data); - converter.applyMigration(data, curVersion); - } -} - -/** - * Abstract class encapsulating the logic for migrating data from a given version up to - * the current version listed in `package.json`. - * - * Note that, for any version converter, the corresponding `applyMigration` - * function would only need to be changed once when the first migration for a - * given version is introduced. Similarly, a version file (within the `versions` - * folder) would only need to be created for a version once with the appropriate - * array nomenclature. - */ -abstract class VersionConverter { - /** - * Iterates through an array of designated migration functions that are each - * called one by one to transform the data. - * @param data The data to be operated on - * @param migrationArr An array of functions that will transform the incoming data - */ - callMigrators(data: any, migrationArr: readonly any[]) { - for (const migrate of migrationArr) { - migrate(data); - } - } - - /** - * Applies any version-agnostic data sanitation as defined within the function - * body. - * @param data The data to be operated on - */ - applyStaticPreprocessors(_data: any): void {} - - /** - * Uses the current version the incoming data to determine the starting point - * of the migration which will cascade up to the latest version, calling the - * necessary migration functions in the process. - * @param data The data to be operated on - * @param curVersion [0] Current major version - * [1] Current minor version - * [2] Current patch version - */ - abstract applyMigration(data: any, curVersion: number[]): void; -} - -/** - * Class encapsulating the logic for migrating {@linkcode SessionSaveData} from - * a given version up to the current version listed in `package.json`. - * @extends VersionConverter - */ -class SessionVersionConverter extends VersionConverter { - override applyStaticPreprocessors(data: SessionSaveData): void { - // Always sanitize money as a safeguard - data.money = Math.floor(data.money); - } - - override applyMigration(data: SessionSaveData, curVersion: number[]): void { - const [curMajor, curMinor, curPatch] = curVersion; - - if (curMajor === 1) { - if (curMinor === 0) { - if (curPatch <= 5) { - console.log("Applying v1.0.4 session data migration!"); - this.callMigrators(data, v1_0_4.sessionMigrators); - } - } - if (curMinor <= 1) { - console.log("Applying v1.1.0 session data migration!"); - this.callMigrators(data, v1_1_0.sessionMigrators); - } - if (curMinor < 7) { - console.log("Applying v1.7.0 session data migration!"); - this.callMigrators(data, v1_7_0.sessionMigrators); - } - } - - console.log(`Session data successfully migrated to v${version}!`); - } -} - -/** - * Class encapsulating the logic for migrating {@linkcode SystemSaveData} from - * a given version up to the current version listed in `package.json`. - * @extends VersionConverter - */ -class SystemVersionConverter extends VersionConverter { - override applyMigration(data: SystemSaveData, curVersion: number[]): void { - const [curMajor, curMinor, curPatch] = curVersion; - - if (curMajor === 1) { - if (curMinor === 0) { - if (curPatch <= 4) { - console.log("Applying v1.0.4 system data migraton!"); - this.callMigrators(data, v1_0_4.systemMigrators); - } - } - if (curMinor <= 1) { - console.log("Applying v1.1.0 system data migraton!"); - this.callMigrators(data, v1_1_0.systemMigrators); - } - if (curMinor < 7) { - console.log("Applying v1.7.0 system data migration!"); - this.callMigrators(data, v1_7_0.systemMigrators); - } - if (curMinor === 8) { - if (curPatch <= 2) { - console.log("Applying v1.8.3 system data migration!"); - this.callMigrators(data, v1_8_3.systemMigrators); - } - } - } - - console.log(`System data successfully migrated to v${version}!`); - } -} - -/** - * Class encapsulating the logic for migrating settings data from - * a given version up to the current version listed in `package.json`. - * @extends VersionConverter - */ -class SettingsVersionConverter extends VersionConverter { - override applyMigration(data: Object, curVersion: number[]): void { - const [curMajor, curMinor, curPatch] = curVersion; - - if (curMajor === 1) { - if (curMinor === 0) { - if (curPatch <= 4) { - console.log("Applying v1.0.4 settings data migraton!"); - this.callMigrators(data, v1_0_4.settingsMigrators); - } - } - if (curMinor <= 1) { - console.log("Applying v1.1.0 settings data migraton!"); - this.callMigrators(data, v1_1_0.settingsMigrators); - } - if (curMinor < 7) { - console.log("Applying v1.7.0 settings data migration!"); - this.callMigrators(data, v1_7_0.settingsMigrators); - } - } - - console.log(`Settings data successfully migrated to v${version}!`); + if (isCurrentVersionHigher) { + applyMigrators(settingsMigrators, data, prevVersion); + console.log(`Settings successfully migrated to v${LATEST_VERSION}!`); } } diff --git a/src/system/version_migration/versions/v1_0_4.ts b/src/system/version_migration/versions/v1_0_4.ts index 16bd9db9915..2139352b783 100644 --- a/src/system/version_migration/versions/v1_0_4.ts +++ b/src/system/version_migration/versions/v1_0_4.ts @@ -4,15 +4,18 @@ import { AbilityAttr, defaultStarterSpecies, DexAttr } from "#app/system/game-da import { allSpecies } from "#app/data/pokemon-species"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { isNullOrUndefined } from "#app/utils"; +import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; +import type { SettingsSaveMigrator } from "#app/@types/SettingsSaveMigrator"; +import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator"; -export const systemMigrators = [ - /** - * Migrate ability starter data if empty for caught species. - * @param data {@linkcode SystemSaveData} - */ - function migrateAbilityData(data: SystemSaveData) { +/** + * Migrate ability starter data if empty for caught species. + * @param data - {@linkcode SystemSaveData} + */ +const migrateAbilityData: SystemSaveMigrator = { + version: "1.0.4", + migrate: (data: SystemSaveData): void => { if (data.starterData && data.dexData) { - // biome-ignore lint/complexity/noForEach: Object.keys(data.starterData).forEach(sd => { if (data.dexData[sd]?.caughtAttr && data.starterData[sd] && !data.starterData[sd].abilityAttr) { data.starterData[sd].abilityAttr = 1; @@ -20,12 +23,15 @@ export const systemMigrators = [ }); } }, +}; - /** - * Populate legendary Pokémon statistics if they are missing. - * @param data {@linkcode SystemSaveData} - */ - function fixLegendaryStats(data: SystemSaveData) { +/** + * Populate legendary Pokémon statistics if they are missing. + * @param data - {@linkcode SystemSaveData} + */ +const fixLegendaryStats: SystemSaveMigrator = { + version: "1.0.4", + migrate: (data: SystemSaveData): void => { if ( data.gameStats && data.gameStats.legendaryPokemonCaught !== undefined && @@ -34,7 +40,6 @@ export const systemMigrators = [ data.gameStats.subLegendaryPokemonSeen = 0; data.gameStats.subLegendaryPokemonCaught = 0; data.gameStats.subLegendaryPokemonHatched = 0; - // biome-ignore lint/complexity/noForEach: allSpecies .filter(s => s.subLegendary) .forEach(s => { @@ -66,12 +71,15 @@ export const systemMigrators = [ ); } }, +}; - /** - * Unlock all starters' first ability and female gender option. - * @param data {@linkcode SystemSaveData} - */ - function fixStarterData(data: SystemSaveData) { +/** + * Unlock all starters' first ability and female gender option. + * @param data - {@linkcode SystemSaveData} + */ +const fixStarterData: SystemSaveMigrator = { + version: "1.0.4", + migrate: (data: SystemSaveData): void => { if (!isNullOrUndefined(data.starterData)) { for (const starterId of defaultStarterSpecies) { if (data.starterData[starterId]?.abilityAttr) { @@ -83,17 +91,22 @@ export const systemMigrators = [ } } }, +}; + +export const systemMigrators: Readonly = [ + migrateAbilityData, + fixLegendaryStats, + fixStarterData, ] as const; -export const settingsMigrators = [ - /** - * Migrate from "REROLL_TARGET" property to {@linkcode - * SettingKeys.Shop_Cursor_Target}. - * @param data the `settings` object - */ - - // biome-ignore lint/complexity/noBannedTypes: TODO: fix the type to not be object... - function fixRerollTarget(data: Object) { +/** + * Migrate from `REROLL_TARGET` property to {@linkcode SettingKeys.Shop_Cursor_Target} + * @param data - The `settings` object + */ +const fixRerollTarget: SettingsSaveMigrator = { + version: "1.0.4", + // biome-ignore lint/complexity/noBannedTypes: TODO - refactor settings + migrate: (data: Object): void => { if (data.hasOwnProperty("REROLL_TARGET") && !data.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) { data[SettingKeys.Shop_Cursor_Target] = data["REROLL_TARGET"]; // biome-ignore lint/performance/noDelete: intentional @@ -101,16 +114,20 @@ export const settingsMigrators = [ localStorage.setItem("settings", JSON.stringify(data)); } }, -] as const; +}; -export const sessionMigrators = [ - /** - * Converts old lapsing modifiers (battle items, lures, and Dire Hit) and - * other miscellaneous modifiers (vitamins, White Herb) to any new class - * names and/or change in reload arguments. - * @param data {@linkcode SessionSaveData} - */ - function migrateModifiers(data: SessionSaveData) { +export const settingsMigrators: Readonly = [fixRerollTarget] as const; + +/** + * Converts old lapsing modifiers (battle items, lures, and Dire Hit) and + * other miscellaneous modifiers (vitamins, White Herb) to any new class + * names and/or change in reload arguments. + * @param data - {@linkcode SessionSaveData} + */ +const migrateModifiers: SessionSaveMigrator = { + version: "1.0.4", + // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: necessary? + migrate: (data: SessionSaveData): void => { for (const m of data.modifiers) { if (m.className === "PokemonBaseStatModifier") { m.className = "BaseStatModifier"; @@ -163,12 +180,11 @@ export const sessionMigrators = [ } } }, - /** - * Converts old Pokemon natureOverride and mysteryEncounterData - * to use the new conjoined {@linkcode Pokemon.customPokemonData} structure instead. - * @param data {@linkcode SessionSaveData} - */ - function migrateCustomPokemonDataAndNatureOverrides(data: SessionSaveData) { +}; + +const migrateCustomPokemonData: SessionSaveMigrator = { + version: "1.0.4", + migrate: (data: SessionSaveData): void => { // Fix Pokemon nature overrides and custom data migration for (const pokemon of data.party) { if (pokemon["mysteryEncounterPokemonData"]) { @@ -186,4 +202,6 @@ export const sessionMigrators = [ } } }, -] as const; +}; + +export const sessionMigrators: Readonly = [migrateModifiers, migrateCustomPokemonData] as const; diff --git a/src/system/version_migration/versions/v1_1_0.ts b/src/system/version_migration/versions/v1_1_0.ts deleted file mode 100644 index 5d6247aeaa2..00000000000 --- a/src/system/version_migration/versions/v1_1_0.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const systemMigrators = [] as const; - -export const settingsMigrators = [] as const; - -export const sessionMigrators = [] as const; diff --git a/src/system/version_migration/versions/v1_7_0.ts b/src/system/version_migration/versions/v1_7_0.ts index 167cd974e56..a1213ccf64c 100644 --- a/src/system/version_migration/versions/v1_7_0.ts +++ b/src/system/version_migration/versions/v1_7_0.ts @@ -1,15 +1,18 @@ +import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator"; +import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { globalScene } from "#app/global-scene"; import { DexAttr, type SessionSaveData, type SystemSaveData } from "#app/system/game-data"; -import * as Utils from "#app/utils"; +import { isNullOrUndefined } from "#app/utils"; -export const systemMigrators = [ - /** - * If a starter is caught, but the only forms registered as caught are not starterSelectable, - * unlock the default form. - * @param data {@linkcode SystemSaveData} - */ - function migrateUnselectableForms(data: SystemSaveData) { +/** + * If a starter is caught, but the only forms registered as caught are not starterSelectable, + * unlock the default form. + * @param data - {@linkcode SystemSaveData} + */ +const migrateUnselectableForms: SystemSaveMigrator = { + version: "1.7.0", + migrate: (data: SystemSaveData): void => { if (data.starterData && data.dexData) { Object.keys(data.starterData).forEach(sd => { const caughtAttr = data.dexData[sd]?.caughtAttr; @@ -30,12 +33,13 @@ export const systemMigrators = [ }); } }, -] as const; +}; -export const settingsMigrators = [] as const; +export const systemMigrators: Readonly = [migrateUnselectableForms] as const; -export const sessionMigrators = [ - function migrateTera(data: SessionSaveData) { +const migrateTera: SessionSaveMigrator = { + version: "1.7.0", + migrate: (data: SessionSaveData): void => { for (let i = 0; i < data.modifiers.length; ) { if (data.modifiers[i].className === "TerastallizeModifier") { data.party.forEach(p => { @@ -63,15 +67,17 @@ export const sessionMigrators = [ } data.party.forEach(p => { - if (Utils.isNullOrUndefined(p.teraType)) { + if (isNullOrUndefined(p.teraType)) { p.teraType = getPokemonSpeciesForm(p.species, p.formIndex).type1; } }); data.enemyParty.forEach(p => { - if (Utils.isNullOrUndefined(p.teraType)) { + if (isNullOrUndefined(p.teraType)) { p.teraType = getPokemonSpeciesForm(p.species, p.formIndex).type1; } }); }, -] as const; +}; + +export const sessionMigrators: Readonly = [migrateTera] as const; diff --git a/src/system/version_migration/versions/v1_8_3.ts b/src/system/version_migration/versions/v1_8_3.ts index d35530c28e9..6e2d96d3673 100644 --- a/src/system/version_migration/versions/v1_8_3.ts +++ b/src/system/version_migration/versions/v1_8_3.ts @@ -1,14 +1,16 @@ +import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { DexAttr, type SystemSaveData } from "#app/system/game-data"; import { Species } from "#enums/species"; -export const systemMigrators = [ - /** - * If a starter is caught, but the only forms registered as caught are not starterSelectable, - * unlock the default form. - * @param data {@linkcode SystemSaveData} - */ - function migratePichuForms(data: SystemSaveData) { +/** + * If a starter is caught, but the only forms registered as caught are not starterSelectable, + * unlock the default form. + * @param data - {@linkcode SystemSaveData} + */ +const migratePichuForms: SystemSaveMigrator = { + version: "1.8.3", + migrate: (data: SystemSaveData): void => { if (data.starterData && data.dexData) { // This is Pichu's Pokédex number const sd = 172; @@ -23,8 +25,6 @@ export const systemMigrators = [ } } }, -] as const; +}; -export const settingsMigrators = [] as const; - -export const sessionMigrators = [] as const; +export const systemMigrators: Readonly = [migratePichuForms] as const; From cb5deb408ff75310cf4fca403099d3b57f4c376d Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 6 Apr 2025 21:25:20 -0700 Subject: [PATCH 19/33] [Refactor] Delete stale pokemon objects at the end of a battle Co-authored-by: Frutescens --- src/phases/battle-end-phase.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index ff17b17ab8b..dea575a71b3 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -73,6 +73,11 @@ export class BattleEndPhase extends BattlePhase { } globalScene.clearEnemyHeldItemModifiers(); + try { + globalScene.getEnemyParty().forEach(p => p.destroy()); + } catch { + console.warn("Unable to destroy stale pokemon objects in BattleEndPhase."); + } const lapsingModifiers = globalScene.findModifiers( m => m instanceof LapsingPersistentModifier || m instanceof LapsingPokemonHeldItemModifier, From 17a56cc6c1d2dfebfd6fbb0045fb742b421ccb7b Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Mon, 7 Apr 2025 15:57:50 -0700 Subject: [PATCH 20/33] Move `try/catch` inside `for` loop Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> --- src/phases/battle-end-phase.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index dea575a71b3..645b04aea46 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -73,10 +73,12 @@ export class BattleEndPhase extends BattlePhase { } globalScene.clearEnemyHeldItemModifiers(); - try { - globalScene.getEnemyParty().forEach(p => p.destroy()); - } catch { - console.warn("Unable to destroy stale pokemon objects in BattleEndPhase."); + for (const p of globalScene.getEnemyParty()) { + try { + p.destroy(); + } catch { + console.warn("Unable to destroy stale pokemon objects in BattleEndPhase."); + } } const lapsingModifiers = globalScene.findModifiers( From 1171656d12b6333bfda5ff9c10b55d393d21898f Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Mon, 7 Apr 2025 16:02:58 -0700 Subject: [PATCH 21/33] Update console message --- src/phases/battle-end-phase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index 645b04aea46..0d831c65b52 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -77,7 +77,7 @@ export class BattleEndPhase extends BattlePhase { try { p.destroy(); } catch { - console.warn("Unable to destroy stale pokemon objects in BattleEndPhase."); + console.warn("Unable to destroy stale pokemon object in BattleEndPhase:", p); } } From 31835e6d534b0a399df01222633f3d5ba8eaa81d Mon Sep 17 00:00:00 2001 From: Dean <69436131+emdeann@users.noreply.github.com> Date: Mon, 7 Apr 2025 16:32:10 -0700 Subject: [PATCH 22/33] [Bug] Fix #4972 Status-Prevention Abilities do not Cure Status (#5406) * Add PostSummonHealAbAttr and give it to appropriate abilities * Add attr to insomnia * Remove attr from leaf guard (it does not activate on gain with sun up) * Add tests and remove attr from shields down * Add PostSummonRemoveBattlerTag and give it to oblivious and own tempo * Add tests for oblivious and own tempo * Fix oblivious test sometimes failing * Remove Comatose changes as it doesn't reapply * Remove unused tagRemoved field * Fix tests checking status instead of tag * Fix attr comments * Add PostSetStatusHealStatusAbAttr * Add ResetStatusPhase * Modify pokemon.resetStatus to use ResetStatusPhase * Move post status effects to ObtainStatusEffectPhase * Ensure status overriding (ie rest) works properly * Add PostApplyBattlerTagRemoveTagAbAttr for own tempo and oblivious * Guard removeTag call in PostApplyBattlerTagRemoveTagAbAttr * Commenting * Handle Mold Breaker case in MoveEndPhase * Remove PostSummonHealStatusAbAttr from purifying salt * Fix not passing overrideStatus to canSetStatus * Apply suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Add isNullOrUndefined import * Add canApply to new attrs * Add followup argument back * Remove guard around new MoveEndPhase --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/ability.ts | 78 ++++++++++++++++++++++++ src/data/moves/move.ts | 12 ++-- src/field/pokemon.ts | 38 +++--------- src/phases/move-end-phase.ts | 17 +++++- src/phases/move-phase.ts | 10 ++- src/phases/obtain-status-effect-phase.ts | 9 +++ src/phases/reset-status-phase.ts | 44 +++++++++++++ test/abilities/immunity.test.ts | 51 ++++++++++++++++ test/abilities/insomnia.test.ts | 51 ++++++++++++++++ test/abilities/limber.test.ts | 51 ++++++++++++++++ test/abilities/magma_armor.test.ts | 51 ++++++++++++++++ test/abilities/oblivious.test.ts | 69 +++++++++++++++++++++ test/abilities/own_tempo.test.ts | 51 ++++++++++++++++ test/abilities/thermal_exchange.test.ts | 51 ++++++++++++++++ test/abilities/vital_spirit.test.ts | 51 ++++++++++++++++ test/abilities/water_bubble.test.ts | 51 ++++++++++++++++ test/abilities/water_veil.test.ts | 51 ++++++++++++++++ 17 files changed, 693 insertions(+), 43 deletions(-) create mode 100644 src/phases/reset-status-phase.ts create mode 100644 test/abilities/immunity.test.ts create mode 100644 test/abilities/insomnia.test.ts create mode 100644 test/abilities/limber.test.ts create mode 100644 test/abilities/magma_armor.test.ts create mode 100644 test/abilities/oblivious.test.ts create mode 100644 test/abilities/own_tempo.test.ts create mode 100644 test/abilities/thermal_exchange.test.ts create mode 100644 test/abilities/vital_spirit.test.ts create mode 100644 test/abilities/water_bubble.test.ts create mode 100644 test/abilities/water_veil.test.ts diff --git a/src/data/ability.ts b/src/data/ability.ts index a7107ce2e9d..f8c9b4cb8fe 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2295,6 +2295,11 @@ export class PostSummonAbAttr extends AbAttr { applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {} } +/** + * Base class for ability attributes which remove an effect on summon + */ +export class PostSummonRemoveEffectAbAttr extends PostSummonAbAttr {} + /** * Removes specified arena tags when a Pokemon is summoned. */ @@ -2405,6 +2410,31 @@ export class PostSummonAddBattlerTagAbAttr extends PostSummonAbAttr { } } +/** + * Removes Specific battler tags when a Pokemon is summoned + * + * This should realistically only ever activate on gain rather than on summon + */ +export class PostSummonRemoveBattlerTagAbAttr extends PostSummonRemoveEffectAbAttr { + private immuneTags: BattlerTagType[]; + + /** + * @param immuneTags - The {@linkcode BattlerTagType | battler tags} the Pokémon is immune to. + */ + constructor(...immuneTags: BattlerTagType[]) { + super(); + this.immuneTags = immuneTags; + } + + public override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + return this.immuneTags.some(tagType => !!pokemon.getTag(tagType)); + } + + public override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + this.immuneTags.forEach(tagType => pokemon.removeTag(tagType)); + } +} + export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { private stats: BattleStat[]; private stages: number; @@ -2592,6 +2622,43 @@ export class PostSummonTerrainChangeAbAttr extends PostSummonAbAttr { } } +/** + * Heals a status effect if the Pokemon is afflicted with it upon switch in (or gain) + */ +export class PostSummonHealStatusAbAttr extends PostSummonRemoveEffectAbAttr { + private immuneEffects: StatusEffect[]; + private statusHealed: StatusEffect; + + /** + * @param immuneEffects - The {@linkcode StatusEffect}s the Pokémon is immune to. + */ + constructor(...immuneEffects: StatusEffect[]) { + super(); + this.immuneEffects = immuneEffects; + } + + public override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + const status = pokemon.status?.effect; + return !Utils.isNullOrUndefined(status) && (this.immuneEffects.length < 1 || this.immuneEffects.includes(status)) + } + + public override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + const status = pokemon.status?.effect; + if (!Utils.isNullOrUndefined(status)) { + this.statusHealed = status; + pokemon.resetStatus(false); + pokemon.updateInfo(); + } + } + + public override getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null { + if (this.statusHealed) { + return getStatusEffectHealText(this.statusHealed, getPokemonNameWithAffix(_pokemon)); + } + return null; + } +} + export class PostSummonFormChangeAbAttr extends PostSummonAbAttr { private formFunc: (p: Pokemon) => number; @@ -6291,6 +6358,7 @@ export function initAbilities() { .ignorable(), new Ability(Abilities.LIMBER, 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.PARALYSIS) + .attr(PostSummonHealStatusAbAttr, StatusEffect.PARALYSIS) .ignorable(), new Ability(Abilities.SAND_VEIL, 3) .attr(StatMultiplierAbAttr, Stat.EVA, 1.2) @@ -6308,6 +6376,7 @@ export function initAbilities() { .ignorable(), new Ability(Abilities.OBLIVIOUS, 3) .attr(BattlerTagImmunityAbAttr, [ BattlerTagType.INFATUATED, BattlerTagType.TAUNT ]) + .attr(PostSummonRemoveBattlerTagAbAttr, BattlerTagType.INFATUATED, BattlerTagType.TAUNT) .attr(IntimidateImmunityAbAttr) .ignorable(), new Ability(Abilities.CLOUD_NINE, 3) @@ -6320,6 +6389,7 @@ export function initAbilities() { .attr(StatMultiplierAbAttr, Stat.ACC, 1.3), new Ability(Abilities.INSOMNIA, 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP) + .attr(PostSummonHealStatusAbAttr, StatusEffect.SLEEP) .attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY) .ignorable(), new Ability(Abilities.COLOR_CHANGE, 3) @@ -6327,6 +6397,7 @@ export function initAbilities() { .condition(getSheerForceHitDisableAbCondition()), new Ability(Abilities.IMMUNITY, 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) + .attr(PostSummonHealStatusAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) .ignorable(), new Ability(Abilities.FLASH_FIRE, 3) .attr(TypeImmunityAddBattlerTagAbAttr, PokemonType.FIRE, BattlerTagType.FIRE_BOOST, 1) @@ -6336,6 +6407,7 @@ export function initAbilities() { .ignorable(), new Ability(Abilities.OWN_TEMPO, 3) .attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED) + .attr(PostSummonRemoveBattlerTagAbAttr, BattlerTagType.CONFUSED) .attr(IntimidateImmunityAbAttr) .ignorable(), new Ability(Abilities.SUCTION_CUPS, 3) @@ -6401,9 +6473,11 @@ export function initAbilities() { .ignorable(), new Ability(Abilities.MAGMA_ARMOR, 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.FREEZE) + .attr(PostSummonHealStatusAbAttr, StatusEffect.FREEZE) .ignorable(), new Ability(Abilities.WATER_VEIL, 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.BURN) + .attr(PostSummonHealStatusAbAttr, StatusEffect.BURN) .ignorable(), new Ability(Abilities.MAGNET_PULL, 3) .attr(ArenaTrapAbAttr, (user, target) => { @@ -6497,6 +6571,7 @@ export function initAbilities() { .attr(DoubleBattleChanceAbAttr), new Ability(Abilities.VITAL_SPIRIT, 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP) + .attr(PostSummonHealStatusAbAttr, StatusEffect.SLEEP) .attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY) .ignorable(), new Ability(Abilities.WHITE_SMOKE, 3) @@ -6835,6 +6910,7 @@ export function initAbilities() { .attr(MoveTypeChangeAbAttr, PokemonType.ICE, 1.2, (user, target, move) => move.type === PokemonType.NORMAL && !move.hasAttr(VariableMoveTypeAttr)), new Ability(Abilities.SWEET_VEIL, 6) .attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.SLEEP) + .attr(PostSummonUserFieldRemoveStatusEffectAbAttr, StatusEffect.SLEEP) .attr(UserFieldBattlerTagImmunityAbAttr, BattlerTagType.DROWSY) .ignorable() .partial(), // Mold Breaker ally should not be affected by Sweet Veil @@ -6919,6 +6995,7 @@ export function initAbilities() { .attr(ReceivedTypeDamageMultiplierAbAttr, PokemonType.FIRE, 0.5) .attr(MoveTypePowerBoostAbAttr, PokemonType.WATER, 2) .attr(StatusEffectImmunityAbAttr, StatusEffect.BURN) + .attr(PostSummonHealStatusAbAttr, StatusEffect.BURN) .ignorable(), new Ability(Abilities.STEELWORKER, 7) .attr(MoveTypePowerBoostAbAttr, PokemonType.STEEL), @@ -7197,6 +7274,7 @@ export function initAbilities() { new Ability(Abilities.THERMAL_EXCHANGE, 9) .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === PokemonType.FIRE && move.category !== MoveCategory.STATUS, Stat.ATK, 1) .attr(StatusEffectImmunityAbAttr, StatusEffect.BURN) + .attr(PostSummonHealStatusAbAttr, StatusEffect.BURN) .ignorable(), new Ability(Abilities.ANGER_SHELL, 9) .attr(PostDefendHpGatedStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [ Stat.ATK, Stat.SPATK, Stat.SPD ], 1) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 7a820d984d0..1af4be4fdf0 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -2443,12 +2443,8 @@ export class StatusEffectAttr extends MoveEffectAttr { const statusCheck = moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance; if (statusCheck) { const pokemon = this.selfTarget ? user : target; - if (pokemon.status) { - if (this.overrideStatus) { - pokemon.resetStatus(); - } else { - return false; - } + if (pokemon.status && !this.overrideStatus) { + return false; } if (user !== target && target.isSafeguarded(user)) { @@ -2457,8 +2453,8 @@ export class StatusEffectAttr extends MoveEffectAttr { } return false; } - if ((!pokemon.status || (pokemon.status.effect === this.effect && moveChance < 0)) - && pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining)) { + if (((!pokemon.status || this.overrideStatus) || (pokemon.status.effect === this.effect && moveChance < 0)) + && pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining, null, this.overrideStatus)) { applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect); return true; } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index f3e758e4efd..7d856696188 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -264,6 +264,7 @@ import { StatusEffect } from "#enums/status-effect"; import { doShinySparkleAnim } from "#app/field/anims"; import { MoveFlags } from "#enums/MoveFlags"; import { timedEventManager } from "#app/global-event-manager"; +import { ResetStatusPhase } from "#app/phases/reset-status-phase"; export enum LearnMoveSituation { MISC, @@ -4809,7 +4810,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (newTag.canAdd(this)) { this.summonData.tags.push(newTag); newTag.onAdd(this); - return true; } @@ -5480,8 +5480,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { sourcePokemon: Pokemon | null = null, turnsRemaining = 0, sourceText: string | null = null, + overrideStatus?: boolean ): boolean { - if (!this.canSetStatus(effect, asPhase, false, sourcePokemon)) { + if (!this.canSetStatus(effect, asPhase, overrideStatus, sourcePokemon)) { return false; } if (this.isFainted() && effect !== StatusEffect.FAINT) { @@ -5497,6 +5498,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (asPhase) { + if (overrideStatus) { + this.resetStatus(false); + } globalScene.unshiftPhase( new ObtainStatusEffectPhase( this.getBattlerIndex(), @@ -5536,20 +5540,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { effect = effect!; // If `effect` is undefined then `trySetStatus()` will have already returned early via the `canSetStatus()` call this.status = new Status(effect, 0, sleepTurnsRemaining?.value); - if (effect !== StatusEffect.FAINT) { - globalScene.triggerPokemonFormChange( - this, - SpeciesFormChangeStatusEffectTrigger, - true, - ); - applyPostSetStatusAbAttrs( - PostSetStatusAbAttr, - this, - effect, - sourcePokemon, - ); - } - return true; } @@ -5564,21 +5554,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!revive && lastStatus === StatusEffect.FAINT) { return; } - this.status = null; - if (lastStatus === StatusEffect.SLEEP) { - this.setFrameRate(10); - if (this.getTag(BattlerTagType.NIGHTMARE)) { - this.lapseTag(BattlerTagType.NIGHTMARE); - } - } - if (confusion) { - if (this.getTag(BattlerTagType.CONFUSED)) { - this.lapseTag(BattlerTagType.CONFUSED); - } - } - if (reloadAssets) { - this.loadAssets(false).then(() => this.playAnim()); - } + globalScene.unshiftPhase(new ResetStatusPhase(this, confusion, reloadAssets)); } /** diff --git a/src/phases/move-end-phase.ts b/src/phases/move-end-phase.ts index 53856956401..176abee5e98 100644 --- a/src/phases/move-end-phase.ts +++ b/src/phases/move-end-phase.ts @@ -2,11 +2,18 @@ import { globalScene } from "#app/global-scene"; import { BattlerTagLapseType } from "#app/data/battler-tags"; import { PokemonPhase } from "./pokemon-phase"; import type { BattlerIndex } from "#app/battle"; +import { applyPostSummonAbAttrs, PostSummonRemoveEffectAbAttr } from "#app/data/ability"; +import type Pokemon from "#app/field/pokemon"; export class MoveEndPhase extends PokemonPhase { private wasFollowUp: boolean; - constructor(battlerIndex: BattlerIndex, wasFollowUp = false) { + + /** Targets from the preceding MovePhase */ + private targets: Pokemon[]; + constructor(battlerIndex: BattlerIndex, targets: Pokemon[], wasFollowUp = false) { super(battlerIndex); + + this.targets = targets; this.wasFollowUp = wasFollowUp; } @@ -17,9 +24,15 @@ export class MoveEndPhase extends PokemonPhase { if (!this.wasFollowUp && pokemon?.isActive(true)) { pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE); } - globalScene.arena.setIgnoreAbilities(false); + // Remove effects which were set on a Pokemon which removes them on summon (i.e. via Mold Breaker) + for (const target of this.targets) { + if (target) { + applyPostSummonAbAttrs(PostSummonRemoveEffectAbAttr, target); + } + } + this.end(); } } diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 5232dfee8ba..478229dcae8 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -169,7 +169,11 @@ export class MovePhase extends BattlePhase { // Check move to see if arena.ignoreAbilities should be true. if (!this.followUp || this.reflected) { - if (this.move.getMove().doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user: this.pokemon, isFollowUp: this.followUp })) { + if ( + this.move + .getMove() + .doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user: this.pokemon, isFollowUp: this.followUp }) + ) { globalScene.arena.setIgnoreAbilities(true, this.pokemon.getBattlerIndex()); } } @@ -473,7 +477,9 @@ export class MovePhase extends BattlePhase { * Queues a {@linkcode MoveEndPhase} and then ends the phase */ public end(): void { - globalScene.unshiftPhase(new MoveEndPhase(this.pokemon.getBattlerIndex(), this.followUp)); + globalScene.unshiftPhase( + new MoveEndPhase(this.pokemon.getBattlerIndex(), this.getActiveTargetPokemon(), this.followUp), + ); super.end(); } diff --git a/src/phases/obtain-status-effect-phase.ts b/src/phases/obtain-status-effect-phase.ts index a0c0c14e93f..cba9399b996 100644 --- a/src/phases/obtain-status-effect-phase.ts +++ b/src/phases/obtain-status-effect-phase.ts @@ -6,6 +6,9 @@ import { StatusEffect } from "#app/enums/status-effect"; import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonPhase } from "./pokemon-phase"; +import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms"; +import { applyPostSetStatusAbAttrs, PostSetStatusAbAttr } from "#app/data/ability"; +import { isNullOrUndefined } from "#app/utils"; export class ObtainStatusEffectPhase extends PokemonPhase { private statusEffect?: StatusEffect; @@ -44,6 +47,12 @@ export class ObtainStatusEffectPhase extends PokemonPhase { this.sourceText ?? undefined, ), ); + if (!isNullOrUndefined(this.statusEffect) && this.statusEffect !== StatusEffect.FAINT) { + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeStatusEffectTrigger, true); + // If mold breaker etc was used to set this status, it shouldn't apply to abilities activated afterwards + globalScene.arena.setIgnoreAbilities(false); + applyPostSetStatusAbAttrs(PostSetStatusAbAttr, pokemon, this.statusEffect, this.sourcePokemon); + } this.end(); }); return; diff --git a/src/phases/reset-status-phase.ts b/src/phases/reset-status-phase.ts new file mode 100644 index 00000000000..0ba3559d9b7 --- /dev/null +++ b/src/phases/reset-status-phase.ts @@ -0,0 +1,44 @@ +import type Pokemon from "#app/field/pokemon"; +import { BattlePhase } from "#app/phases/battle-phase"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { StatusEffect } from "#enums/status-effect"; + +/** + * Phase which handles resetting a Pokemon's status to none + * + * This is necessary to perform in a phase primarly to ensure that the status icon disappears at the correct time in the battle + */ +export class ResetStatusPhase extends BattlePhase { + private readonly pokemon: Pokemon; + private readonly affectConfusion: boolean; + private readonly reloadAssets: boolean; + + constructor(pokemon: Pokemon, affectConfusion: boolean, reloadAssets: boolean) { + super(); + + this.pokemon = pokemon; + this.affectConfusion = affectConfusion; + this.reloadAssets = reloadAssets; + } + + public override start() { + const lastStatus = this.pokemon.status?.effect; + this.pokemon.status = null; + if (lastStatus === StatusEffect.SLEEP) { + this.pokemon.setFrameRate(10); + if (this.pokemon.getTag(BattlerTagType.NIGHTMARE)) { + this.pokemon.lapseTag(BattlerTagType.NIGHTMARE); + } + } + if (this.affectConfusion) { + if (this.pokemon.getTag(BattlerTagType.CONFUSED)) { + this.pokemon.lapseTag(BattlerTagType.CONFUSED); + } + } + if (this.reloadAssets) { + this.pokemon.loadAssets(false).then(() => this.pokemon.playAnim()); + } + this.pokemon.updateInfo(true); + this.end(); + } +} diff --git a/test/abilities/immunity.test.ts b/test/abilities/immunity.test.ts new file mode 100644 index 00000000000..51e9598720b --- /dev/null +++ b/test/abilities/immunity.test.ts @@ -0,0 +1,51 @@ +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Immunity", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should remove poison when gained", async () => { + game.override.ability(Abilities.IMMUNITY) + .enemyAbility(Abilities.BALL_FETCH) + .moveset(Moves.SKILL_SWAP) + .enemyMoveset(Moves.SPLASH), + + await game.classicMode.startBattle([ Species.FEEBAS ]); + const enemy = game.scene.getEnemyPokemon(); + enemy?.trySetStatus(StatusEffect.POISON); + expect(enemy?.status?.effect).toBe(StatusEffect.POISON); + + game.move.select(Moves.SKILL_SWAP); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy?.status).toBeNull(); + }); +}); diff --git a/test/abilities/insomnia.test.ts b/test/abilities/insomnia.test.ts new file mode 100644 index 00000000000..91fdc3fc668 --- /dev/null +++ b/test/abilities/insomnia.test.ts @@ -0,0 +1,51 @@ +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Insomnia", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should remove sleep when gained", async () => { + game.override.ability(Abilities.INSOMNIA) + .enemyAbility(Abilities.BALL_FETCH) + .moveset(Moves.SKILL_SWAP) + .enemyMoveset(Moves.SPLASH), + + await game.classicMode.startBattle([ Species.FEEBAS ]); + const enemy = game.scene.getEnemyPokemon(); + enemy?.trySetStatus(StatusEffect.SLEEP); + expect(enemy?.status?.effect).toBe(StatusEffect.SLEEP); + + game.move.select(Moves.SKILL_SWAP); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy?.status).toBeNull(); + }); +}); diff --git a/test/abilities/limber.test.ts b/test/abilities/limber.test.ts new file mode 100644 index 00000000000..2b167cc155f --- /dev/null +++ b/test/abilities/limber.test.ts @@ -0,0 +1,51 @@ +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Limber", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should remove paralysis when gained", async () => { + game.override.ability(Abilities.LIMBER) + .enemyAbility(Abilities.BALL_FETCH) + .moveset(Moves.SKILL_SWAP) + .enemyMoveset(Moves.SPLASH), + + await game.classicMode.startBattle([ Species.FEEBAS ]); + const enemy = game.scene.getEnemyPokemon(); + enemy?.trySetStatus(StatusEffect.PARALYSIS); + expect(enemy?.status?.effect).toBe(StatusEffect.PARALYSIS); + + game.move.select(Moves.SKILL_SWAP); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy?.status).toBeNull(); + }); +}); diff --git a/test/abilities/magma_armor.test.ts b/test/abilities/magma_armor.test.ts new file mode 100644 index 00000000000..b1d62f948d2 --- /dev/null +++ b/test/abilities/magma_armor.test.ts @@ -0,0 +1,51 @@ +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Magma Armor", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should remove freeze when gained", async () => { + game.override.ability(Abilities.MAGMA_ARMOR) + .enemyAbility(Abilities.BALL_FETCH) + .moveset(Moves.SKILL_SWAP) + .enemyMoveset(Moves.SPLASH), + + await game.classicMode.startBattle([ Species.FEEBAS ]); + const enemy = game.scene.getEnemyPokemon(); + enemy?.trySetStatus(StatusEffect.FREEZE); + expect(enemy?.status?.effect).toBe(StatusEffect.FREEZE); + + game.move.select(Moves.SKILL_SWAP); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy?.status).toBeNull(); + }); +}); diff --git a/test/abilities/oblivious.test.ts b/test/abilities/oblivious.test.ts new file mode 100644 index 00000000000..d5089ef6a72 --- /dev/null +++ b/test/abilities/oblivious.test.ts @@ -0,0 +1,69 @@ +import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Abilities - Oblivious", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should remove taunt when gained", async () => { + game.override.ability(Abilities.OBLIVIOUS) + .enemyAbility(Abilities.BALL_FETCH) + .moveset(Moves.SKILL_SWAP) + .enemyMoveset(Moves.SPLASH), + + await game.classicMode.startBattle([ Species.FEEBAS ]); + const enemy = game.scene.getEnemyPokemon(); + enemy?.addTag(BattlerTagType.TAUNT); + expect(enemy?.getTag(BattlerTagType.TAUNT)).toBeTruthy(); + + game.move.select(Moves.SKILL_SWAP); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy?.getTag(BattlerTagType.TAUNT)).toBeFalsy(); + }); + + it("should remove infatuation when gained", async () => { + game.override.ability(Abilities.OBLIVIOUS) + .enemyAbility(Abilities.BALL_FETCH) + .moveset(Moves.SKILL_SWAP) + .enemyMoveset(Moves.SPLASH), + + await game.classicMode.startBattle([ Species.FEEBAS ]); + const enemy = game.scene.getEnemyPokemon(); + vi.spyOn(enemy!, "isOppositeGender").mockReturnValue(true); + enemy?.addTag(BattlerTagType.INFATUATED, 5, Moves.JUDGMENT, game.scene.getPlayerPokemon()?.id); // sourceID needs to be defined + expect(enemy?.getTag(BattlerTagType.INFATUATED)).toBeTruthy(); + + game.move.select(Moves.SKILL_SWAP); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy?.getTag(BattlerTagType.INFATUATED)).toBeFalsy(); + }); +}); diff --git a/test/abilities/own_tempo.test.ts b/test/abilities/own_tempo.test.ts new file mode 100644 index 00000000000..936b4311b20 --- /dev/null +++ b/test/abilities/own_tempo.test.ts @@ -0,0 +1,51 @@ +import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Own Tempo", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should remove confusion when gained", async () => { + game.override.ability(Abilities.OWN_TEMPO) + .enemyAbility(Abilities.BALL_FETCH) + .moveset(Moves.SKILL_SWAP) + .enemyMoveset(Moves.SPLASH), + + await game.classicMode.startBattle([ Species.FEEBAS ]); + const enemy = game.scene.getEnemyPokemon(); + enemy?.addTag(BattlerTagType.CONFUSED); + expect(enemy?.getTag(BattlerTagType.CONFUSED)).toBeTruthy(); + + game.move.select(Moves.SKILL_SWAP); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy?.getTag(BattlerTagType.CONFUSED)).toBeFalsy(); + }); +}); diff --git a/test/abilities/thermal_exchange.test.ts b/test/abilities/thermal_exchange.test.ts new file mode 100644 index 00000000000..124c1dba286 --- /dev/null +++ b/test/abilities/thermal_exchange.test.ts @@ -0,0 +1,51 @@ +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Thermal Exchange", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should remove burn when gained", async () => { + game.override.ability(Abilities.THERMAL_EXCHANGE) + .enemyAbility(Abilities.BALL_FETCH) + .moveset(Moves.SKILL_SWAP) + .enemyMoveset(Moves.SPLASH), + + await game.classicMode.startBattle([ Species.FEEBAS ]); + const enemy = game.scene.getEnemyPokemon(); + enemy?.trySetStatus(StatusEffect.BURN); + expect(enemy?.status?.effect).toBe(StatusEffect.BURN); + + game.move.select(Moves.SKILL_SWAP); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy?.status).toBeNull(); + }); +}); diff --git a/test/abilities/vital_spirit.test.ts b/test/abilities/vital_spirit.test.ts new file mode 100644 index 00000000000..3a53c3f520e --- /dev/null +++ b/test/abilities/vital_spirit.test.ts @@ -0,0 +1,51 @@ +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Vital Spirit", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should remove sleep when gained", async () => { + game.override.ability(Abilities.INSOMNIA) + .enemyAbility(Abilities.BALL_FETCH) + .moveset(Moves.SKILL_SWAP) + .enemyMoveset(Moves.SPLASH), + + await game.classicMode.startBattle([ Species.FEEBAS ]); + const enemy = game.scene.getEnemyPokemon(); + enemy?.trySetStatus(StatusEffect.SLEEP); + expect(enemy?.status?.effect).toBe(StatusEffect.SLEEP); + + game.move.select(Moves.SKILL_SWAP); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy?.status).toBeNull(); + }); +}); diff --git a/test/abilities/water_bubble.test.ts b/test/abilities/water_bubble.test.ts new file mode 100644 index 00000000000..0b85a5814da --- /dev/null +++ b/test/abilities/water_bubble.test.ts @@ -0,0 +1,51 @@ +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Water Bubble", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should remove burn when gained", async () => { + game.override.ability(Abilities.THERMAL_EXCHANGE) + .enemyAbility(Abilities.BALL_FETCH) + .moveset(Moves.SKILL_SWAP) + .enemyMoveset(Moves.SPLASH), + + await game.classicMode.startBattle([ Species.FEEBAS ]); + const enemy = game.scene.getEnemyPokemon(); + enemy?.trySetStatus(StatusEffect.BURN); + expect(enemy?.status?.effect).toBe(StatusEffect.BURN); + + game.move.select(Moves.SKILL_SWAP); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy?.status).toBeNull(); + }); +}); diff --git a/test/abilities/water_veil.test.ts b/test/abilities/water_veil.test.ts new file mode 100644 index 00000000000..38c9a05600b --- /dev/null +++ b/test/abilities/water_veil.test.ts @@ -0,0 +1,51 @@ +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Water Veil", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should remove burn when gained", async () => { + game.override.ability(Abilities.THERMAL_EXCHANGE) + .enemyAbility(Abilities.BALL_FETCH) + .moveset(Moves.SKILL_SWAP) + .enemyMoveset(Moves.SPLASH), + + await game.classicMode.startBattle([ Species.FEEBAS ]); + const enemy = game.scene.getEnemyPokemon(); + enemy?.trySetStatus(StatusEffect.BURN); + expect(enemy?.status?.effect).toBe(StatusEffect.BURN); + + game.move.select(Moves.SKILL_SWAP); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy?.status).toBeNull(); + }); +}); From 787feceb14f0cd635ca833e94a877b6fe379e50a Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Wed, 9 Apr 2025 10:43:05 -0500 Subject: [PATCH 23/33] [Refactor] Refactor variant sprite code part 1 (#5592) * Move exp to its own masterlist, simplify initVariantData * Update test/sprites/pokemonSprite.test.ts * Extract loadPokemonVariantAssets out of BattleScene * move variant.ts and update pokemon.loadAssets * Add fuzzy matching for applying variant recolors * Move glsl shaders to their own files * Remove extra variants from shader masterlist Their exp sprites have since been removed. Co-authored-by: Unicorn_Power <189861924+Unicornpowerstar@users.noreply.github.com> * Make exp sprite keys a set instead of an array * Remove outdated exp sprite jsons Co-authored-by: Unicorn_Power <189861924+Unicornpowerstar@users.noreply.github.com> --------- Co-authored-by: Unicorn_Power <189861924+Unicornpowerstar@users.noreply.github.com> --- .../pokemon/variant/_exp_masterlist.json | 656 +++++++++++++++++ .../images/pokemon/variant/_masterlist.json | 664 ------------------ public/images/pokemon/variant/exp/698.json | 48 -- public/images/pokemon/variant/exp/703.json | 32 - public/images/pokemon/variant/exp/708.json | 28 - public/images/pokemon/variant/exp/714.json | 32 - .../images/pokemon/variant/exp/back/698.json | 38 - .../images/pokemon/variant/exp/back/703.json | 28 - .../images/pokemon/variant/exp/back/708.json | 40 -- .../images/pokemon/variant/exp/back/714.json | 24 - scripts/find_sprite_variant_mismatches.py | 4 + src/battle-scene.ts | 137 +--- .../utils/encounter-phase-utils.ts | 2 +- src/data/pokemon-species.ts | 31 +- src/data/trainers/trainer-config.ts | 7 +- src/data/variant.ts | 31 - src/field/anims.ts | 2 +- src/field/mystery-encounter-intro.ts | 7 +- src/field/pokemon.ts | 188 ++--- src/overrides.ts | 2 +- src/pipelines/field-sprite.ts | 208 +----- src/pipelines/glsl/fieldSpriteFragShader.frag | 168 +++++ src/pipelines/glsl/invert.frag | 10 + src/pipelines/glsl/spriteFragShader.frag | 279 ++++++++ src/pipelines/glsl/spriteShader.vert | 32 + src/pipelines/invert.ts | 14 +- src/pipelines/sprite.ts | 312 +------- src/sprites/pokemon-asset-loader.ts | 11 + src/sprites/pokemon-sprite.ts | 79 +++ src/sprites/sprite-keys.ts | 1 + src/sprites/sprite-utils.ts | 28 + src/sprites/variant.ts | 145 ++++ src/system/game-data.ts | 2 +- src/system/pokemon-data.ts | 2 +- src/ui/battle-info.ts | 2 +- src/ui/hatched-pokemon-container.ts | 2 +- src/ui/party-ui-handler.ts | 2 +- src/ui/pokedex-mon-container.ts | 2 +- src/ui/pokedex-page-ui-handler.ts | 4 +- src/ui/pokedex-ui-handler.ts | 4 +- src/ui/pokemon-info-container.ts | 2 +- src/ui/run-info-ui-handler.ts | 2 +- src/ui/starter-select-ui-handler.ts | 4 +- src/ui/summary-ui-handler.ts | 4 +- src/utils.ts | 22 + test/sprites/pokemonSprite.test.ts | 13 +- test/testUtils/helpers/overridesHelper.ts | 2 +- 47 files changed, 1599 insertions(+), 1758 deletions(-) create mode 100644 public/images/pokemon/variant/_exp_masterlist.json delete mode 100644 public/images/pokemon/variant/exp/698.json delete mode 100644 public/images/pokemon/variant/exp/703.json delete mode 100644 public/images/pokemon/variant/exp/708.json delete mode 100644 public/images/pokemon/variant/exp/714.json delete mode 100644 public/images/pokemon/variant/exp/back/698.json delete mode 100644 public/images/pokemon/variant/exp/back/703.json delete mode 100644 public/images/pokemon/variant/exp/back/708.json delete mode 100644 public/images/pokemon/variant/exp/back/714.json delete mode 100644 src/data/variant.ts create mode 100644 src/pipelines/glsl/fieldSpriteFragShader.frag create mode 100644 src/pipelines/glsl/invert.frag create mode 100644 src/pipelines/glsl/spriteFragShader.frag create mode 100644 src/pipelines/glsl/spriteShader.vert create mode 100644 src/sprites/pokemon-asset-loader.ts create mode 100644 src/sprites/pokemon-sprite.ts create mode 100644 src/sprites/sprite-keys.ts create mode 100644 src/sprites/sprite-utils.ts create mode 100644 src/sprites/variant.ts diff --git a/public/images/pokemon/variant/_exp_masterlist.json b/public/images/pokemon/variant/_exp_masterlist.json new file mode 100644 index 00000000000..0ef5f209439 --- /dev/null +++ b/public/images/pokemon/variant/_exp_masterlist.json @@ -0,0 +1,656 @@ +{ + "3-mega": [0, 2, 2], + "6-mega-x": [0, 2, 2], + "6-mega-y": [0, 2, 2], + "80-mega": [0, 1, 1], + "94-mega": [2, 2, 2], + "127-mega": [0, 1, 1], + "130-mega": [0, 1, 1], + "142-mega": [0, 1, 1], + "150-mega-x": [0, 1, 1], + "150-mega-y": [0, 1, 1], + "181-mega": [0, 1, 2], + "212-mega": [1, 1, 2], + "229-mega": [0, 1, 1], + "248-mega": [0, 1, 1], + "257-mega": [0, 1, 1], + "282-mega": [0, 2, 2], + "302-mega": [0, 1, 1], + "303-mega": [0, 1, 1], + "306-mega": [1, 1, 1], + "308-mega": [0, 1, 1], + "310-mega": [0, 1, 1], + "334-mega": [0, 2, 1], + "354-mega": [0, 1, 1], + "359-mega": [0, 1, 1], + "362-mega": [0, 1, 1], + "373-mega": [0, 1, 1], + "376-mega": [0, 1, 1], + "380-mega": [0, 1, 1], + "381-mega": [0, 1, 1], + "382-primal": [0, 1, 1], + "383-primal": [0, 1, 1], + "384-mega": [0, 2, 1], + "428-mega": [0, 1, 1], + "445-mega": [1, 1, 1], + "448-mega": [1, 1, 1], + "475-mega": [0, 2, 2], + "531-mega": [0, 1, 1], + "653": [0, 1, 1], + "654": [0, 1, 1], + "655": [0, 1, 1], + "656": [0, 1, 1], + "657": [0, 1, 1], + "658": [0, 1, 1], + "658-ash": [0, 1, 1], + "664": [0, 1, 1], + "665": [0, 1, 1], + "666-archipelago": [0, 1, 1], + "666-continental": [0, 1, 1], + "666-elegant": [0, 1, 1], + "666-fancy": [0, 1, 1], + "666-garden": [0, 1, 1], + "666-high-plains": [0, 1, 1], + "666-icy-snow": [0, 1, 1], + "666-jungle": [0, 1, 1], + "666-marine": [0, 1, 1], + "666-meadow": [0, 1, 1], + "666-modern": [0, 1, 1], + "666-monsoon": [0, 1, 1], + "666-ocean": [0, 1, 1], + "666-poke-ball": [0, 1, 1], + "666-polar": [0, 1, 1], + "666-river": [0, 1, 1], + "666-sandstorm": [0, 1, 1], + "666-savanna": [0, 1, 1], + "666-sun": [0, 1, 1], + "666-tundra": [0, 1, 1], + "669-red": [0, 2, 2], + "669-blue": [0, 1, 1], + "669-white": [0, 1, 1], + "669-yellow": [0, 1, 1], + "669-orange": [0, 2, 2], + "670-white": [0, 1, 1], + "670-blue": [0, 1, 1], + "670-orange": [0, 1, 1], + "670-red": [0, 1, 1], + "670-yellow": [0, 1, 1], + "671-red": [0, 1, 2], + "671-blue": [0, 1, 2], + "671-yellow": [0, 1, 1], + "671-white": [0, 1, 2], + "671-orange": [0, 1, 2], + "672": [0, 1, 1], + "673": [0, 1, 1], + "676": [0, 1, 1], + "677": [0, 1, 1], + "678-female": [0, 1, 1], + "678": [0, 1, 1], + "682": [0, 1, 1], + "683": [0, 1, 1], + "684": [0, 1, 1], + "685": [0, 1, 1], + "688": [0, 1, 1], + "689": [0, 1, 1], + "690": [0, 1, 1], + "691": [0, 1, 1], + "696": [0, 1, 1], + "697": [0, 1, 1], + "699": [0, 1, 1], + "700": [0, 1, 1], + "702": [0, 1, 1], + "704": [0, 1, 1], + "705": [0, 1, 1], + "706": [0, 1, 1], + "709": [0, 1, 1], + "710": [0, 1, 1], + "711": [1, 1, 1], + "712": [0, 1, 1], + "713": [0, 1, 1], + "715": [0, 1, 1], + "716-active": [0, 1, 1], + "716-neutral": [0, 1, 1], + "717": [0, 2, 2], + "720-unbound": [1, 1, 1], + "720": [1, 1, 1], + "728": [0, 1, 1], + "729": [0, 1, 1], + "730": [0, 1, 1], + "734": [0, 1, 1], + "735": [0, 1, 1], + "742": [0, 2, 2], + "743": [0, 2, 2], + "747": [0, 2, 2], + "748": [0, 1, 1], + "751": [0, 1, 1], + "752": [0, 1, 1], + "753": [0, 1, 1], + "754": [0, 2, 2], + "755": [0, 1, 1], + "756": [0, 1, 1], + "761": [0, 1, 1], + "762": [0, 1, 1], + "763": [0, 1, 1], + "767": [0, 1, 1], + "768": [0, 1, 1], + "770": [0, 0, 0], + "771": [0, 2, 2], + "772": [0, 1, 1], + "773-fighting": [0, 1, 1], + "773-psychic": [0, 1, 1], + "773-poison": [0, 1, 1], + "773-ground": [0, 1, 1], + "773-ghost": [0, 1, 1], + "773-steel": [0, 1, 1], + "773-rock": [0, 1, 1], + "773-grass": [0, 1, 1], + "773-dragon": [0, 1, 1], + "773-bug": [0, 1, 1], + "773-ice": [0, 1, 1], + "773-dark": [0, 1, 1], + "773": [0, 1, 1], + "773-fairy": [0, 1, 1], + "773-water": [0, 1, 1], + "773-electric": [0, 1, 1], + "773-flying": [0, 1, 1], + "773-fire": [0, 1, 1], + "776": [0, 1, 1], + "777": [0, 1, 1], + "778-busted": [0, 1, 1], + "778-disguised": [0, 1, 1], + "779": [0, 1, 1], + "789": [1, 1, 1], + "790": [0, 1, 1], + "791": [2, 1, 1], + "792": [0, 1, 1], + "793": [0, 2, 2], + "797": [0, 1, 1], + "798": [0, 1, 1], + "800-dawn-wings": [0, 1, 1], + "800-dusk-mane": [0, 1, 1], + "800-ultra": [0, 1, 1], + "800": [0, 1, 1], + "802": [1, 1, 1], + "803": [0, 1, 1], + "804": [0, 1, 1], + "807": [0, 1, 1], + "808": [0, 1, 1], + "809": [0, 1, 1], + "816": [0, 1, 1], + "817": [0, 1, 1], + "818": [1, 1, 1], + "821": [0, 2, 2], + "822": [0, 1, 1], + "823": [0, 1, 1], + "829": [0, 1, 1], + "830": [0, 1, 1], + "835": [0, 1, 1], + "836": [0, 2, 2], + "850": [0, 1, 1], + "851": [0, 1, 1], + "854": [0, 1, 1], + "855": [0, 1, 1], + "856": [0, 1, 1], + "857": [0, 2, 2], + "858": [0, 1, 1], + "859": [0, 1, 1], + "860": [0, 1, 1], + "861": [0, 1, 1], + "862": [0, 1, 1], + "863": [0, 1, 1], + "864": [0, 1, 1], + "867": [0, 1, 1], + "872": [1, 1, 1], + "873": [1, 1, 1], + "876-female": [0, 1, 1], + "876": [0, 1, 1], + "877-hangry": [1, 1, 1], + "877": [1, 1, 1], + "880": [0, 1, 1], + "881": [0, 1, 1], + "882": [0, 2, 1], + "883": [0, 1, 1], + "884": [0, 1, 1], + "885": [1, 1, 1], + "886": [1, 1, 1], + "887": [1, 1, 1], + "888": [0, 1, 1], + "888-crowned": [0, 1, 1], + "889": [0, 1, 1], + "889-crowned": [0, 1, 1], + "890": [0, 2, 1], + "890-eternamax": [0, 1, 1], + "891": [1, 1, 1], + "892-rapid-strike": [1, 1, 1], + "892": [1, 1, 1], + "894": [0, 1, 1], + "895": [0, 1, 1], + "896": [1, 1, 1], + "897": [1, 1, 1], + "898": [1, 1, 1], + "898-ice": [1, 1, 1], + "898-shadow": [1, 1, 1], + "900": [0, 1, 1], + "901": [0, 1, 1], + "903": [0, 1, 1], + "909": [0, 1, 1], + "910": [0, 2, 2], + "911": [0, 2, 2], + "912": [0, 1, 2], + "913": [0, 1, 2], + "914": [0, 2, 1], + "919": [1, 1, 1], + "920": [1, 1, 1], + "924": [1, 1, 1], + "925-four": [1, 2, 2], + "925-three": [1, 2, 2], + "932": [0, 2, 2], + "933": [0, 2, 2], + "934": [0, 1, 1], + "935": [1, 1, 2], + "936": [2, 2, 2], + "937": [2, 2, 2], + "940": [0, 1, 1], + "941": [0, 1, 1], + "944": [0, 1, 1], + "945": [0, 1, 1], + "948": [0, 1, 1], + "949": [0, 1, 1], + "951": [0, 1, 1], + "952": [0, 1, 1], + "953": [0, 1, 1], + "954": [0, 1, 1], + "957": [2, 2, 2], + "958": [2, 2, 2], + "959": [2, 2, 2], + "962": [1, 1, 1], + "967": [0, 1, 1], + "968": [0, 1, 1], + "969": [0, 1, 1], + "970": [0, 1, 1], + "973": [1, 1, 1], + "974": [0, 1, 1], + "975": [0, 1, 1], + "978-curly": [0, 2, 2], + "978-droopy": [0, 2, 2], + "978-stretchy": [0, 2, 2], + "979": [2, 2, 2], + "981": [0, 1, 1], + "982": [0, 1, 1], + "982-three-segment": [0, 1, 1], + "987": [1, 1, 1], + "988": [0, 1, 2], + "993": [0, 1, 1], + "994": [0, 1, 2], + "995": [0, 1, 1], + "996": [0, 1, 1], + "997": [0, 2, 2], + "998": [0, 2, 2], + "999": [2, 1, 1], + "1000": [1, 1, 1], + "1001": [0, 1, 1], + "1003": [0, 1, 1], + "1004": [0, 1, 1], + "1006": [0, 2, 1], + "1007-apex-build": [0, 2, 2], + "1008-ultimate-mode": [1, 1, 1], + "2026": [0, 1, 1], + "2027": [0, 1, 1], + "2028": [0, 1, 1], + "2052": [0, 1, 1], + "2053": [0, 1, 0], + "2103": [0, 1, 1], + "4052": [0, 1, 1], + "4077": [0, 1, 1], + "4078": [0, 1, 1], + "4079": [0, 1, 1], + "4080": [2, 1, 1], + "4144": [0, 1, 1], + "4145": [0, 1, 1], + "4146": [0, 1, 1], + "4199": [2, 1, 1], + "4222": [0, 1, 1], + "4263": [0, 1, 1], + "4264": [0, 1, 1], + "4562": [0, 1, 1], + "6100": [0, 1, 1], + "6101": [0, 1, 1], + "6215": [0, 1, 1], + "6503": [0, 1, 1], + "6549": [0, 1, 1], + "6570": [0, 1, 1], + "6571": [0, 1, 1], + "6705": [0, 1, 1], + "6706": [0, 1, 1], + "6713": [0, 1, 1], + "female": { + "6215": [0, 1, 1] + }, + "back": { + "3-mega": [0, 2, 2], + "6-mega-x": [0, 2, 2], + "6-mega-y": [0, 1, 2], + "80-mega": [0, 1, 1], + "94-mega": [1, 1, 1], + "127-mega": [0, 1, 1], + "130-mega": [0, 1, 1], + "142-mega": [0, 1, 1], + "150-mega-x": [0, 1, 1], + "150-mega-y": [0, 1, 1], + "181-mega": [0, 1, 2], + "212-mega": [1, 2, 2], + "229-mega": [0, 1, 1], + "248-mega": [0, 1, 1], + "257-mega": [0, 1, 1], + "282-mega": [0, 1, 1], + "302-mega": [0, 1, 1], + "303-mega": [0, 1, 1], + "306-mega": [1, 1, 1], + "308-mega": [0, 1, 1], + "310-mega": [0, 1, 1], + "334-mega": [0, 1, 1], + "354-mega": [0, 1, 1], + "359-mega": [0, 1, 1], + "362-mega": [0, 1, 1], + "373-mega": [0, 1, 1], + "376-mega": [0, 1, 1], + "380-mega": [0, 1, 1], + "381-mega": [0, 1, 1], + "382-primal": [0, 1, 1], + "383-primal": [0, 1, 1], + "384-mega": [0, 1, 1], + "428-mega": [0, 1, 1], + "445-mega": [1, 1, 1], + "448-mega": [1, 1, 1], + "475-mega": [0, 2, 2], + "531-mega": [0, 1, 1], + "653": [0, 1, 1], + "654": [0, 1, 1], + "655": [0, 1, 1], + "656": [0, 1, 1], + "657": [0, 1, 1], + "658": [0, 1, 1], + "658-ash": [0, 1, 1], + "664": [0, 1, 1], + "665": [0, 1, 1], + "666-archipelago": [0, 1, 1], + "666-continental": [0, 1, 1], + "666-elegant": [0, 1, 1], + "666-fancy": [0, 1, 1], + "666-garden": [0, 1, 1], + "666-high-plains": [0, 1, 1], + "666-icy-snow": [0, 1, 1], + "666-jungle": [0, 1, 1], + "666-marine": [0, 1, 1], + "666-meadow": [0, 1, 1], + "666-modern": [0, 1, 1], + "666-monsoon": [0, 1, 1], + "666-ocean": [0, 1, 1], + "666-poke-ball": [0, 1, 1], + "666-polar": [0, 1, 1], + "666-river": [0, 1, 1], + "666-sandstorm": [0, 1, 1], + "666-savanna": [0, 1, 1], + "666-sun": [0, 1, 1], + "666-tundra": [0, 1, 1], + "669-red": [0, 2, 2], + "669-blue": [0, 2, 2], + "669-white": [0, 2, 2], + "669-yellow": [0, 2, 2], + "669-orange": [0, 2, 2], + "670-white": [0, 1, 1], + "670-blue": [0, 2, 2], + "670-orange": [0, 1, 1], + "670-red": [0, 1, 1], + "670-yellow": [0, 1, 1], + "671-red": [0, 1, 1], + "671-blue": [0, 1, 1], + "671-yellow": [0, 1, 1], + "671-white": [0, 1, 1], + "671-orange": [0, 1, 1], + "672": [0, 1, 1], + "673": [0, 1, 1], + "676": [0, 1, 1], + "677": [0, 1, 1], + "678-female": [0, 1, 1], + "678": [0, 1, 1], + "682": [0, 1, 1], + "683": [0, 1, 1], + "684": [0, 1, 1], + "685": [0, 1, 1], + "688": [0, 1, 1], + "689": [0, 1, 1], + "690": [0, 1, 1], + "691": [0, 1, 1], + "696": [0, 1, 1], + "697": [0, 1, 1], + "699": [0, 2, 2], + "700": [0, 1, 1], + "702": [0, 1, 1], + "704": [0, 1, 1], + "705": [0, 1, 1], + "706": [0, 1, 1], + "709": [0, 1, 1], + "710": [0, 1, 1], + "711": [1, 1, 1], + "712": [0, 1, 1], + "713": [0, 1, 1], + "715": [0, 1, 1], + "716-active": [0, 1, 1], + "716-neutral": [0, 1, 1], + "717": [0, 1, 1], + "720-unbound": [1, 1, 1], + "720": [1, 1, 1], + "728": [0, 1, 1], + "729": [0, 1, 1], + "730": [0, 1, 1], + "734": [0, 1, 1], + "735": [0, 1, 1], + "742": [0, 2, 2], + "743": [0, 2, 2], + "747": [0, 2, 2], + "748": [0, 1, 1], + "751": [0, 1, 1], + "752": [0, 1, 1], + "753": [0, 1, 1], + "754": [0, 2, 2], + "755": [0, 1, 1], + "756": [0, 1, 1], + "761": [0, 1, 1], + "762": [0, 1, 1], + "763": [0, 1, 1], + "767": [0, 1, 1], + "768": [0, 1, 1], + "771": [0, 1, 1], + "772": [0, 1, 1], + "773-fighting": [0, 1, 1], + "773-psychic": [0, 1, 1], + "773-poison": [0, 1, 1], + "773-ground": [0, 1, 1], + "773-ghost": [0, 1, 1], + "773-steel": [0, 1, 1], + "773-rock": [0, 1, 1], + "773-grass": [0, 1, 1], + "773-dragon": [0, 1, 1], + "773-bug": [0, 1, 1], + "773-ice": [0, 1, 1], + "773-dark": [0, 1, 1], + "773": [0, 1, 1], + "773-fairy": [0, 1, 1], + "773-water": [0, 1, 1], + "773-electric": [0, 1, 1], + "773-flying": [0, 1, 1], + "773-fire": [0, 1, 1], + "776": [0, 2, 2], + "777": [0, 1, 1], + "778-busted": [0, 1, 1], + "778-disguised": [0, 1, 1], + "779": [0, 1, 1], + "789": [1, 1, 1], + "790": [0, 1, 1], + "791": [1, 1, 1], + "792": [0, 1, 1], + "793": [0, 1, 1], + "797": [0, 1, 1], + "798": [0, 1, 1], + "800-dawn-wings": [0, 1, 1], + "800-dusk-mane": [0, 1, 1], + "800-ultra": [0, 1, 1], + "800": [0, 1, 1], + "802": [1, 1, 1], + "803": [0, 1, 1], + "804": [0, 1, 1], + "807": [0, 1, 1], + "808": [0, 1, 1], + "809": [0, 1, 1], + "816": [0, 1, 1], + "817": [0, 1, 1], + "818": [0, 1, 1], + "821": [0, 1, 1], + "822": [0, 1, 1], + "823": [0, 1, 1], + "829": [0, 1, 1], + "830": [0, 1, 1], + "835": [0, 1, 1], + "836": [0, 1, 1], + "850": [0, 1, 1], + "851": [0, 1, 1], + "854": [0, 1, 1], + "855": [0, 1, 1], + "856": [0, 1, 1], + "857": [0, 2, 2], + "858": [0, 1, 1], + "859": [0, 1, 1], + "860": [0, 1, 1], + "861": [0, 1, 1], + "862": [0, 1, 1], + "863": [0, 1, 1], + "864": [0, 1, 1], + "867": [0, 1, 1], + "872": [1, 1, 1], + "873": [1, 1, 1], + "876-female": [0, 1, 1], + "876": [0, 1, 1], + "877-hangry": [1, 1, 1], + "877": [1, 1, 1], + "880": [0, 1, 1], + "881": [0, 1, 1], + "882": [0, 1, 1], + "883": [0, 1, 1], + "884": [0, 1, 1], + "885": [1, 1, 1], + "886": [1, 1, 1], + "887": [1, 1, 1], + "888": [0, 1, 1], + "888-crowned": [0, 1, 1], + "889": [0, 1, 1], + "889-crowned": [0, 1, 1], + "890": [0, 1, 1], + "891": [1, 1, 1], + "892-rapid-strike": [1, 1, 1], + "892": [1, 1, 1], + "894": [0, 1, 1], + "895": [0, 1, 1], + "896": [1, 1, 1], + "897": [1, 1, 1], + "898": [1, 1, 1], + "898-ice": [1, 1, 1], + "898-shadow": [1, 1, 1], + "900": [0, 1, 1], + "901": [0, 1, 1], + "903": [0, 1, 1], + "909": [0, 1, 1], + "910": [0, 2, 2], + "911": [0, 1, 1], + "912": [0, 1, 1], + "913": [0, 1, 1], + "914": [0, 2, 2], + "919": [1, 1, 1], + "920": [1, 1, 1], + "924": [1, 1, 1], + "925-four": [1, 2, 2], + "925-three": [1, 2, 2], + "932": [0, 1, 1], + "933": [0, 1, 1], + "934": [0, 1, 1], + "935": [2, 2, 2], + "936": [2, 2, 2], + "937": [2, 2, 2], + "940": [0, 1, 1], + "941": [0, 1, 1], + "944": [0, 1, 1], + "945": [0, 1, 1], + "948": [0, 1, 1], + "949": [0, 1, 1], + "951": [0, 1, 1], + "952": [0, 2, 1], + "953": [0, 1, 1], + "954": [0, 1, 1], + "957": [1, 1, 1], + "958": [1, 1, 1], + "959": [1, 1, 1], + "962": [1, 1, 1], + "967": [0, 1, 1], + "968": [0, 2, 2], + "969": [0, 1, 1], + "970": [0, 1, 1], + "973": [1, 1, 1], + "974": [0, 1, 1], + "975": [0, 1, 1], + "978-curly": [0, 2, 2], + "978-droopy": [0, 2, 2], + "978-stretchy": [0, 1, 1], + "979": [1, 1, 1], + "981": [0, 1, 1], + "982": [0, 1, 1], + "982-three-segment": [0, 1, 1], + "987": [1, 1, 1], + "988": [0, 1, 1], + "993": [0, 1, 1], + "994": [0, 1, 1], + "995": [0, 1, 1], + "996": [0, 1, 1], + "997": [0, 1, 1], + "998": [0, 1, 1], + "999": [1, 1, 1], + "1000": [1, 1, 1], + "1001": [0, 1, 1], + "1003": [0, 1, 1], + "1004": [0, 1, 1], + "1006": [0, 2, 2], + "1007-apex-build": [0, 2, 2], + "1008-ultimate-mode": [1, 1, 1], + "2026": [0, 1, 1], + "2027": [0, 1, 1], + "2028": [0, 1, 1], + "2052": [0, 1, 1], + "2053": [0, 1, 1], + "2103": [0, 1, 1], + "4052": [0, 1, 1], + "4077": [0, 1, 1], + "4078": [0, 1, 1], + "4079": [0, 1, 1], + "4080": [2, 2, 2], + "4144": [0, 1, 1], + "4145": [0, 1, 1], + "4146": [0, 1, 1], + "4199": [2, 1, 1], + "4222": [0, 1, 1], + "4263": [0, 1, 1], + "4264": [0, 1, 1], + "4562": [0, 1, 1], + "6100": [0, 1, 1], + "6101": [0, 1, 1], + "6215": [0, 1, 1], + "6503": [0, 1, 1], + "6549": [0, 1, 1], + "6570": [0, 1, 1], + "6571": [0, 1, 1], + "6705": [0, 1, 1], + "6706": [0, 1, 1], + "6713": [0, 1, 1], + "female": { + "6215": [0, 1, 1] + } + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/_masterlist.json b/public/images/pokemon/variant/_masterlist.json index 175b56139a6..ac683d9544e 100644 --- a/public/images/pokemon/variant/_masterlist.json +++ b/public/images/pokemon/variant/_masterlist.json @@ -1813,669 +1813,5 @@ "593": [1, 1, 1], "6215": [0, 1, 1] } - }, - "exp": { - "3-mega": [0, 2, 2], - "6-mega-x": [0, 2, 2], - "6-mega-y": [0, 2, 2], - "80-mega": [0, 1, 1], - "94-mega": [2, 2, 2], - "127-mega": [0, 1, 1], - "130-mega": [0, 1, 1], - "142-mega": [0, 1, 1], - "150-mega-x": [0, 1, 1], - "150-mega-y": [0, 1, 1], - "181-mega": [0, 1, 2], - "212-mega": [1, 1, 2], - "229-mega": [0, 1, 1], - "248-mega": [0, 1, 1], - "257-mega": [0, 1, 1], - "282-mega": [0, 2, 2], - "302-mega": [0, 1, 1], - "303-mega": [0, 1, 1], - "306-mega": [1, 1, 1], - "308-mega": [0, 1, 1], - "310-mega": [0, 1, 1], - "334-mega": [0, 2, 1], - "354-mega": [0, 1, 1], - "359-mega": [0, 1, 1], - "362-mega": [0, 1, 1], - "373-mega": [0, 1, 1], - "376-mega": [0, 1, 1], - "380-mega": [0, 1, 1], - "381-mega": [0, 1, 1], - "382-primal": [0, 1, 1], - "383-primal": [0, 1, 1], - "384-mega": [0, 2, 1], - "428-mega": [0, 1, 1], - "445-mega": [1, 1, 1], - "448-mega": [1, 1, 1], - "475-mega": [0, 2, 2], - "531-mega": [0, 1, 1], - "653": [0, 1, 1], - "654": [0, 1, 1], - "655": [0, 1, 1], - "656": [0, 1, 1], - "657": [0, 1, 1], - "658": [0, 1, 1], - "658-ash": [0, 1, 1], - "664": [0, 1, 1], - "665": [0, 1, 1], - "666-archipelago": [0, 1, 1], - "666-continental": [0, 1, 1], - "666-elegant": [0, 1, 1], - "666-fancy": [0, 1, 1], - "666-garden": [0, 1, 1], - "666-high-plains": [0, 1, 1], - "666-icy-snow": [0, 1, 1], - "666-jungle": [0, 1, 1], - "666-marine": [0, 1, 1], - "666-meadow": [0, 1, 1], - "666-modern": [0, 1, 1], - "666-monsoon": [0, 1, 1], - "666-ocean": [0, 1, 1], - "666-poke-ball": [0, 1, 1], - "666-polar": [0, 1, 1], - "666-river": [0, 1, 1], - "666-sandstorm": [0, 1, 1], - "666-savanna": [0, 1, 1], - "666-sun": [0, 1, 1], - "666-tundra": [0, 1, 1], - "669-red": [0, 2, 2], - "669-blue": [0, 1, 1], - "669-white": [0, 1, 1], - "669-yellow": [0, 1, 1], - "669-orange": [0, 2, 2], - "670-white": [0, 1, 1], - "670-blue": [0, 1, 1], - "670-orange": [0, 1, 1], - "670-red": [0, 1, 1], - "670-yellow": [0, 1, 1], - "671-red": [0, 1, 2], - "671-blue": [0, 1, 2], - "671-yellow": [0, 1, 1], - "671-white": [0, 1, 2], - "671-orange": [0, 1, 2], - "672": [0, 1, 1], - "673": [0, 1, 1], - "676": [0, 1, 1], - "677": [0, 1, 1], - "678-female": [0, 1, 1], - "678": [0, 1, 1], - "682": [0, 1, 1], - "683": [0, 1, 1], - "684": [0, 1, 1], - "685": [0, 1, 1], - "688": [0, 1, 1], - "689": [0, 1, 1], - "690": [0, 1, 1], - "691": [0, 1, 1], - "696": [0, 1, 1], - "697": [0, 1, 1], - "698": [0, 1, 1], - "699": [0, 1, 1], - "700": [0, 1, 1], - "702": [0, 1, 1], - "703": [0, 1, 1], - "704": [0, 1, 1], - "705": [0, 1, 1], - "706": [0, 1, 1], - "708": [0, 1, 1], - "709": [0, 1, 1], - "710": [0, 1, 1], - "711": [1, 1, 1], - "712": [0, 1, 1], - "713": [0, 1, 1], - "714": [0, 1, 1], - "715": [0, 1, 1], - "716-active": [0, 1, 1], - "716-neutral": [0, 1, 1], - "717": [0, 2, 2], - "720-unbound": [1, 1, 1], - "720": [1, 1, 1], - "728": [0, 1, 1], - "729": [0, 1, 1], - "730": [0, 1, 1], - "734": [0, 1, 1], - "735": [0, 1, 1], - "742": [0, 2, 2], - "743": [0, 2, 2], - "747": [0, 2, 2], - "748": [0, 1, 1], - "751": [0, 1, 1], - "752": [0, 1, 1], - "753": [0, 1, 1], - "754": [0, 2, 2], - "755": [0, 1, 1], - "756": [0, 1, 1], - "761": [0, 1, 1], - "762": [0, 1, 1], - "763": [0, 1, 1], - "767": [0, 1, 1], - "768": [0, 1, 1], - "770": [0, 0, 0], - "771": [0, 2, 2], - "772": [0, 1, 1], - "773-fighting": [0, 1, 1], - "773-psychic": [0, 1, 1], - "773-poison": [0, 1, 1], - "773-ground": [0, 1, 1], - "773-ghost": [0, 1, 1], - "773-steel": [0, 1, 1], - "773-rock": [0, 1, 1], - "773-grass": [0, 1, 1], - "773-dragon": [0, 1, 1], - "773-bug": [0, 1, 1], - "773-ice": [0, 1, 1], - "773-dark": [0, 1, 1], - "773": [0, 1, 1], - "773-fairy": [0, 1, 1], - "773-water": [0, 1, 1], - "773-electric": [0, 1, 1], - "773-flying": [0, 1, 1], - "773-fire": [0, 1, 1], - "776": [0, 1, 1], - "777": [0, 1, 1], - "778-busted": [0, 1, 1], - "778-disguised": [0, 1, 1], - "779": [0, 1, 1], - "789": [1, 1, 1], - "790": [0, 1, 1], - "791": [2, 1, 1], - "792": [0, 1, 1], - "793": [0, 2, 2], - "797": [0, 1, 1], - "798": [0, 1, 1], - "800-dawn-wings": [0, 1, 1], - "800-dusk-mane": [0, 1, 1], - "800-ultra": [0, 1, 1], - "800": [0, 1, 1], - "802": [1, 1, 1], - "803": [0, 1, 1], - "804": [0, 1, 1], - "807": [0, 1, 1], - "808": [0, 1, 1], - "809": [0, 1, 1], - "816": [0, 1, 1], - "817": [0, 1, 1], - "818": [1, 1, 1], - "821": [0, 2, 2], - "822": [0, 1, 1], - "823": [0, 1, 1], - "829": [0, 1, 1], - "830": [0, 1, 1], - "835": [0, 1, 1], - "836": [0, 2, 2], - "850": [0, 1, 1], - "851": [0, 1, 1], - "854": [0, 1, 1], - "855": [0, 1, 1], - "856": [0, 1, 1], - "857": [0, 2, 2], - "858": [0, 1, 1], - "859": [0, 1, 1], - "860": [0, 1, 1], - "861": [0, 1, 1], - "862": [0, 1, 1], - "863": [0, 1, 1], - "864": [0, 1, 1], - "867": [0, 1, 1], - "872": [1, 1, 1], - "873": [1, 1, 1], - "876-female": [0, 1, 1], - "876": [0, 1, 1], - "877-hangry": [1, 1, 1], - "877": [1, 1, 1], - "880": [0, 1, 1], - "881": [0, 1, 1], - "882": [0, 2, 1], - "883": [0, 1, 1], - "884": [0, 1, 1], - "885": [1, 1, 1], - "886": [1, 1, 1], - "887": [1, 1, 1], - "888": [0, 1, 1], - "888-crowned": [0, 1, 1], - "889": [0, 1, 1], - "889-crowned": [0, 1, 1], - "890": [0, 2, 1], - "890-eternamax": [0, 1, 1], - "891": [1, 1, 1], - "892-rapid-strike": [1, 1, 1], - "892": [1, 1, 1], - "894": [0, 1, 1], - "895": [0, 1, 1], - "896": [1, 1, 1], - "897": [1, 1, 1], - "898": [1, 1, 1], - "898-ice": [1, 1, 1], - "898-shadow": [1, 1, 1], - "900": [0, 1, 1], - "901": [0, 1, 1], - "903": [0, 1, 1], - "909": [0, 1, 1], - "910": [0, 2, 2], - "911": [0, 2, 2], - "912": [0, 1, 2], - "913": [0, 1, 2], - "914": [0, 2, 1], - "919": [1, 1, 1], - "920": [1, 1, 1], - "924": [1, 1, 1], - "925-four": [1, 2, 2], - "925-three": [1, 2, 2], - "932": [0, 2, 2], - "933": [0, 2, 2], - "934": [0, 1, 1], - "935": [1, 1, 2], - "936": [2, 2, 2], - "937": [2, 2, 2], - "940": [0, 1, 1], - "941": [0, 1, 1], - "944": [0, 1, 1], - "945": [0, 1, 1], - "948": [0, 1, 1], - "949": [0, 1, 1], - "951": [0, 1, 1], - "952": [0, 1, 1], - "953": [0, 1, 1], - "954": [0, 1, 1], - "957": [2, 2, 2], - "958": [2, 2, 2], - "959": [2, 2, 2], - "962": [1, 1, 1], - "967": [0, 1, 1], - "968": [0, 1, 1], - "969": [0, 1, 1], - "970": [0, 1, 1], - "973": [1, 1, 1], - "974": [0, 1, 1], - "975": [0, 1, 1], - "978-curly": [0, 2, 2], - "978-droopy": [0, 2, 2], - "978-stretchy": [0, 2, 2], - "979": [2, 2, 2], - "981": [0, 1, 1], - "982": [0, 1, 1], - "982-three-segment": [0, 1, 1], - "987": [1, 1, 1], - "988": [0, 1, 2], - "993": [0, 1, 1], - "994": [0, 1, 2], - "995": [0, 1, 1], - "996": [0, 1, 1], - "997": [0, 2, 2], - "998": [0, 2, 2], - "999": [2, 1, 1], - "1000": [1, 1, 1], - "1001": [0, 1, 1], - "1003": [0, 1, 1], - "1004": [0, 1, 1], - "1006": [0, 2, 1], - "1007-apex-build": [0, 2, 2], - "1008-ultimate-mode": [1, 1, 1], - "2026": [0, 1, 1], - "2027": [0, 1, 1], - "2028": [0, 1, 1], - "2052": [0, 1, 1], - "2053": [0, 1, 0], - "2103": [0, 1, 1], - "4052": [0, 1, 1], - "4077": [0, 1, 1], - "4078": [0, 1, 1], - "4079": [0, 1, 1], - "4080": [2, 1, 1], - "4144": [0, 1, 1], - "4145": [0, 1, 1], - "4146": [0, 1, 1], - "4199": [2, 1, 1], - "4222": [0, 1, 1], - "4263": [0, 1, 1], - "4264": [0, 1, 1], - "4562": [0, 1, 1], - "6100": [0, 1, 1], - "6101": [0, 1, 1], - "6215": [0, 1, 1], - "6503": [0, 1, 1], - "6549": [0, 1, 1], - "6570": [0, 1, 1], - "6571": [0, 1, 1], - "6705": [0, 1, 1], - "6706": [0, 1, 1], - "6713": [0, 1, 1], - "female": { - "6215": [0, 1, 1] - }, - "back": { - "3-mega": [0, 2, 2], - "6-mega-x": [0, 2, 2], - "6-mega-y": [0, 1, 2], - "80-mega": [0, 1, 1], - "94-mega": [1, 1, 1], - "127-mega": [0, 1, 1], - "130-mega": [0, 1, 1], - "142-mega": [0, 1, 1], - "150-mega-x": [0, 1, 1], - "150-mega-y": [0, 1, 1], - "181-mega": [0, 1, 2], - "212-mega": [1, 2, 2], - "229-mega": [0, 1, 1], - "248-mega": [0, 1, 1], - "257-mega": [0, 1, 1], - "282-mega": [0, 1, 1], - "302-mega": [0, 1, 1], - "303-mega": [0, 1, 1], - "306-mega": [1, 1, 1], - "308-mega": [0, 1, 1], - "310-mega": [0, 1, 1], - "334-mega": [0, 1, 1], - "354-mega": [0, 1, 1], - "359-mega": [0, 1, 1], - "362-mega": [0, 1, 1], - "373-mega": [0, 1, 1], - "376-mega": [0, 1, 1], - "380-mega": [0, 1, 1], - "381-mega": [0, 1, 1], - "382-primal": [0, 1, 1], - "383-primal": [0, 1, 1], - "384-mega": [0, 1, 1], - "428-mega": [0, 1, 1], - "445-mega": [1, 1, 1], - "448-mega": [1, 1, 1], - "475-mega": [0, 2, 2], - "531-mega": [0, 1, 1], - "653": [0, 1, 1], - "654": [0, 1, 1], - "655": [0, 1, 1], - "656": [0, 1, 1], - "657": [0, 1, 1], - "658": [0, 1, 1], - "658-ash": [0, 1, 1], - "664": [0, 1, 1], - "665": [0, 1, 1], - "666-archipelago": [0, 1, 1], - "666-continental": [0, 1, 1], - "666-elegant": [0, 1, 1], - "666-fancy": [0, 1, 1], - "666-garden": [0, 1, 1], - "666-high-plains": [0, 1, 1], - "666-icy-snow": [0, 1, 1], - "666-jungle": [0, 1, 1], - "666-marine": [0, 1, 1], - "666-meadow": [0, 1, 1], - "666-modern": [0, 1, 1], - "666-monsoon": [0, 1, 1], - "666-ocean": [0, 1, 1], - "666-poke-ball": [0, 1, 1], - "666-polar": [0, 1, 1], - "666-river": [0, 1, 1], - "666-sandstorm": [0, 1, 1], - "666-savanna": [0, 1, 1], - "666-sun": [0, 1, 1], - "666-tundra": [0, 1, 1], - "669-red": [0, 2, 2], - "669-blue": [0, 2, 2], - "669-white": [0, 2, 2], - "669-yellow": [0, 2, 2], - "669-orange": [0, 2, 2], - "670-white": [0, 1, 1], - "670-blue": [0, 2, 2], - "670-orange": [0, 1, 1], - "670-red": [0, 1, 1], - "670-yellow": [0, 1, 1], - "671-red": [0, 1, 1], - "671-blue": [0, 1, 1], - "671-yellow": [0, 1, 1], - "671-white": [0, 1, 1], - "671-orange": [0, 1, 1], - "672": [0, 1, 1], - "673": [0, 1, 1], - "676": [0, 1, 1], - "677": [0, 1, 1], - "678-female": [0, 1, 1], - "678": [0, 1, 1], - "682": [0, 1, 1], - "683": [0, 1, 1], - "684": [0, 1, 1], - "685": [0, 1, 1], - "688": [0, 1, 1], - "689": [0, 1, 1], - "690": [0, 1, 1], - "691": [0, 1, 1], - "696": [0, 1, 1], - "697": [0, 1, 1], - "698": [0, 1, 1], - "699": [0, 2, 2], - "700": [0, 1, 1], - "702": [0, 1, 1], - "703": [0, 1, 1], - "704": [0, 1, 1], - "705": [0, 1, 1], - "706": [0, 1, 1], - "708": [0, 1, 1], - "709": [0, 1, 1], - "710": [0, 1, 1], - "711": [1, 1, 1], - "712": [0, 1, 1], - "713": [0, 1, 1], - "714": [0, 1, 1], - "715": [0, 1, 1], - "716-active": [0, 1, 1], - "716-neutral": [0, 1, 1], - "717": [0, 1, 1], - "720-unbound": [1, 1, 1], - "720": [1, 1, 1], - "728": [0, 1, 1], - "729": [0, 1, 1], - "730": [0, 1, 1], - "734": [0, 1, 1], - "735": [0, 1, 1], - "742": [0, 2, 2], - "743": [0, 2, 2], - "747": [0, 2, 2], - "748": [0, 1, 1], - "751": [0, 1, 1], - "752": [0, 1, 1], - "753": [0, 1, 1], - "754": [0, 2, 2], - "755": [0, 1, 1], - "756": [0, 1, 1], - "761": [0, 1, 1], - "762": [0, 1, 1], - "763": [0, 1, 1], - "767": [0, 1, 1], - "768": [0, 1, 1], - "771": [0, 1, 1], - "772": [0, 1, 1], - "773-fighting": [0, 1, 1], - "773-psychic": [0, 1, 1], - "773-poison": [0, 1, 1], - "773-ground": [0, 1, 1], - "773-ghost": [0, 1, 1], - "773-steel": [0, 1, 1], - "773-rock": [0, 1, 1], - "773-grass": [0, 1, 1], - "773-dragon": [0, 1, 1], - "773-bug": [0, 1, 1], - "773-ice": [0, 1, 1], - "773-dark": [0, 1, 1], - "773": [0, 1, 1], - "773-fairy": [0, 1, 1], - "773-water": [0, 1, 1], - "773-electric": [0, 1, 1], - "773-flying": [0, 1, 1], - "773-fire": [0, 1, 1], - "776": [0, 2, 2], - "777": [0, 1, 1], - "778-busted": [0, 1, 1], - "778-disguised": [0, 1, 1], - "779": [0, 1, 1], - "789": [1, 1, 1], - "790": [0, 1, 1], - "791": [1, 1, 1], - "792": [0, 1, 1], - "793": [0, 1, 1], - "797": [0, 1, 1], - "798": [0, 1, 1], - "800-dawn-wings": [0, 1, 1], - "800-dusk-mane": [0, 1, 1], - "800-ultra": [0, 1, 1], - "800": [0, 1, 1], - "802": [1, 1, 1], - "803": [0, 1, 1], - "804": [0, 1, 1], - "807": [0, 1, 1], - "808": [0, 1, 1], - "809": [0, 1, 1], - "816": [0, 1, 1], - "817": [0, 1, 1], - "818": [0, 1, 1], - "821": [0, 1, 1], - "822": [0, 1, 1], - "823": [0, 1, 1], - "829": [0, 1, 1], - "830": [0, 1, 1], - "835": [0, 1, 1], - "836": [0, 1, 1], - "850": [0, 1, 1], - "851": [0, 1, 1], - "854": [0, 1, 1], - "855": [0, 1, 1], - "856": [0, 1, 1], - "857": [0, 2, 2], - "858": [0, 1, 1], - "859": [0, 1, 1], - "860": [0, 1, 1], - "861": [0, 1, 1], - "862": [0, 1, 1], - "863": [0, 1, 1], - "864": [0, 1, 1], - "867": [0, 1, 1], - "872": [1, 1, 1], - "873": [1, 1, 1], - "876-female": [0, 1, 1], - "876": [0, 1, 1], - "877-hangry": [1, 1, 1], - "877": [1, 1, 1], - "880": [0, 1, 1], - "881": [0, 1, 1], - "882": [0, 1, 1], - "883": [0, 1, 1], - "884": [0, 1, 1], - "885": [1, 1, 1], - "886": [1, 1, 1], - "887": [1, 1, 1], - "888": [0, 1, 1], - "888-crowned": [0, 1, 1], - "889": [0, 1, 1], - "889-crowned": [0, 1, 1], - "890": [0, 1, 1], - "891": [1, 1, 1], - "892-rapid-strike": [1, 1, 1], - "892": [1, 1, 1], - "894": [0, 1, 1], - "895": [0, 1, 1], - "896": [1, 1, 1], - "897": [1, 1, 1], - "898": [1, 1, 1], - "898-ice": [1, 1, 1], - "898-shadow": [1, 1, 1], - "900": [0, 1, 1], - "901": [0, 1, 1], - "903": [0, 1, 1], - "909": [0, 1, 1], - "910": [0, 2, 2], - "911": [0, 1, 1], - "912": [0, 1, 1], - "913": [0, 1, 1], - "914": [0, 2, 2], - "919": [1, 1, 1], - "920": [1, 1, 1], - "924": [1, 1, 1], - "925-four": [1, 2, 2], - "925-three": [1, 2, 2], - "932": [0, 1, 1], - "933": [0, 1, 1], - "934": [0, 1, 1], - "935": [2, 2, 2], - "936": [2, 2, 2], - "937": [2, 2, 2], - "940": [0, 1, 1], - "941": [0, 1, 1], - "944": [0, 1, 1], - "945": [0, 1, 1], - "948": [0, 1, 1], - "949": [0, 1, 1], - "951": [0, 1, 1], - "952": [0, 2, 1], - "953": [0, 1, 1], - "954": [0, 1, 1], - "957": [1, 1, 1], - "958": [1, 1, 1], - "959": [1, 1, 1], - "962": [1, 1, 1], - "967": [0, 1, 1], - "968": [0, 2, 2], - "969": [0, 1, 1], - "970": [0, 1, 1], - "973": [1, 1, 1], - "974": [0, 1, 1], - "975": [0, 1, 1], - "978-curly": [0, 2, 2], - "978-droopy": [0, 2, 2], - "978-stretchy": [0, 1, 1], - "979": [1, 1, 1], - "981": [0, 1, 1], - "982": [0, 1, 1], - "982-three-segment": [0, 1, 1], - "987": [1, 1, 1], - "988": [0, 1, 1], - "993": [0, 1, 1], - "994": [0, 1, 1], - "995": [0, 1, 1], - "996": [0, 1, 1], - "997": [0, 1, 1], - "998": [0, 1, 1], - "999": [1, 1, 1], - "1000": [1, 1, 1], - "1001": [0, 1, 1], - "1003": [0, 1, 1], - "1004": [0, 1, 1], - "1006": [0, 2, 2], - "1007-apex-build": [0, 2, 2], - "1008-ultimate-mode": [1, 1, 1], - "2026": [0, 1, 1], - "2027": [0, 1, 1], - "2028": [0, 1, 1], - "2052": [0, 1, 1], - "2053": [0, 1, 1], - "2103": [0, 1, 1], - "4052": [0, 1, 1], - "4077": [0, 1, 1], - "4078": [0, 1, 1], - "4079": [0, 1, 1], - "4080": [2, 2, 2], - "4144": [0, 1, 1], - "4145": [0, 1, 1], - "4146": [0, 1, 1], - "4199": [2, 1, 1], - "4222": [0, 1, 1], - "4263": [0, 1, 1], - "4264": [0, 1, 1], - "4562": [0, 1, 1], - "6100": [0, 1, 1], - "6101": [0, 1, 1], - "6215": [0, 1, 1], - "6503": [0, 1, 1], - "6549": [0, 1, 1], - "6570": [0, 1, 1], - "6571": [0, 1, 1], - "6705": [0, 1, 1], - "6706": [0, 1, 1], - "6713": [0, 1, 1], - "female": { - "6215": [0, 1, 1] - } - } } } \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/698.json b/public/images/pokemon/variant/exp/698.json deleted file mode 100644 index daf9b8c6f84..00000000000 --- a/public/images/pokemon/variant/exp/698.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "1": { - "cbaa84": "44827c", - "b3747e": "4b7465", - "eeffbf": "cdffb5", - "dcffb2": "8eeab9", - "ffbfca": "43bf8d", - "b7ffb2": "72d8ce", - "fff2b2": "9bffa9", - "85b4cc": "cf755d", - "a6e1ff": "efab87", - "101010": "101010", - "cacaca": "cacaca", - "537180": "b04f4b", - "2eaeec": "4dc796", - "1f75a0": "29988e", - "fdfdfd": "fdfdfd", - "d197a1": "d197a1", - "ffdce6": "ffdce6", - "217aa6": "7f99e1", - "30b2f2": "b5dcff", - "f9f9f9": "e6e3b4", - "c0c0c0": "d7cca0" - }, - "2": { - "cbaa84": "cc78db", - "b3747e": "c452a6", - "eeffbf": "ed9ff2", - "dcffb2": "d7bbf4", - "ffbfca": "faccff", - "b7ffb2": "dceeff", - "fff2b2": "eb88b9", - "85b4cc": "654a8a", - "a6e1ff": "936daa", - "101010": "101010", - "cacaca": "cacaca", - "537180": "392d65", - "2eaeec": "ad4e6e", - "1f75a0": "8d2656", - "fdfdfd": "fdfdfd", - "d197a1": "d197a1", - "ffdce6": "ffdce6", - "217aa6": "efaa51", - "30b2f2": "ffd169", - "f9f9f9": "373453", - "c0c0c0": "282747" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/703.json b/public/images/pokemon/variant/exp/703.json deleted file mode 100644 index c024feb1b30..00000000000 --- a/public/images/pokemon/variant/exp/703.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "1": { - "535763": "292638", - "306090": "c35b2a", - "c3c7d3": "68638e", - "88aacc": "e67c37", - "fefefe": "fefefe", - "a3a7b3": "4d496b", - "737783": "37344e", - "101010": "101010", - "bbddff": "ffa633", - "1fbfdf": "ff9b44", - "5f6060": "e6ac60", - "fcfefe": "ffeed6", - "bfbbbb": "ffd3a1" - }, - "2": { - "535763": "976ba9", - "306090": "a03c69", - "c3c7d3": "faecff", - "88aacc": "e25493", - "fefefe": "ffe2ee", - "a3a7b3": "e4cdf9", - "737783": "cca1db", - "101010": "101010", - "bbddff": "f591bd", - "1fbfdf": "de5f8e", - "5f6060": "5a3d84", - "fcfefe": "a473bf", - "bfbbbb": "8359a7" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/708.json b/public/images/pokemon/variant/exp/708.json deleted file mode 100644 index b32bbb79cd9..00000000000 --- a/public/images/pokemon/variant/exp/708.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "1": { - "101010": "101010", - "2b2a3a": "722023", - "603d2b": "36384f", - "215738": "4d362e", - "48484a": "a14743", - "c18760": "7c808c", - "3fa76c": "907f76", - "915e45": "575a6a", - "0b0c0b": "0b0c0b", - "da585b": "5996d2", - "ff8c8f": "87d1ff" - }, - "2": { - "101010": "101010", - "2b2a3a": "6f5f80", - "603d2b": "31161d", - "215738": "a94079", - "48484a": "9c92a4", - "c18760": "7e5658", - "3fa76c": "da7ea8", - "915e45": "56323a", - "0b0c0b": "0b0c0b", - "da585b": "e18933", - "ff8c8f": "ffc875" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/714.json b/public/images/pokemon/variant/exp/714.json deleted file mode 100644 index 018366c5381..00000000000 --- a/public/images/pokemon/variant/exp/714.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "1": { - "6a3f73": "731338", - "bd70cc": "a42c54", - "101010": "101010", - "bfacbf": "7047ba", - "8e5499": "8e1d4b", - "f2daf2": "8d7be3", - "404040": "202558", - "665c66": "2f386b", - "ccb43d": "ff8a58", - "f8f8f8": "8d7be3", - "595959": "2f386b", - "ffe14c": "ffc182", - "000000": "101010" - }, - "2": { - "6a3f73": "5f151c", - "bd70cc": "c24430", - "101010": "101010", - "bfacbf": "f9e8dd", - "8e5499": "882c27", - "f2daf2": "f8f8f8", - "404040": "5b1922", - "665c66": "7c2928", - "ccb43d": "33d8d0", - "f8f8f8": "f8f8f8", - "595959": "7c2928", - "ffe14c": "49ffcd", - "000000": "101010" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/back/698.json b/public/images/pokemon/variant/exp/back/698.json deleted file mode 100644 index af193c3bc0c..00000000000 --- a/public/images/pokemon/variant/exp/back/698.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "1": { - "b3747e": "4b7465", - "ffbfca": "43bf8d", - "fff2b2": "9bffa9", - "537180": "b04f4b", - "a6e1ff": "efab87", - "101010": "101010", - "85b4cc": "cf755d", - "217aa6": "7f99e1", - "30b2f2": "b5dcff", - "fdfdfd": "fdfdfd", - "c0c0c0": "d7cca0", - "cacaca": "cacaca", - "cbaa84": "44827c", - "dcffb2": "8eeab9", - "eeffbf": "cdffb5", - "b7ffb2": "72d8ce" - }, - "2": { - "b3747e": "c452a6", - "ffbfca": "faccff", - "fff2b2": "eb88b9", - "537180": "392d65", - "a6e1ff": "936daa", - "101010": "101010", - "85b4cc": "654a8a", - "217aa6": "efaa51", - "30b2f2": "ffd169", - "fdfdfd": "fdfdfd", - "c0c0c0": "282747", - "cacaca": "cacaca", - "cbaa84": "cc78db", - "dcffb2": "d7bbf4", - "eeffbf": "ed9ff2", - "b7ffb2": "dceeff" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/back/703.json b/public/images/pokemon/variant/exp/back/703.json deleted file mode 100644 index 376abd466d2..00000000000 --- a/public/images/pokemon/variant/exp/back/703.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "1": { - "306090": "c35b2a", - "88aacc": "e67c37", - "fefefe": "fefefe", - "535763": "292638", - "a3a7b3": "4d496b", - "737783": "37344e", - "bbddff": "ffa633", - "101010": "101010", - "5f6060": "e6ac60", - "bfbbbb": "ffd3a1", - "fcfefe": "ffeed6" - }, - "2": { - "306090": "a03c69", - "88aacc": "e25493", - "fefefe": "ffe2ee", - "535763": "976ba9", - "a3a7b3": "e4cdf9", - "737783": "cca1db", - "bbddff": "f591bd", - "101010": "101010", - "5f6060": "5a3d84", - "bfbbbb": "8359a7", - "fcfefe": "a473bf" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/back/708.json b/public/images/pokemon/variant/exp/back/708.json deleted file mode 100644 index 7d41d6d24b0..00000000000 --- a/public/images/pokemon/variant/exp/back/708.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "1": { - "1a1a1c": "1a1a1c", - "686665": "646085", - "103222": "802c26", - "221b17": "221b17", - "090606": "090606", - "4ab38e": "a14743", - "38956f": "a14743", - "ab9074": "7c808c", - "4e4e4e": "494e5b", - "917860": "7c808c", - "424244": "2b303c", - "78604c": "575a6a", - "6b5442": "40435a", - "5f4939": "36384f", - "4f2a09": "292929", - "6c4513": "36384f", - "353638": "353638" - }, - "2": { - "1a1a1c": "1a1a1c", - "686665": "ccc3cf", - "103222": "a94079", - "221b17": "221b17", - "090606": "090606", - "4ab38e": "da7ea8", - "38956f": "da7ea8", - "ab9074": "7e5658", - "4e4e4e": "9c92a4", - "917860": "7e5658", - "424244": "6f5f80", - "78604c": "56323a", - "6b5442": "47232b", - "5f4939": "31161d", - "4f2a09": "250e14", - "6c4513": "31161d", - "353638": "57496b" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/back/714.json b/public/images/pokemon/variant/exp/back/714.json deleted file mode 100644 index 22933e71338..00000000000 --- a/public/images/pokemon/variant/exp/back/714.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "1": { - "101010": "101010", - "6a3f73": "500a25", - "bd70cc": "a42c54", - "8e5499": "8e1d4b", - "404040": "202558", - "595959": "2f386b", - "bfacbf": "8d7be3", - "665c66": "2f386b", - "f2daf2": "8d7be3" - }, - "2": { - "101010": "101010", - "6a3f73": "5f151c", - "bd70cc": "c24430", - "8e5499": "882c27", - "404040": "5b1922", - "595959": "7c2928", - "bfacbf": "f9e8dd", - "665c66": "7c2928", - "f2daf2": "f9e8dd" - } -} \ No newline at end of file diff --git a/scripts/find_sprite_variant_mismatches.py b/scripts/find_sprite_variant_mismatches.py index 483695fdb66..b26058c2de3 100644 --- a/scripts/find_sprite_variant_mismatches.py +++ b/scripts/find_sprite_variant_mismatches.py @@ -22,6 +22,9 @@ from typing import Literal as L MASTERLIST_PATH = os.path.join( os.path.dirname(os.path.dirname(__file__)), "public", "images", "pokemon", "variant", "_masterlist.json" ) +EXP_MASTERLIST_PATH = os.path.join( + os.path.dirname(os.path.dirname(__file__)), "public", "images", "pokemon", "variant", "_exp_masterlist.json" +) DEFAULT_OUTPUT_PATH = "sprite-mismatches.csv" @@ -93,6 +96,7 @@ if __name__ == "__main__": help=f"The path to a file to save the output file. If not specified, will write to {DEFAULT_OUTPUT_PATH}.", ) p.add_argument("--masterlist", default=MASTERLIST_PATH, help=f"The path to the masterlist file to validate. Defaults to {MASTERLIST_PATH}.") + p.add_argument("--exp-masterlist", default=EXP_MASTERLIST_PATH, help=f"The path to the exp masterlist file to validate against. Defaults to {EXP_MASTERLIST_PATH}.") args = p.parse_args() mismatches = make_mismatch_sprite_list(args.masterlist) write_mismatch_csv(args.output, mismatches) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index f676ba63306..acc8dafdd35 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -106,8 +106,8 @@ import PokemonInfoContainer from "#app/ui/pokemon-info-container"; import { biomeDepths, getBiomeName } from "#app/data/balance/biomes"; import { SceneBase } from "#app/scene-base"; import CandyBar from "#app/ui/candy-bar"; -import type { Variant, VariantSet } from "#app/data/variant"; -import { variantColorCache, variantData } from "#app/data/variant"; +import type { Variant } from "#app/sprites/variant"; +import { variantData, clearVariantData } from "#app/sprites/variant"; import type { Localizable } from "#app/interfaces/locales"; import Overrides from "#app/overrides"; import { InputsController } from "#app/inputs-controller"; @@ -170,6 +170,8 @@ import { StatusEffect } from "#enums/status-effect"; import { initGlobalScene } from "#app/global-scene"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import { HideAbilityPhase } from "#app/phases/hide-ability-phase"; +import { expSpriteKeys } from "./sprites/sprite-keys"; +import { hasExpSprite } from "./sprites/sprite-utils"; import { timedEventManager } from "./global-event-manager"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; @@ -182,8 +184,6 @@ const OPP_IVS_OVERRIDE_VALIDATED: number[] = ( export const startingWave = Overrides.STARTING_WAVE_OVERRIDE || 1; -const expSpriteKeys: string[] = []; - export let starterColors: StarterColors; interface StarterColors { [key: string]: [string, string]; @@ -409,7 +409,7 @@ export default class BattleScene extends SceneBase { } const variant = atlasPath.includes("variant/") || /_[0-3]$/.test(atlasPath); if (experimental) { - experimental = this.hasExpSprite(key); + experimental = hasExpSprite(key); } if (variant) { atlasPath = atlasPath.replace("variant/", ""); @@ -421,35 +421,6 @@ export default class BattleScene extends SceneBase { ); } - /** - * Load the variant assets for the given sprite and stores them in {@linkcode variantColorCache} - */ - public async loadPokemonVariantAssets(spriteKey: string, fileRoot: string, variant?: Variant): Promise { - const useExpSprite = this.experimentalSprites && this.hasExpSprite(spriteKey); - if (useExpSprite) { - fileRoot = `exp/${fileRoot}`; - } - let variantConfig = variantData; - fileRoot.split("/").map(p => (variantConfig ? (variantConfig = variantConfig[p]) : null)); - const variantSet = variantConfig as VariantSet; - - return new Promise(resolve => { - if (variantSet && variant !== undefined && variantSet[variant] === 1) { - if (variantColorCache.hasOwnProperty(spriteKey)) { - return resolve(); - } - this.cachedFetch(`./images/pokemon/variant/${fileRoot}.json`) - .then(res => res.json()) - .then(c => { - variantColorCache[spriteKey] = c; - resolve(); - }); - } else { - resolve(); - } - }); - } - async preload() { if (DEBUG_RNG) { const originalRealInRange = Phaser.Math.RND.realInRange; @@ -783,53 +754,36 @@ export default class BattleScene extends SceneBase { } async initExpSprites(): Promise { - if (expSpriteKeys.length) { + if (expSpriteKeys.size > 0) { return; } this.cachedFetch("./exp-sprites.json") .then(res => res.json()) .then(keys => { if (Array.isArray(keys)) { - expSpriteKeys.push(...keys); + for (const key of keys) { + expSpriteKeys.add(key); + } } Promise.resolve(); }); } + /** + * Initialize the variant data. + * If experimental sprites are enabled, their entries are replaced via this method. + */ async initVariantData(): Promise { - for (const key of Object.keys(variantData)) { - delete variantData[key]; + clearVariantData(); + const otherVariantData = await this.cachedFetch("./images/pokemon/variant/_masterlist.json").then(r => r.json()); + for (const k of Object.keys(otherVariantData)) { + variantData[k] = otherVariantData[k]; } - await this.cachedFetch("./images/pokemon/variant/_masterlist.json") - .then(res => res.json()) - .then(v => { - for (const k of Object.keys(v)) { - variantData[k] = v[k]; - } - if (this.experimentalSprites) { - const expVariantData = variantData["exp"]; - const traverseVariantData = (keys: string[]) => { - let variantTree = variantData; - let expTree = expVariantData; - keys.map((k: string, i: number) => { - if (i < keys.length - 1) { - variantTree = variantTree[k]; - expTree = expTree[k]; - } else if (variantTree.hasOwnProperty(k) && expTree.hasOwnProperty(k)) { - if (["back", "female"].includes(k)) { - traverseVariantData(keys.concat(k)); - } else { - variantTree[k] = expTree[k]; - } - } - }); - }; - for (const ek of Object.keys(expVariantData)) { - traverseVariantData([ek]); - } - } - Promise.resolve(); - }); + if (!this.experimentalSprites) { + return; + } + const expVariantData = await this.cachedFetch("./images/pokemon/variant/_exp_masterlist.json").then(r => r.json()); + Utils.deepMergeObjects(variantData, expVariantData); } cachedFetch(url: string, init?: RequestInit): Promise { @@ -843,48 +797,15 @@ export default class BattleScene extends SceneBase { return fetch(url, init); } - initStarterColors(): Promise { - return new Promise(resolve => { - if (starterColors) { - return resolve(); - } - - this.cachedFetch("./starter-colors.json") - .then(res => res.json()) - .then(sc => { - starterColors = {}; - for (const key of Object.keys(sc)) { - starterColors[key] = sc[key]; - } - - resolve(); - }); - }); - } - - hasExpSprite(key: string): boolean { - const keyMatch = /^pkmn__?(back__)?(shiny__)?(female__)?(\d+)(\-.*?)?(?:_[1-3])?$/g.exec(key); - if (!keyMatch) { - return false; + async initStarterColors(): Promise { + if (starterColors) { + return; } - - let k = keyMatch[4]!; - if (keyMatch[2]) { - k += "s"; + const sc = await this.cachedFetch("./starter-colors.json").then(res => res.json()); + starterColors = {}; + for (const key of Object.keys(sc)) { + starterColors[key] = sc[key]; } - if (keyMatch[1]) { - k += "b"; - } - if (keyMatch[3]) { - k += "f"; - } - if (keyMatch[5]) { - k += keyMatch[5]; - } - if (!expSpriteKeys.includes(k)) { - return false; - } - return true; } public getPlayerParty(): PlayerPokemon[] { diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 76d07bf01ba..f3a06242a13 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -58,7 +58,7 @@ import { BattleEndPhase } from "#app/phases/battle-end-phase"; import { GameOverPhase } from "#app/phases/game-over-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { PartyExpPhase } from "#app/phases/party-exp-phase"; -import type { Variant } from "#app/data/variant"; +import type { Variant } from "#app/sprites/variant"; import { StatusEffect } from "#enums/status-effect"; import { globalScene } from "#app/global-scene"; import { getPokemonSpecies } from "#app/data/pokemon-species"; diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 929d632eb0b..a8942a39880 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -26,11 +26,12 @@ import { pokemonSpeciesLevelMoves, } from "#app/data/balance/pokemon-level-moves"; import type { Stat } from "#enums/stat"; -import type { Variant, VariantSet } from "#app/data/variant"; -import { variantData } from "#app/data/variant"; +import type { Variant, VariantSet } from "#app/sprites/variant"; +import { variantData } from "#app/sprites/variant"; import { speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { SpeciesFormKey } from "#enums/species-form-key"; import { starterPassiveAbilities } from "#app/data/balance/passives"; +import { loadPokemonVariantAssets } from "#app/sprites/pokemon-sprite"; export enum Region { NORMAL, @@ -387,6 +388,7 @@ export abstract class PokemonSpeciesForm { return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`; } + /** Compute the sprite ID of the pokemon form. */ getSpriteId(female: boolean, formIndex?: number, shiny?: boolean, variant = 0, back?: boolean): string { if (formIndex === undefined || this instanceof PokemonForm) { formIndex = this.formIndex; @@ -394,7 +396,9 @@ export abstract class PokemonSpeciesForm { const formSpriteKey = this.getFormSpriteKey(formIndex); const showGenderDiffs = - this.genderDiffs && female && ![SpeciesFormKey.MEGA, SpeciesFormKey.GIGANTAMAX].find(k => formSpriteKey === k); + this.genderDiffs && + female && + ![SpeciesFormKey.MEGA, SpeciesFormKey.GIGANTAMAX].includes(formSpriteKey as SpeciesFormKey); const baseSpriteKey = `${showGenderDiffs ? "female__" : ""}${this.speciesId}${formSpriteKey ? `-${formSpriteKey}` : ""}`; @@ -585,18 +589,19 @@ export abstract class PokemonSpeciesForm { return true; } - loadAssets( + async loadAssets( female: boolean, formIndex?: number, - shiny?: boolean, + shiny = false, variant?: Variant, - startLoad?: boolean, - back?: boolean, + startLoad = false, + back = false, ): Promise { - return new Promise(resolve => { - const spriteKey = this.getSpriteKey(female, formIndex, shiny, variant, back); - globalScene.loadPokemonAtlas(spriteKey, this.getSpriteAtlasPath(female, formIndex, shiny, variant, back)); - globalScene.load.audio(`${this.getCryKey(formIndex)}`, `audio/${this.getCryKey(formIndex)}.m4a`); + const spriteKey = this.getSpriteKey(female, formIndex, shiny, variant, back); + globalScene.loadPokemonAtlas(spriteKey, this.getSpriteAtlasPath(female, formIndex, shiny, variant, back)); + globalScene.load.audio(this.getCryKey(formIndex), `audio/${this.getCryKey(formIndex)}.m4a`); + + return new Promise(resolve => { globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => { const originalWarn = console.warn; // Ignore warnings for missing frames, because there will be a lot @@ -621,7 +626,9 @@ export abstract class PokemonSpeciesForm { const spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant, back) .replace("variant/", "") .replace(/_[1-3]$/, ""); - globalScene.loadPokemonVariantAssets(spriteKey, spritePath, variant).then(() => resolve()); + if (!Utils.isNullOrUndefined(variant)) { + loadPokemonVariantAssets(spriteKey, spritePath, variant).then(() => resolve()); + } }); if (startLoad) { if (!globalScene.load.isLoading()) { diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index a5ba19290fe..5fab70971ec 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -2236,12 +2236,7 @@ export const trainerConfigs: TrainerConfigs = { Species.PHANTUMP, Species.PUMPKABOO, ], - [TrainerPoolTier.RARE]: [ - Species.SNEASEL, - Species.LITWICK, - Species.PAWNIARD, - Species.NOIBAT, - ], + [TrainerPoolTier.RARE]: [Species.SNEASEL, Species.LITWICK, Species.PAWNIARD, Species.NOIBAT], [TrainerPoolTier.SUPER_RARE]: [Species.SLIGGOO, Species.HISUI_SLIGGOO, Species.HISUI_AVALUGG], }), [TrainerType.BRYONY]: new TrainerConfig(++t) diff --git a/src/data/variant.ts b/src/data/variant.ts deleted file mode 100644 index 13c11b0bb40..00000000000 --- a/src/data/variant.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { VariantTier } from "#app/enums/variant-tier"; - -export type Variant = 0 | 1 | 2; - -export type VariantSet = [Variant, Variant, Variant]; - -export const variantData: any = {}; - -export const variantColorCache = {}; - -export function getVariantTint(variant: Variant): number { - switch (variant) { - case 0: - return 0xf8c020; - case 1: - return 0x20f8f0; - case 2: - return 0xe81048; - } -} - -export function getVariantIcon(variant: Variant): number { - switch (variant) { - case 0: - return VariantTier.STANDARD; - case 1: - return VariantTier.RARE; - case 2: - return VariantTier.EPIC; - } -} diff --git a/src/field/anims.ts b/src/field/anims.ts index cd6209dddff..eb895c2d8f9 100644 --- a/src/field/anims.ts +++ b/src/field/anims.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; import { PokeballType } from "#enums/pokeball"; -import type { Variant } from "#app/data/variant"; +import type { Variant } from "#app/sprites/variant"; import { getFrameMs, randGauss } from "#app/utils"; export function addPokeballOpenParticles(x: number, y: number, pokeballType: PokeballType): void { diff --git a/src/field/mystery-encounter-intro.ts b/src/field/mystery-encounter-intro.ts index 649a969d415..e1fb0c37074 100644 --- a/src/field/mystery-encounter-intro.ts +++ b/src/field/mystery-encounter-intro.ts @@ -4,9 +4,10 @@ import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounte import type { Species } from "#enums/species"; import { isNullOrUndefined } from "#app/utils"; import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; -import type { Variant } from "#app/data/variant"; +import type { Variant } from "#app/sprites/variant"; import { doShinySparkleAnim } from "#app/field/anims"; import PlayAnimationConfig = Phaser.Types.Animations.PlayAnimationConfig; +import { loadPokemonVariantAssets } from "#app/sprites/pokemon-sprite"; type KnownFileRoot = | "arenas" @@ -233,8 +234,8 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con this.spriteConfigs.forEach(config => { if (config.isPokemon) { globalScene.loadPokemonAtlas(config.spriteKey, config.fileRoot); - if (config.isShiny) { - shinyPromises.push(globalScene.loadPokemonVariantAssets(config.spriteKey, config.fileRoot, config.variant)); + if (config.isShiny && !isNullOrUndefined(config.variant)) { + shinyPromises.push(loadPokemonVariantAssets(config.spriteKey, config.fileRoot, config.variant)); } } else if (config.isItem) { globalScene.loadAtlas("items", ""); diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 7d856696188..72da3f1ed6f 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2,9 +2,9 @@ import Phaser from "phaser"; import type { AnySound } from "#app/battle-scene"; import type BattleScene from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; -import type { Variant, VariantSet } from "#app/data/variant"; -import { variantColorCache } from "#app/data/variant"; -import { variantData } from "#app/data/variant"; +import type { Variant, VariantSet } from "#app/sprites/variant"; +import { populateVariantColors, variantColorCache } from "#app/sprites/variant"; +import { variantData } from "#app/sprites/variant"; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo, @@ -263,7 +263,9 @@ import { Nature } from "#enums/nature"; import { StatusEffect } from "#enums/status-effect"; import { doShinySparkleAnim } from "#app/field/anims"; import { MoveFlags } from "#enums/MoveFlags"; +import { hasExpSprite } from "#app/sprites/sprite-utils"; import { timedEventManager } from "#app/global-event-manager"; +import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; import { ResetStatusPhase } from "#app/phases/reset-status-phase"; export enum LearnMoveSituation { @@ -696,115 +698,79 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { abstract getBattlerIndex(): BattlerIndex; - loadAssets(ignoreOverride = true): Promise { - return new Promise(resolve => { - const moveIds = this.getMoveset().map(m => m.getMove().id); - Promise.allSettled(moveIds.map(m => initMoveAnim(m))).then(() => { - loadMoveAnimAssets(moveIds); - this.getSpeciesForm().loadAssets( - this.getGender() === Gender.FEMALE, - this.formIndex, - this.shiny, - this.variant, - ); - if (this.isPlayer() || this.getFusionSpeciesForm()) { - globalScene.loadPokemonAtlas( - this.getBattleSpriteKey(true, ignoreOverride), - this.getBattleSpriteAtlasPath(true, ignoreOverride), - ); - } - if (this.getFusionSpeciesForm()) { - this.getFusionSpeciesForm().loadAssets( - this.getFusionGender() === Gender.FEMALE, - this.fusionFormIndex, - this.fusionShiny, - this.fusionVariant, - ); - globalScene.loadPokemonAtlas( - this.getFusionBattleSpriteKey(true, ignoreOverride), - this.getFusionBattleSpriteAtlasPath(true, ignoreOverride), - ); - } - globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => { - if (this.isPlayer()) { - const originalWarn = console.warn; - // Ignore warnings for missing frames, because there will be a lot - console.warn = () => {}; - const battleFrameNames = globalScene.anims.generateFrameNames( - this.getBattleSpriteKey(), - { zeroPad: 4, suffix: ".png", start: 1, end: 400 }, - ); - console.warn = originalWarn; - if (!globalScene.anims.exists(this.getBattleSpriteKey())) { - globalScene.anims.create({ - key: this.getBattleSpriteKey(), - frames: battleFrameNames, - frameRate: 10, - repeat: -1, - }); - } - } - this.playAnim(); - const updateFusionPaletteAndResolve = () => { - this.updateFusionPalette(); - if (this.summonData?.speciesForm) { - this.updateFusionPalette(true); - } - resolve(); - }; - if (this.shiny) { - const populateVariantColors = ( - isBackSprite = false, - ): Promise => { - return new Promise(async resolve => { - const battleSpritePath = this.getBattleSpriteAtlasPath( - isBackSprite, - ignoreOverride, - ) - .replace("variant/", "") - .replace(/_[1-3]$/, ""); - let config = variantData; - const useExpSprite = - globalScene.experimentalSprites && - globalScene.hasExpSprite( - this.getBattleSpriteKey(isBackSprite, ignoreOverride), - ); - battleSpritePath - .split("/") - .map(p => (config ? (config = config[p]) : null)); - const variantSet: VariantSet = config as VariantSet; - if (variantSet && variantSet[this.variant] === 1) { - const cacheKey = this.getBattleSpriteKey(isBackSprite); - if (!variantColorCache.hasOwnProperty(cacheKey)) { - await this.populateVariantColorCache( - cacheKey, - useExpSprite, - battleSpritePath, - ); - } - } - resolve(); - }); - }; - if (this.isPlayer()) { - Promise.all([ - populateVariantColors(false), - populateVariantColors(true), - ]).then(() => updateFusionPaletteAndResolve()); - } else { - populateVariantColors(false).then(() => - updateFusionPaletteAndResolve(), - ); - } - } else { - updateFusionPaletteAndResolve(); - } - }); - if (!globalScene.load.isLoading()) { - globalScene.load.start(); - } + async loadAssets(ignoreOverride = true): Promise { + /** Promises that are loading assets and can be run concurrently. */ + const loadPromises: Promise[] = []; + // Assets for moves + loadPromises.push(loadMoveAnimations(this.getMoveset().map(m => m.getMove().id))); + + // Load the assets for the species form + loadPromises.push( + this.getSpeciesForm().loadAssets(this.getGender() === Gender.FEMALE, this.formIndex, this.shiny, this.variant), + ); + + if (this.isPlayer() || this.getFusionSpeciesForm()) { + globalScene.loadPokemonAtlas( + this.getBattleSpriteKey(true, ignoreOverride), + this.getBattleSpriteAtlasPath(true, ignoreOverride), + ); + } + if (this.getFusionSpeciesForm()) { + loadPromises.push(this.getFusionSpeciesForm().loadAssets( + this.getFusionGender() === Gender.FEMALE, + this.fusionFormIndex, + this.fusionShiny, + this.fusionVariant, + )); + globalScene.loadPokemonAtlas( + this.getFusionBattleSpriteKey(true, ignoreOverride), + this.getFusionBattleSpriteAtlasPath(true, ignoreOverride), + ); + } + + if (this.shiny) { + loadPromises.push(populateVariantColors(this, false, ignoreOverride)) + if (this.isPlayer()) { + loadPromises.push(populateVariantColors(this, true, ignoreOverride)); + } + } + + await Promise.allSettled(loadPromises); + + // Wait for the assets we queued to load to finish loading, then... + if (!globalScene.load.isLoading()) { + globalScene.load.start(); + } + // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#creating_a_promise_around_an_old_callback_api + await new Promise(resolve => globalScene.load.once(Phaser.Loader.Events.COMPLETE, resolve)); + + // With the sprites loaded, generate the animation frame information + if (this.isPlayer()) { + const originalWarn = console.warn; + // Ignore warnings for missing frames, because there will be a lot + console.warn = () => {}; + const battleFrameNames = globalScene.anims.generateFrameNames(this.getBattleSpriteKey(), { + zeroPad: 4, + suffix: ".png", + start: 1, + end: 400, }); - }); + console.warn = originalWarn; + globalScene.anims.create({ + key: this.getBattleSpriteKey(), + frames: battleFrameNames, + frameRate: 10, + repeat: -1, + }); + } + // With everything loaded, now begin playing the animation. + this.playAnim(); + + // update the fusion palette + this.updateFusionPalette(); + if (this.summonData?.speciesForm) { + this.updateFusionPalette(true); + } } /** diff --git a/src/overrides.ts b/src/overrides.ts index 3a9a54e740b..21c72cd7b98 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -2,7 +2,7 @@ import { type PokeballCounts } from "#app/battle-scene"; import { EvolutionItem } from "#app/data/balance/pokemon-evolutions"; import { Gender } from "#app/data/gender"; import { FormChangeItem } from "#app/data/pokemon-forms"; -import { Variant } from "#app/data/variant"; +import { Variant } from "#app/sprites/variant"; import { type ModifierOverride } from "#app/modifier/modifier-type"; import { Unlockables } from "#app/system/unlockables"; import { Abilities } from "#enums/abilities"; diff --git a/src/pipelines/field-sprite.ts b/src/pipelines/field-sprite.ts index 547281d7dee..612c9fae052 100644 --- a/src/pipelines/field-sprite.ts +++ b/src/pipelines/field-sprite.ts @@ -1,210 +1,8 @@ import { globalScene } from "#app/global-scene"; import { TerrainType, getTerrainColor } from "../data/terrain"; import * as Utils from "../utils"; - -const spriteFragShader = ` -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif - -uniform sampler2D uMainSampler[%count%]; - -varying vec2 outTexCoord; -varying float outTexId; -varying float outTintEffect; -varying vec4 outTint; - -uniform float time; -uniform int ignoreTimeTint; -uniform int isOutside; -uniform vec3 dayTint; -uniform vec3 duskTint; -uniform vec3 nightTint; -uniform vec3 terrainColor; -uniform float terrainColorRatio; - -float blendOverlay(float base, float blend) { - return base<0.5?(2.0*base*blend):(1.0-2.0*(1.0-base)*(1.0-blend)); -} - -vec3 blendOverlay(vec3 base, vec3 blend) { - return vec3(blendOverlay(base.r,blend.r),blendOverlay(base.g,blend.g),blendOverlay(base.b,blend.b)); -} - -vec3 blendHardLight(vec3 base, vec3 blend) { - return blendOverlay(blend, base); -} - -float hue2rgb(float f1, float f2, float hue) { - if (hue < 0.0) - hue += 1.0; - else if (hue > 1.0) - hue -= 1.0; - float res; - if ((6.0 * hue) < 1.0) - res = f1 + (f2 - f1) * 6.0 * hue; - else if ((2.0 * hue) < 1.0) - res = f2; - else if ((3.0 * hue) < 2.0) - res = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0; - else - res = f1; - return res; -} - -vec3 rgb2hsl(vec3 color) { - vec3 hsl; - - float fmin = min(min(color.r, color.g), color.b); - float fmax = max(max(color.r, color.g), color.b); - float delta = fmax - fmin; - - hsl.z = (fmax + fmin) / 2.0; - - if (delta == 0.0) { - hsl.x = 0.0; - hsl.y = 0.0; - } else { - if (hsl.z < 0.5) - hsl.y = delta / (fmax + fmin); - else - hsl.y = delta / (2.0 - fmax - fmin); - - float deltaR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta; - float deltaG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta; - float deltaB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta; - - if (color.r == fmax ) - hsl.x = deltaB - deltaG; - else if (color.g == fmax) - hsl.x = (1.0 / 3.0) + deltaR - deltaB; - else if (color.b == fmax) - hsl.x = (2.0 / 3.0) + deltaG - deltaR; - - if (hsl.x < 0.0) - hsl.x += 1.0; - else if (hsl.x > 1.0) - hsl.x -= 1.0; - } - - return hsl; -} - -vec3 hsl2rgb(vec3 hsl) { - vec3 rgb; - - if (hsl.y == 0.0) - rgb = vec3(hsl.z); - else { - float f2; - - if (hsl.z < 0.5) - f2 = hsl.z * (1.0 + hsl.y); - else - f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z); - - float f1 = 2.0 * hsl.z - f2; - - rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0)); - rgb.g = hue2rgb(f1, f2, hsl.x); - rgb.b = hue2rgb(f1, f2, hsl.x - (1.0/3.0)); - } - - return rgb; -} - -vec3 blendHue(vec3 base, vec3 blend) { - vec3 baseHSL = rgb2hsl(base); - return hsl2rgb(vec3(rgb2hsl(blend).r, baseHSL.g, baseHSL.b)); -} - -void main() { - vec4 texture; - - %forloop% - - vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a); - - // Multiply texture tint - vec4 color = texture * texel; - - if (outTintEffect == 1.0) { - // Solid color + texture alpha - color.rgb = mix(texture.rgb, outTint.bgr * outTint.a, texture.a); - } else if (outTintEffect == 2.0) { - // Solid color, no texture - color = texel; - } - - /* Apply day/night tint */ - if (color.a > 0.0 && ignoreTimeTint == 0) { - vec3 dayNightTint; - - if (time < 0.25) { - dayNightTint = dayTint; - } else if (isOutside == 0 && time < 0.5) { - dayNightTint = mix(dayTint, nightTint, (time - 0.25) / 0.25); - } else if (time < 0.375) { - dayNightTint = mix(dayTint, duskTint, (time - 0.25) / 0.125); - } else if (time < 0.5) { - dayNightTint = mix(duskTint, nightTint, (time - 0.375) / 0.125); - } else if (time < 0.75) { - dayNightTint = nightTint; - } else if (isOutside == 0) { - dayNightTint = mix(nightTint, dayTint, (time - 0.75) / 0.25); - } else if (time < 0.875) { - dayNightTint = mix(nightTint, duskTint, (time - 0.75) / 0.125); - } else { - dayNightTint = mix(duskTint, dayTint, (time - 0.875) / 0.125); - } - - color = vec4(blendHardLight(color.rgb, dayNightTint), color.a); - } - - if (terrainColorRatio > 0.0 && (1.0 - terrainColorRatio) < outTexCoord.y) { - if (color.a > 0.0 && (terrainColor.r > 0.0 || terrainColor.g > 0.0 || terrainColor.b > 0.0)) { - color.rgb = mix(color.rgb, blendHue(color.rgb, terrainColor), 1.0); - } - } - - gl_FragColor = color; -} -`; - -const spriteVertShader = ` -precision mediump float; - -uniform mat4 uProjectionMatrix; -uniform int uRoundPixels; -uniform vec2 uResolution; - -attribute vec2 inPosition; -attribute vec2 inTexCoord; -attribute float inTexId; -attribute float inTintEffect; -attribute vec4 inTint; - -varying vec2 outTexCoord; -varying float outTexId; -varying vec2 outPosition; -varying float outTintEffect; -varying vec4 outTint; - -void main() { - gl_Position = uProjectionMatrix * vec4(inPosition, 1.0, 1.0); - if (uRoundPixels == 1) - { - gl_Position.xy = floor(((gl_Position.xy + 1.0) * 0.5 * uResolution) + 0.5) / uResolution * 2.0 - 1.0; - } - outTexCoord = inTexCoord; - outTexId = inTexId; - outPosition = inPosition; - outTint = inTint; - outTintEffect = inTintEffect; -} -`; +import fieldSpriteFragShader from "./glsl/fieldSpriteFragShader.frag?raw"; +import spriteVertShader from "./glsl/spriteShader.vert?raw"; export default class FieldSpritePipeline extends Phaser.Renderer.WebGL.Pipelines.MultiPipeline { constructor(game: Phaser.Game, config?: Phaser.Types.Renderer.WebGL.WebGLPipelineConfig) { @@ -212,7 +10,7 @@ export default class FieldSpritePipeline extends Phaser.Renderer.WebGL.Pipelines config || { game: game, name: "field-sprite", - fragShader: spriteFragShader, + fragShader: fieldSpriteFragShader, vertShader: spriteVertShader, }, ); diff --git a/src/pipelines/glsl/fieldSpriteFragShader.frag b/src/pipelines/glsl/fieldSpriteFragShader.frag new file mode 100644 index 00000000000..e79dea86fe9 --- /dev/null +++ b/src/pipelines/glsl/fieldSpriteFragShader.frag @@ -0,0 +1,168 @@ +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif + +uniform sampler2D uMainSampler[%count%]; + +varying vec2 outTexCoord; +varying float outTexId; +varying float outTintEffect; +varying vec4 outTint; + +uniform float time; +uniform int ignoreTimeTint; +uniform int isOutside; +uniform vec3 dayTint; +uniform vec3 duskTint; +uniform vec3 nightTint; +uniform vec3 terrainColor; +uniform float terrainColorRatio; + +float blendOverlay(float base, float blend) { + return base<0.5?(2.0*base*blend):(1.0-2.0*(1.0-base)*(1.0-blend)); +} + +vec3 blendOverlay(vec3 base, vec3 blend) { + return vec3(blendOverlay(base.r,blend.r),blendOverlay(base.g,blend.g),blendOverlay(base.b,blend.b)); +} + +vec3 blendHardLight(vec3 base, vec3 blend) { + return blendOverlay(blend, base); +} + +float hue2rgb(float f1, float f2, float hue) { + if (hue < 0.0) + hue += 1.0; + else if (hue > 1.0) + hue -= 1.0; + float res; + if ((6.0 * hue) < 1.0) + res = f1 + (f2 - f1) * 6.0 * hue; + else if ((2.0 * hue) < 1.0) + res = f2; + else if ((3.0 * hue) < 2.0) + res = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0; + else + res = f1; + return res; +} + +vec3 rgb2hsl(vec3 color) { + vec3 hsl; + + float fmin = min(min(color.r, color.g), color.b); + float fmax = max(max(color.r, color.g), color.b); + float delta = fmax - fmin; + + hsl.z = (fmax + fmin) / 2.0; + + if (delta == 0.0) { + hsl.x = 0.0; + hsl.y = 0.0; + } else { + if (hsl.z < 0.5) + hsl.y = delta / (fmax + fmin); + else + hsl.y = delta / (2.0 - fmax - fmin); + + float deltaR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta; + float deltaG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta; + float deltaB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta; + + if (color.r == fmax ) + hsl.x = deltaB - deltaG; + else if (color.g == fmax) + hsl.x = (1.0 / 3.0) + deltaR - deltaB; + else if (color.b == fmax) + hsl.x = (2.0 / 3.0) + deltaG - deltaR; + + if (hsl.x < 0.0) + hsl.x += 1.0; + else if (hsl.x > 1.0) + hsl.x -= 1.0; + } + + return hsl; +} + +vec3 hsl2rgb(vec3 hsl) { + vec3 rgb; + + if (hsl.y == 0.0) + rgb = vec3(hsl.z); + else { + float f2; + + if (hsl.z < 0.5) + f2 = hsl.z * (1.0 + hsl.y); + else + f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z); + + float f1 = 2.0 * hsl.z - f2; + + rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0)); + rgb.g = hue2rgb(f1, f2, hsl.x); + rgb.b = hue2rgb(f1, f2, hsl.x - (1.0/3.0)); + } + + return rgb; +} + +vec3 blendHue(vec3 base, vec3 blend) { + vec3 baseHSL = rgb2hsl(base); + return hsl2rgb(vec3(rgb2hsl(blend).r, baseHSL.g, baseHSL.b)); +} + +void main() { + vec4 texture; + + %forloop% + + vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a); + + // Multiply texture tint + vec4 color = texture * texel; + + if (outTintEffect == 1.0) { + // Solid color + texture alpha + color.rgb = mix(texture.rgb, outTint.bgr * outTint.a, texture.a); + } else if (outTintEffect == 2.0) { + // Solid color, no texture + color = texel; + } + + /* Apply day/night tint */ + if (color.a > 0.0 && ignoreTimeTint == 0) { + vec3 dayNightTint; + + if (time < 0.25) { + dayNightTint = dayTint; + } else if (isOutside == 0 && time < 0.5) { + dayNightTint = mix(dayTint, nightTint, (time - 0.25) / 0.25); + } else if (time < 0.375) { + dayNightTint = mix(dayTint, duskTint, (time - 0.25) / 0.125); + } else if (time < 0.5) { + dayNightTint = mix(duskTint, nightTint, (time - 0.375) / 0.125); + } else if (time < 0.75) { + dayNightTint = nightTint; + } else if (isOutside == 0) { + dayNightTint = mix(nightTint, dayTint, (time - 0.75) / 0.25); + } else if (time < 0.875) { + dayNightTint = mix(nightTint, duskTint, (time - 0.75) / 0.125); + } else { + dayNightTint = mix(duskTint, dayTint, (time - 0.875) / 0.125); + } + + color = vec4(blendHardLight(color.rgb, dayNightTint), color.a); + } + + if (terrainColorRatio > 0.0 && (1.0 - terrainColorRatio) < outTexCoord.y) { + if (color.a > 0.0 && (terrainColor.r > 0.0 || terrainColor.g > 0.0 || terrainColor.b > 0.0)) { + color.rgb = mix(color.rgb, blendHue(color.rgb, terrainColor), 1.0); + } + } + + gl_FragColor = color; +} \ No newline at end of file diff --git a/src/pipelines/glsl/invert.frag b/src/pipelines/glsl/invert.frag new file mode 100644 index 00000000000..24d9ee83a55 --- /dev/null +++ b/src/pipelines/glsl/invert.frag @@ -0,0 +1,10 @@ +precision mediump float; + +uniform sampler2D uMainSampler; + +varying vec2 outTexCoord; + +void main() +{ + gl_FragColor = 1.0 - texture2D(uMainSampler, outTexCoord); +} \ No newline at end of file diff --git a/src/pipelines/glsl/spriteFragShader.frag b/src/pipelines/glsl/spriteFragShader.frag new file mode 100644 index 00000000000..3765e595b70 --- /dev/null +++ b/src/pipelines/glsl/spriteFragShader.frag @@ -0,0 +1,279 @@ +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif + +uniform sampler2D uMainSampler[%count%]; + +varying vec2 outTexCoord; +varying float outTexId; +varying vec2 outPosition; +varying float outTintEffect; +varying vec4 outTint; + +uniform float time; +uniform int ignoreTimeTint; +uniform int isOutside; +uniform vec3 dayTint; +uniform vec3 duskTint; +uniform vec3 nightTint; +uniform float teraTime; +uniform vec3 teraColor; +uniform int hasShadow; +uniform int yCenter; +uniform float fieldScale; +uniform float vCutoff; +uniform vec2 relPosition; +uniform vec2 texFrameUv; +uniform vec2 size; +uniform vec2 texSize; +uniform float yOffset; +uniform float yShadowOffset; +uniform vec4 tone; +uniform ivec4 baseVariantColors[32]; +uniform vec4 variantColors[32]; +uniform ivec4 spriteColors[32]; +uniform ivec4 fusionSpriteColors[32]; + +const vec3 lumaF = vec3(.299, .587, .114); + +float blendOverlay(float base, float blend) { + return base<0.5?(2.0*base*blend):(1.0-2.0*(1.0-base)*(1.0-blend)); +} + +vec3 blendOverlay(vec3 base, vec3 blend) { + return vec3(blendOverlay(base.r,blend.r),blendOverlay(base.g,blend.g),blendOverlay(base.b,blend.b)); +} + +vec3 blendHardLight(vec3 base, vec3 blend) { + return blendOverlay(blend, base); +} + +float hue2rgb(float f1, float f2, float hue) { + if (hue < 0.0) + hue += 1.0; + else if (hue > 1.0) + hue -= 1.0; + float res; + if ((6.0 * hue) < 1.0) + res = f1 + (f2 - f1) * 6.0 * hue; + else if ((2.0 * hue) < 1.0) + res = f2; + else if ((3.0 * hue) < 2.0) + res = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0; + else + res = f1; + return res; +} + +vec3 rgb2hsl(vec3 color) { + vec3 hsl; + + float fmin = min(min(color.r, color.g), color.b); + float fmax = max(max(color.r, color.g), color.b); + float delta = fmax - fmin; + + hsl.z = (fmax + fmin) / 2.0; + + if (delta == 0.0) { + hsl.x = 0.0; + hsl.y = 0.0; + } else { + if (hsl.z < 0.5) + hsl.y = delta / (fmax + fmin); + else + hsl.y = delta / (2.0 - fmax - fmin); + + float deltaR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta; + float deltaG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta; + float deltaB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta; + + if (color.r == fmax ) + hsl.x = deltaB - deltaG; + else if (color.g == fmax) + hsl.x = (1.0 / 3.0) + deltaR - deltaB; + else if (color.b == fmax) + hsl.x = (2.0 / 3.0) + deltaG - deltaR; + + if (hsl.x < 0.0) + hsl.x += 1.0; + else if (hsl.x > 1.0) + hsl.x -= 1.0; + } + + return hsl; +} + +vec3 hsl2rgb(vec3 hsl) { + vec3 rgb; + + if (hsl.y == 0.0) + rgb = vec3(hsl.z); + else { + float f2; + + if (hsl.z < 0.5) + f2 = hsl.z * (1.0 + hsl.y); + else + f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z); + + float f1 = 2.0 * hsl.z - f2; + + rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0)); + rgb.g = hue2rgb(f1, f2, hsl.x); + rgb.b= hue2rgb(f1, f2, hsl.x - (1.0/3.0)); + } + + return rgb; +} + +vec3 blendHue(vec3 base, vec3 blend) { + vec3 baseHSL = rgb2hsl(base); + return hsl2rgb(vec3(rgb2hsl(blend).r, baseHSL.g, baseHSL.b)); +} + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +void main() { + vec4 texture = texture2D(uMainSampler[0], outTexCoord); + + ivec4 colorInt = ivec4(texture*255.0); + + for (int i = 0; i < 32; i++) { + if (baseVariantColors[i][3] == 0) + break; + // abs value is broken in this version of gles with highp + ivec3 diffs = ivec3( + (colorInt.r > baseVariantColors[i].r) ? colorInt.r - baseVariantColors[i].r : baseVariantColors[i].r - colorInt.r, + (colorInt.g > baseVariantColors[i].g) ? colorInt.g - baseVariantColors[i].g : baseVariantColors[i].g - colorInt.g, + (colorInt.b > baseVariantColors[i].b) ? colorInt.b - baseVariantColors[i].b : baseVariantColors[i].b - colorInt.b + ); + // Set color threshold to be within 3 points for each channel + bvec3 threshold = lessThan(diffs, ivec3(3)); + + if (texture.a > 0.0 && all(threshold)) { + texture.rgb = variantColors[i].rgb; + break; + } + } + + for (int i = 0; i < 32; i++) { + if (spriteColors[i][3] == 0) + break; + if (texture.a > 0.0 && colorInt.r == spriteColors[i].r && colorInt.g == spriteColors[i].g && colorInt.b == spriteColors[i].b) { + vec3 fusionColor = vec3(float(fusionSpriteColors[i].r) / 255.0, float(fusionSpriteColors[i].g) / 255.0, float(fusionSpriteColors[i].b) / 255.0); + vec3 bg = vec3(spriteColors[i].rgb) / 255.0; + float gray = (bg.r + bg.g + bg.b) / 3.0; + bg = vec3(gray, gray, gray); + vec3 fg = fusionColor; + texture.rgb = mix(1.0 - 2.0 * (1.0 - bg) * (1.0 - fg), 2.0 * bg * fg, step(bg, vec3(0.5))); + break; + } + } + + vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a); + + // Multiply texture tint + vec4 color = texture * texel; + + if (color.a > 0.0 && teraColor.r > 0.0 && teraColor.g > 0.0 && teraColor.b > 0.0) { + vec2 relUv = vec2((outTexCoord.x - texFrameUv.x) / (size.x / texSize.x), (outTexCoord.y - texFrameUv.y) / (size.y / texSize.y)); + vec2 teraTexCoord = vec2(relUv.x * (size.x / 200.0), relUv.y * (size.y / 120.0)); + vec4 teraCol = texture2D(uMainSampler[1], teraTexCoord); + float floorValue = 86.0 / 255.0; + vec3 teraPatternHsv = rgb2hsv(teraCol.rgb); + teraCol.rgb = hsv2rgb(vec3((teraPatternHsv.b - floorValue) * 4.0 + teraTexCoord.x * fieldScale / 2.0 + teraTexCoord.y * fieldScale / 2.0 + teraTime * 255.0, teraPatternHsv.b, teraPatternHsv.b)); + + color.rgb = mix(color.rgb, blendHue(color.rgb, teraColor), 0.625); + teraCol.rgb = mix(teraCol.rgb, teraColor, 0.5); + color.rgb = blendOverlay(color.rgb, teraCol.rgb); + + if (any(lessThan(teraCol.rgb, vec3(1.0)))) { + vec3 teraColHsv = rgb2hsv(teraColor); + color.rgb = mix(color.rgb, teraColor, (1.0 - teraColHsv.g) / 2.0); + } + } + + if (outTintEffect == 1.0) { + // Solid color + texture alpha + color.rgb = mix(texture.rgb, outTint.bgr * outTint.a, texture.a); + } else if (outTintEffect == 2.0) { + // Solid color, no texture + color = texel; + } + + /* Apply gray */ + float luma = dot(color.rgb, lumaF); + color.rgb = mix(color.rgb, vec3(luma), tone.w); + + /* Apply tone */ + color.rgb += tone.rgb * (color.a / 255.0); + + /* Apply day/night tint */ + if (color.a > 0.0 && ignoreTimeTint == 0) { + vec3 dayNightTint; + + if (time < 0.25) { + dayNightTint = dayTint; + } else if (isOutside == 0 && time < 0.5) { + dayNightTint = mix(dayTint, nightTint, (time - 0.25) / 0.25); + } else if (time < 0.375) { + dayNightTint = mix(dayTint, duskTint, (time - 0.25) / 0.125); + } else if (time < 0.5) { + dayNightTint = mix(duskTint, nightTint, (time - 0.375) / 0.125); + } else if (time < 0.75) { + dayNightTint = nightTint; + } else if (isOutside == 0) { + dayNightTint = mix(nightTint, dayTint, (time - 0.75) / 0.25); + } else if (time < 0.875) { + dayNightTint = mix(nightTint, duskTint, (time - 0.75) / 0.125); + } else { + dayNightTint = mix(duskTint, dayTint, (time - 0.875) / 0.125); + } + + color.rgb = blendHardLight(color.rgb, dayNightTint); + } + + if (hasShadow == 1) { + float width = size.x - (yOffset / 2.0); + + float spriteX = ((floor(outPosition.x / fieldScale) - relPosition.x) / width) + 0.5; + float spriteY = ((floor(outPosition.y / fieldScale) - relPosition.y - yShadowOffset) / size.y); + + if (yCenter == 1) { + spriteY += 0.5; + } else { + spriteY += 1.0; + } + + bool yOverflow = outTexCoord.y >= vCutoff; + + if ((spriteY >= 0.9 && (color.a == 0.0 || yOverflow))) { + float shadowSpriteY = (spriteY - 0.9) * (1.0 / 0.15); + if (distance(vec2(spriteX, shadowSpriteY), vec2(0.5, 0.5)) < 0.5) { + color = vec4(vec3(0.0, 0.0, 0.0), 0.5); + } else if (yOverflow) { + discard; + } + } else if (yOverflow) { + discard; + } + } + + gl_FragColor = color; +} \ No newline at end of file diff --git a/src/pipelines/glsl/spriteShader.vert b/src/pipelines/glsl/spriteShader.vert new file mode 100644 index 00000000000..33743384b47 --- /dev/null +++ b/src/pipelines/glsl/spriteShader.vert @@ -0,0 +1,32 @@ +precision mediump float; + +uniform mat4 uProjectionMatrix; +uniform int uRoundPixels; +uniform vec2 uResolution; + +attribute vec2 inPosition; +attribute vec2 inTexCoord; +attribute float inTexId; +attribute float inTintEffect; +attribute vec4 inTint; + +varying vec2 outTexCoord; +varying vec2 outtexFrameUv; +varying float outTexId; +varying vec2 outPosition; +varying float outTintEffect; +varying vec4 outTint; + +void main() +{ + gl_Position = uProjectionMatrix * vec4(inPosition, 1.0, 1.0); + if (uRoundPixels == 1) + { + gl_Position.xy = floor(((gl_Position.xy + 1.0) * 0.5 * uResolution) + 0.5) / uResolution * 2.0 - 1.0; + } + outTexCoord = inTexCoord; + outTexId = inTexId; + outPosition = inPosition; + outTint = inTint; + outTintEffect = inTintEffect; +} \ No newline at end of file diff --git a/src/pipelines/invert.ts b/src/pipelines/invert.ts index a945d0c95aa..0ebc3ad865f 100644 --- a/src/pipelines/invert.ts +++ b/src/pipelines/invert.ts @@ -1,17 +1,5 @@ import type { Game } from "phaser"; - -const fragShader = ` -precision mediump float; - -uniform sampler2D uMainSampler; - -varying vec2 outTexCoord; - -void main() -{ - gl_FragColor = 1.0 - texture2D(uMainSampler, outTexCoord); -} -`; +import fragShader from "./glsl/invert.frag?raw"; export default class InvertPostFX extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { constructor(game: Game) { diff --git a/src/pipelines/sprite.ts b/src/pipelines/sprite.ts index 439e35f711f..acbaac50476 100644 --- a/src/pipelines/sprite.ts +++ b/src/pipelines/sprite.ts @@ -1,318 +1,12 @@ -import { variantColorCache } from "#app/data/variant"; +import { variantColorCache } from "#app/sprites/variant"; import MysteryEncounterIntroVisuals from "#app/field/mystery-encounter-intro"; import Pokemon from "#app/field/pokemon"; import Trainer from "#app/field/trainer"; import { globalScene } from "#app/global-scene"; import * as Utils from "#app/utils"; import FieldSpritePipeline from "./field-sprite"; - -const spriteFragShader = ` -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif - -uniform sampler2D uMainSampler[%count%]; - -varying vec2 outTexCoord; -varying float outTexId; -varying vec2 outPosition; -varying float outTintEffect; -varying vec4 outTint; - -uniform float time; -uniform int ignoreTimeTint; -uniform int isOutside; -uniform vec3 dayTint; -uniform vec3 duskTint; -uniform vec3 nightTint; -uniform float teraTime; -uniform vec3 teraColor; -uniform int hasShadow; -uniform int yCenter; -uniform float fieldScale; -uniform float vCutoff; -uniform vec2 relPosition; -uniform vec2 texFrameUv; -uniform vec2 size; -uniform vec2 texSize; -uniform float yOffset; -uniform float yShadowOffset; -uniform vec4 tone; -uniform ivec4 baseVariantColors[32]; -uniform vec4 variantColors[32]; -uniform ivec4 spriteColors[32]; -uniform ivec4 fusionSpriteColors[32]; - -const vec3 lumaF = vec3(.299, .587, .114); - -float blendOverlay(float base, float blend) { - return base<0.5?(2.0*base*blend):(1.0-2.0*(1.0-base)*(1.0-blend)); -} - -vec3 blendOverlay(vec3 base, vec3 blend) { - return vec3(blendOverlay(base.r,blend.r),blendOverlay(base.g,blend.g),blendOverlay(base.b,blend.b)); -} - -vec3 blendHardLight(vec3 base, vec3 blend) { - return blendOverlay(blend, base); -} - -float hue2rgb(float f1, float f2, float hue) { - if (hue < 0.0) - hue += 1.0; - else if (hue > 1.0) - hue -= 1.0; - float res; - if ((6.0 * hue) < 1.0) - res = f1 + (f2 - f1) * 6.0 * hue; - else if ((2.0 * hue) < 1.0) - res = f2; - else if ((3.0 * hue) < 2.0) - res = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0; - else - res = f1; - return res; -} - -vec3 rgb2hsl(vec3 color) { - vec3 hsl; - - float fmin = min(min(color.r, color.g), color.b); - float fmax = max(max(color.r, color.g), color.b); - float delta = fmax - fmin; - - hsl.z = (fmax + fmin) / 2.0; - - if (delta == 0.0) { - hsl.x = 0.0; - hsl.y = 0.0; - } else { - if (hsl.z < 0.5) - hsl.y = delta / (fmax + fmin); - else - hsl.y = delta / (2.0 - fmax - fmin); - - float deltaR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta; - float deltaG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta; - float deltaB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta; - - if (color.r == fmax ) - hsl.x = deltaB - deltaG; - else if (color.g == fmax) - hsl.x = (1.0 / 3.0) + deltaR - deltaB; - else if (color.b == fmax) - hsl.x = (2.0 / 3.0) + deltaG - deltaR; - - if (hsl.x < 0.0) - hsl.x += 1.0; - else if (hsl.x > 1.0) - hsl.x -= 1.0; - } - - return hsl; -} - -vec3 hsl2rgb(vec3 hsl) { - vec3 rgb; - - if (hsl.y == 0.0) - rgb = vec3(hsl.z); - else { - float f2; - - if (hsl.z < 0.5) - f2 = hsl.z * (1.0 + hsl.y); - else - f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z); - - float f1 = 2.0 * hsl.z - f2; - - rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0)); - rgb.g = hue2rgb(f1, f2, hsl.x); - rgb.b= hue2rgb(f1, f2, hsl.x - (1.0/3.0)); - } - - return rgb; -} - -vec3 blendHue(vec3 base, vec3 blend) { - vec3 baseHSL = rgb2hsl(base); - return hsl2rgb(vec3(rgb2hsl(blend).r, baseHSL.g, baseHSL.b)); -} - -vec3 rgb2hsv(vec3 c) { - vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); - vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); - vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); - - float d = q.x - min(q.w, q.y); - float e = 1.0e-10; - return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); -} - -vec3 hsv2rgb(vec3 c) { - vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); - vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); - return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); -} - -void main() { - vec4 texture = texture2D(uMainSampler[0], outTexCoord); - - ivec4 colorInt = ivec4(int(texture.r * 255.0), int(texture.g * 255.0), int(texture.b * 255.0), int(texture.a * 255.0)); - - for (int i = 0; i < 32; i++) { - if (baseVariantColors[i][3] == 0) - break; - if (texture.a > 0.0 && colorInt.r == baseVariantColors[i].r && colorInt.g == baseVariantColors[i].g && colorInt.b == baseVariantColors[i].b) { - texture.rgb = variantColors[i].rgb; - break; - } - } - - for (int i = 0; i < 32; i++) { - if (spriteColors[i][3] == 0) - break; - if (texture.a > 0.0 && colorInt.r == spriteColors[i].r && colorInt.g == spriteColors[i].g && colorInt.b == spriteColors[i].b) { - vec3 fusionColor = vec3(float(fusionSpriteColors[i].r) / 255.0, float(fusionSpriteColors[i].g) / 255.0, float(fusionSpriteColors[i].b) / 255.0); - vec3 bg = vec3(float(spriteColors[i].r) / 255.0, float(spriteColors[i].g) / 255.0, float(spriteColors[i].b) / 255.0); - float gray = (bg.r + bg.g + bg.b) / 3.0; - bg = vec3(gray, gray, gray); - vec3 fg = fusionColor; - texture.rgb = mix(1.0 - 2.0 * (1.0 - bg) * (1.0 - fg), 2.0 * bg * fg, step(bg, vec3(0.5))); - break; - } - } - - vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a); - - // Multiply texture tint - vec4 color = texture * texel; - - if (color.a > 0.0 && teraColor.r > 0.0 && teraColor.g > 0.0 && teraColor.b > 0.0) { - vec2 relUv = vec2((outTexCoord.x - texFrameUv.x) / (size.x / texSize.x), (outTexCoord.y - texFrameUv.y) / (size.y / texSize.y)); - vec2 teraTexCoord = vec2(relUv.x * (size.x / 200.0), relUv.y * (size.y / 120.0)); - vec4 teraCol = texture2D(uMainSampler[1], teraTexCoord); - float floorValue = 86.0 / 255.0; - vec3 teraPatternHsv = rgb2hsv(teraCol.rgb); - teraCol.rgb = hsv2rgb(vec3((teraPatternHsv.b - floorValue) * 4.0 + teraTexCoord.x * fieldScale / 2.0 + teraTexCoord.y * fieldScale / 2.0 + teraTime * 255.0, teraPatternHsv.b, teraPatternHsv.b)); - - color.rgb = mix(color.rgb, blendHue(color.rgb, teraColor), 0.625); - teraCol.rgb = mix(teraCol.rgb, teraColor, 0.5); - color.rgb = blendOverlay(color.rgb, teraCol.rgb); - - if (teraColor.r < 1.0 || teraColor.g < 1.0 || teraColor.b < 1.0) { - vec3 teraColHsv = rgb2hsv(teraColor); - color.rgb = mix(color.rgb, teraColor, (1.0 - teraColHsv.g) / 2.0); - } - } - - if (outTintEffect == 1.0) { - // Solid color + texture alpha - color.rgb = mix(texture.rgb, outTint.bgr * outTint.a, texture.a); - } else if (outTintEffect == 2.0) { - // Solid color, no texture - color = texel; - } - - /* Apply gray */ - float luma = dot(color.rgb, lumaF); - color.rgb = mix(color.rgb, vec3(luma), tone.w); - - /* Apply tone */ - color.rgb += tone.rgb * (color.a / 255.0); - - /* Apply day/night tint */ - if (color.a > 0.0 && ignoreTimeTint == 0) { - vec3 dayNightTint; - - if (time < 0.25) { - dayNightTint = dayTint; - } else if (isOutside == 0 && time < 0.5) { - dayNightTint = mix(dayTint, nightTint, (time - 0.25) / 0.25); - } else if (time < 0.375) { - dayNightTint = mix(dayTint, duskTint, (time - 0.25) / 0.125); - } else if (time < 0.5) { - dayNightTint = mix(duskTint, nightTint, (time - 0.375) / 0.125); - } else if (time < 0.75) { - dayNightTint = nightTint; - } else if (isOutside == 0) { - dayNightTint = mix(nightTint, dayTint, (time - 0.75) / 0.25); - } else if (time < 0.875) { - dayNightTint = mix(nightTint, duskTint, (time - 0.75) / 0.125); - } else { - dayNightTint = mix(duskTint, dayTint, (time - 0.875) / 0.125); - } - - color.rgb = blendHardLight(color.rgb, dayNightTint); - } - - if (hasShadow == 1) { - float width = size.x - (yOffset / 2.0); - - float spriteX = ((floor(outPosition.x / fieldScale) - relPosition.x) / width) + 0.5; - float spriteY = ((floor(outPosition.y / fieldScale) - relPosition.y - yShadowOffset) / size.y); - - if (yCenter == 1) { - spriteY += 0.5; - } else { - spriteY += 1.0; - } - - bool yOverflow = outTexCoord.y >= vCutoff; - - if ((spriteY >= 0.9 && (color.a == 0.0 || yOverflow))) { - float shadowSpriteY = (spriteY - 0.9) * (1.0 / 0.15); - if (distance(vec2(spriteX, shadowSpriteY), vec2(0.5, 0.5)) < 0.5) { - color = vec4(vec3(0.0, 0.0, 0.0), 0.5); - } else if (yOverflow) { - discard; - } - } else if (yOverflow) { - discard; - } - } - - gl_FragColor = color; -} -`; - -const spriteVertShader = ` -precision mediump float; - -uniform mat4 uProjectionMatrix; -uniform int uRoundPixels; -uniform vec2 uResolution; - -attribute vec2 inPosition; -attribute vec2 inTexCoord; -attribute float inTexId; -attribute float inTintEffect; -attribute vec4 inTint; - -varying vec2 outTexCoord; -varying vec2 outtexFrameUv; -varying float outTexId; -varying vec2 outPosition; -varying float outTintEffect; -varying vec4 outTint; - -void main() -{ - gl_Position = uProjectionMatrix * vec4(inPosition, 1.0, 1.0); - if (uRoundPixels == 1) - { - gl_Position.xy = floor(((gl_Position.xy + 1.0) * 0.5 * uResolution) + 0.5) / uResolution * 2.0 - 1.0; - } - outTexCoord = inTexCoord; - outTexId = inTexId; - outPosition = inPosition; - outTint = inTint; - outTintEffect = inTintEffect; -} -`; +import spriteFragShader from "./glsl/spriteFragShader.frag?raw"; +import spriteVertShader from "./glsl/spriteShader.vert?raw"; export default class SpritePipeline extends FieldSpritePipeline { private _tone: number[]; diff --git a/src/sprites/pokemon-asset-loader.ts b/src/sprites/pokemon-asset-loader.ts new file mode 100644 index 00000000000..4ce88f4f1fb --- /dev/null +++ b/src/sprites/pokemon-asset-loader.ts @@ -0,0 +1,11 @@ +import type { Moves } from "#enums/moves"; +import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; + +/** + * Asynchronously load the animations and assets for the provided moves. + * @param moveIds - An array of move IDs to load assets for. + */ +export async function loadMoveAnimations(moveIds: Moves[]): Promise { + await Promise.allSettled(moveIds.map(m => initMoveAnim(m))); + await loadMoveAnimAssets(moveIds); +} diff --git a/src/sprites/pokemon-sprite.ts b/src/sprites/pokemon-sprite.ts new file mode 100644 index 00000000000..66432f5a4ea --- /dev/null +++ b/src/sprites/pokemon-sprite.ts @@ -0,0 +1,79 @@ +import { globalScene } from "#app/global-scene"; +import { variantColorCache, variantData } from "#app/sprites/variant"; +import { Gender } from "#app/data/gender"; +import { hasExpSprite } from "./sprite-utils"; +import type { Variant, VariantSet } from "#app/sprites/variant"; +import type Pokemon from "#app/field/pokemon"; +import type BattleScene from "#app/battle-scene"; + +// Regex patterns + +/** Regex matching double underscores */ +const DUNDER_REGEX = /\_{2}/g; + +/** + * Calculate the sprite ID from a pokemon form. + */ +export function getSpriteId(pokemon: Pokemon, ignoreOverride?: boolean): string { + return pokemon + .getSpeciesForm(ignoreOverride) + .getSpriteId( + pokemon.getGender(ignoreOverride) === Gender.FEMALE, + pokemon.formIndex, + pokemon.shiny, + pokemon.variant, + ); +} + +export function getBattleSpriteId(pokemon: Pokemon, back?: boolean, ignoreOverride = false): string { + if (back === undefined) { + back = pokemon.isPlayer(); + } + return pokemon + .getSpeciesForm(ignoreOverride) + .getSpriteId( + pokemon.getGender(ignoreOverride) === Gender.FEMALE, + pokemon.formIndex, + pokemon.shiny, + pokemon.variant, + back, + ); +} + +/** Compute the path to the sprite atlas by converting double underscores to path components (/) + */ +export function getSpriteAtlasPath(pokemon: Pokemon, ignoreOverride = false): string { + const spriteId = getSpriteId(pokemon, ignoreOverride).replace(DUNDER_REGEX, "/"); + return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`; +} + +/** + * Load the variant assets for the given sprite and store it in {@linkcode variantColorCache}. + * @param spriteKey - The key of the sprite to load + * @param fileRoot - The root path of the sprite file + * @param variant - The variant to load + * @param scene - The scene to load the assets in (defaults to the global scene) + */ +export async function loadPokemonVariantAssets( + spriteKey: string, + fileRoot: string, + variant: Variant, + scene: BattleScene = globalScene, +): Promise { + if (variantColorCache.hasOwnProperty(spriteKey)) { + return; + } + const useExpSprite = scene.experimentalSprites && hasExpSprite(spriteKey); + if (useExpSprite) { + fileRoot = `exp/${fileRoot}`; + } + let variantConfig = variantData; + fileRoot.split("/").map(p => (variantConfig ? (variantConfig = variantConfig[p]) : null)); + const variantSet = variantConfig as VariantSet; + if (!variantConfig || variantSet[variant] !== 1) { + return; + } + variantColorCache[spriteKey] = await scene + .cachedFetch(`./images/pokemon/variant/${fileRoot}.json`) + .then(res => res.json()); +} diff --git a/src/sprites/sprite-keys.ts b/src/sprites/sprite-keys.ts new file mode 100644 index 00000000000..f023df089f6 --- /dev/null +++ b/src/sprites/sprite-keys.ts @@ -0,0 +1 @@ +export const expSpriteKeys: Set = new Set(); diff --git a/src/sprites/sprite-utils.ts b/src/sprites/sprite-utils.ts new file mode 100644 index 00000000000..8a352de3d55 --- /dev/null +++ b/src/sprites/sprite-utils.ts @@ -0,0 +1,28 @@ +import { expSpriteKeys } from "#app/sprites/sprite-keys"; + +const expKeyRegex = /^pkmn__?(back__)?(shiny__)?(female__)?(\d+)(\-.*?)?(?:_[1-3])?$/; + +export function hasExpSprite(key: string): boolean { + const keyMatch = expKeyRegex.exec(key); + if (!keyMatch) { + return false; + } + + let k = keyMatch[4]!; + if (keyMatch[2]) { + k += "s"; + } + if (keyMatch[1]) { + k += "b"; + } + if (keyMatch[3]) { + k += "f"; + } + if (keyMatch[5]) { + k += keyMatch[5]; + } + if (!expSpriteKeys.has(k)) { + return false; + } + return true; +} diff --git a/src/sprites/variant.ts b/src/sprites/variant.ts new file mode 100644 index 00000000000..7552f63b778 --- /dev/null +++ b/src/sprites/variant.ts @@ -0,0 +1,145 @@ +import { VariantTier } from "#app/enums/variant-tier"; +import { hasExpSprite } from "#app/sprites/sprite-utils"; +import { globalScene } from "#app/global-scene"; +import type Pokemon from "#app/field/pokemon"; +import { isNullOrUndefined } from "#app/utils"; + +export type Variant = 0 | 1 | 2; + +export type VariantSet = [Variant, Variant, Variant]; + +export const variantData: any = {}; + +/** Caches variant colors that have been generated */ +export const variantColorCache = {}; + +export function getVariantTint(variant: Variant): number { + switch (variant) { + case 0: + return 0xf8c020; + case 1: + return 0x20f8f0; + case 2: + return 0xe81048; + } +} + +export function getVariantIcon(variant: Variant): number { + switch (variant) { + case 0: + return VariantTier.STANDARD; + case 1: + return VariantTier.RARE; + case 2: + return VariantTier.EPIC; + } +} + +/** Delete all of the keys in variantData */ +export function clearVariantData(): void { + for (const key in variantData) { + delete variantData[key]; + } +} + +/** Update the variant data to use experiment sprite files for variants that have experimental sprites. */ +export async function mergeExperimentalData(mainData: any, expData: any): Promise { + if (!expData) { + return; + } + + for (const key of Object.keys(expData)) { + if (typeof expData[key] === "object" && !Array.isArray(expData[key])) { + // If the value is an object, recursively merge. + if (!mainData[key]) { + mainData[key] = {}; + } + mergeExperimentalData(mainData[key], expData[key]); + } else { + // Otherwise, replace the value + mainData[key] = expData[key]; + } + } +} + +/** + * Populate the variant color cache with the variant colors for this pokemon. + * The global scene must be initialized before this function is called. + */ +export async function populateVariantColors( + pokemon: Pokemon, + isBackSprite = false, + ignoreOverride = true, +): Promise { + const battleSpritePath = pokemon + .getBattleSpriteAtlasPath(isBackSprite, ignoreOverride) + .replace("variant/", "") + .replace(/_[1-3]$/, ""); + let config = variantData; + const useExpSprite = + globalScene.experimentalSprites && hasExpSprite(pokemon.getBattleSpriteKey(isBackSprite, ignoreOverride)); + battleSpritePath.split("/").map(p => (config ? (config = config[p]) : null)); + const variantSet: VariantSet = config as VariantSet; + if (!variantSet || variantSet[pokemon.variant] !== 1) { + return; + } + const cacheKey = pokemon.getBattleSpriteKey(isBackSprite); + if (!variantColorCache.hasOwnProperty(cacheKey)) { + await populateVariantColorCache(cacheKey, useExpSprite, battleSpritePath); + } +} + +/** + * Gracefully handle errors loading a variant sprite. Log if it fails and attempt to fall back on + * non-experimental sprites before giving up. + * + * @param cacheKey - The cache key for the variant color sprite + * @param attemptedSpritePath - The sprite path that failed to load + * @param useExpSprite - Was the attempted sprite experimental + * @param battleSpritePath - The filename of the sprite + * @param optionalParams - Any additional params to log + */ +async function fallbackVariantColor( + cacheKey: string, + attemptedSpritePath: string, + useExpSprite: boolean, + battleSpritePath: string, + ...optionalParams: any[] +): Promise { + console.warn(`Could not load ${attemptedSpritePath}!`, ...optionalParams); + if (useExpSprite) { + await populateVariantColorCache(cacheKey, false, battleSpritePath); + } +} + +/** + * Fetch a variant color sprite from the key and store it in the variant color cache. + * + * @param cacheKey - The cache key for the variant color sprite + * @param useExpSprite - Should the experimental sprite be used + * @param battleSpritePath - The filename of the sprite + */ +export async function populateVariantColorCache( + cacheKey: string, + useExpSprite: boolean, + battleSpritePath: string, +): Promise { + const spritePath = `./images/pokemon/variant/${useExpSprite ? "exp/" : ""}${battleSpritePath}.json`; + return globalScene + .cachedFetch(spritePath) + .then(res => { + // Prevent the JSON from processing if it failed to load + if (!res.ok) { + return fallbackVariantColor(cacheKey, res.url, useExpSprite, battleSpritePath, res.status, res.statusText); + } + return res.json(); + }) + .catch(error => { + return fallbackVariantColor(cacheKey, spritePath, useExpSprite, battleSpritePath, error); + }) + .then(c => { + if (!isNullOrUndefined(c)) { + variantColorCache[cacheKey] = c; + } + }); +} diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 391ceec503d..061a6d3a194 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -32,7 +32,7 @@ import { Tutorial } from "#app/tutorial"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { allMoves } from "#app/data/moves/move"; import { TrainerVariant } from "#app/field/trainer"; -import type { Variant } from "#app/data/variant"; +import type { Variant } from "#app/sprites/variant"; import { setSettingGamepad, SettingGamepad, settingGamepadDefaults } from "#app/system/settings/settings-gamepad"; import type { SettingKeyboard } from "#app/system/settings/settings-keyboard"; import { setSettingKeyboard } from "#app/system/settings/settings-keyboard"; diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 957d43797a1..7cdcb0c72c3 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -7,7 +7,7 @@ import { getPokemonSpecies, getPokemonSpeciesForm } from "../data/pokemon-specie import { Status } from "../data/status-effect"; import Pokemon, { EnemyPokemon, PokemonMove, PokemonSummonData } from "../field/pokemon"; import { TrainerSlot } from "#enums/trainer-slot"; -import type { Variant } from "#app/data/variant"; +import type { Variant } from "#app/sprites/variant"; import { loadBattlerTag } from "../data/battler-tags"; import type { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index 355ab9167a1..ab006269d4e 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -7,7 +7,7 @@ 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/data/variant"; +import { getVariantTint } from "#app/sprites/variant"; import { Stat } from "#enums/stat"; import BattleFlyout from "./battle-flyout"; import { WindowVariant, addWindow } from "./ui-theme"; diff --git a/src/ui/hatched-pokemon-container.ts b/src/ui/hatched-pokemon-container.ts index 0b283c2e063..9d1c13e19d5 100644 --- a/src/ui/hatched-pokemon-container.ts +++ b/src/ui/hatched-pokemon-container.ts @@ -1,6 +1,6 @@ import type { EggHatchData } from "#app/data/egg-hatch-data"; import { Gender } from "#app/data/gender"; -import { getVariantTint } from "#app/data/variant"; +import { getVariantTint } from "#app/sprites/variant"; import { DexAttr } from "#app/system/game-data"; import { globalScene } from "#app/global-scene"; import type PokemonSpecies from "#app/data/pokemon-species"; diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index caddd64cd28..ebaccc515c1 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -18,7 +18,7 @@ import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-ico import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { addWindow } from "#app/ui/ui-theme"; import { SpeciesFormChangeItemTrigger, FormChangeItem } from "#app/data/pokemon-forms"; -import { getVariantTint } from "#app/data/variant"; +import { getVariantTint } from "#app/sprites/variant"; import { Button } from "#enums/buttons"; import { applyChallenges, ChallengeType } from "#app/data/challenge"; import MoveInfoOverlay from "#app/ui/move-info-overlay"; diff --git a/src/ui/pokedex-mon-container.ts b/src/ui/pokedex-mon-container.ts index e61da86e95e..410effda40d 100644 --- a/src/ui/pokedex-mon-container.ts +++ b/src/ui/pokedex-mon-container.ts @@ -1,4 +1,4 @@ -import type { Variant } from "#app/data/variant"; +import type { Variant } from "#app/sprites/variant"; import { globalScene } from "#app/global-scene"; import { isNullOrUndefined } from "#app/utils"; import type PokemonSpecies from "../data/pokemon-species"; diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts index 062b4c3797c..eede346f052 100644 --- a/src/ui/pokedex-page-ui-handler.ts +++ b/src/ui/pokedex-page-ui-handler.ts @@ -1,7 +1,7 @@ import type { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions, pokemonStarters } from "#app/data/balance/pokemon-evolutions"; -import type { Variant } from "#app/data/variant"; -import { getVariantTint, getVariantIcon } from "#app/data/variant"; +import type { Variant } from "#app/sprites/variant"; +import { getVariantTint, getVariantIcon } from "#app/sprites/variant"; import { argbFromRgba } from "@material/material-color-utilities"; import i18next from "i18next"; import { starterColors } from "#app/battle-scene"; diff --git a/src/ui/pokedex-ui-handler.ts b/src/ui/pokedex-ui-handler.ts index 230b1bcb42b..59b06d476a2 100644 --- a/src/ui/pokedex-ui-handler.ts +++ b/src/ui/pokedex-ui-handler.ts @@ -1,5 +1,5 @@ -import type { Variant } from "#app/data/variant"; -import { getVariantTint, getVariantIcon } from "#app/data/variant"; +import type { Variant } from "#app/sprites/variant"; +import { getVariantTint, getVariantIcon } from "#app/sprites/variant"; import { argbFromRgba } from "@material/material-color-utilities"; import i18next from "i18next"; import { starterColors } from "#app/battle-scene"; diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 56201f38748..1c880f6aec9 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -1,4 +1,4 @@ -import { getVariantTint } from "#app/data/variant"; +import { getVariantTint } from "#app/sprites/variant"; import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { globalScene } from "#app/global-scene"; import { Gender, getGenderColor, getGenderSymbol } from "../data/gender"; diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index 364cb8e4003..8719950381a 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -18,7 +18,7 @@ import { getTypeRgb } from "#app/data/type"; import { PokemonType } from "#enums/pokemon-type"; import { TypeColor, TypeShadow } from "#app/enums/color"; import { getNatureStatMultiplier, getNatureName } from "../data/nature"; -import { getVariantTint } from "#app/data/variant"; +import { getVariantTint } from "#app/sprites/variant"; import * as Modifier from "../modifier/modifier"; import type { Species } from "#enums/species"; import { PlayerGender } from "#enums/player-gender"; diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 1e84b367791..3876f2585db 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1,8 +1,8 @@ import type { CandyUpgradeNotificationChangedEvent } from "#app/events/battle-scene"; import { BattleSceneEventType } from "#app/events/battle-scene"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; -import type { Variant } from "#app/data/variant"; -import { getVariantTint, getVariantIcon } from "#app/data/variant"; +import type { Variant } from "#app/sprites/variant"; +import { getVariantTint, getVariantIcon } from "#app/sprites/variant"; import { argbFromRgba } from "@material/material-color-utilities"; import i18next from "i18next"; import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 9b209ded57a..aa3d014bd95 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -19,8 +19,8 @@ import { StatusEffect } from "#enums/status-effect"; import { getBiomeName } from "#app/data/balance/biomes"; import { getNatureName, getNatureStatMultiplier } from "#app/data/nature"; import { loggedInUser } from "#app/account"; -import type { Variant } from "#app/data/variant"; -import { getVariantTint } from "#app/data/variant"; +import type { Variant } from "#app/sprites/variant"; +import { getVariantTint } from "#app/sprites/variant"; import { Button } from "#enums/buttons"; import type { Ability } from "#app/data/ability"; import i18next from "i18next"; diff --git a/src/utils.ts b/src/utils.ts index 4092b68b405..2f05e2724ff 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -613,3 +613,25 @@ 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()); } + +/** + * Merges the two objects, such that for each property in `b` that matches a property in `a`, + * the value in `a` is replaced by the value in `b`. This is done recursively if the property is a non-array object + * + * If the property does not exist in `a` or its `typeof` evaluates differently, the property is skipped. + * If the value of the property is an array, the array is replaced. If it is any other object, the object is merged recursively. + */ +// biome-ignore lint/complexity/noBannedTypes: This function is designed to merge json objects +export function deepMergeObjects(a: Object, b: Object) { + for (const key in b) { + // !(key in a) is redundant here, yet makes it clear that we're explicitly interested in properties that exist in `a` + if (!(key in a) || typeof a[key] !== typeof b[key]) { + continue; + } + if (typeof b[key] === "object" && !Array.isArray(b[key])) { + deepMergeObjects(a[key], b[key]); + } else { + a[key] = b[key]; + } + } +} diff --git a/test/sprites/pokemonSprite.test.ts b/test/sprites/pokemonSprite.test.ts index 5bd08a58cda..a008b75b42e 100644 --- a/test/sprites/pokemonSprite.test.ts +++ b/test/sprites/pokemonSprite.test.ts @@ -3,8 +3,10 @@ import fs from "fs"; import path from "path"; import { beforeAll, describe, expect, it } from "vitest"; import _masterlist from "../../public/images/pokemon/variant/_masterlist.json"; +import _exp_masterlist from "../../public/images/pokemon/variant/_exp_masterlist.json"; type PokemonVariantMasterlist = typeof _masterlist; +type PokemonExpVariantMasterlist = typeof _exp_masterlist; const deepCopy = (data: any) => { return JSON.parse(JSON.stringify(data)); @@ -12,7 +14,7 @@ const deepCopy = (data: any) => { describe("check if every variant's sprite are correctly set", () => { let masterlist: PokemonVariantMasterlist; - let expVariant: PokemonVariantMasterlist["exp"]; + let expVariant: PokemonExpVariantMasterlist; let femaleVariant: PokemonVariantMasterlist["female"]; let backVariant: PokemonVariantMasterlist["back"]; let rootDir: string; @@ -20,13 +22,12 @@ describe("check if every variant's sprite are correctly set", () => { beforeAll(() => { rootDir = `${getAppRootDir()}${path.sep}public${path.sep}images${path.sep}pokemon${path.sep}variant${path.sep}`; masterlist = deepCopy(_masterlist); - expVariant = masterlist.exp; + expVariant = deepCopy(_exp_masterlist); femaleVariant = masterlist.female; backVariant = masterlist.back; - //@ts-ignore - delete masterlist.exp; //TODO: resolve ts-ignore - //@ts-ignore - delete masterlist.female; //TODO: resolve ts-ignore + + // @ts-ignore + delete masterlist.female; // TODO: resolve ts-ignore //@ts-ignore delete masterlist.back; //TODO: resolve ts-ignore }); diff --git a/test/testUtils/helpers/overridesHelper.ts b/test/testUtils/helpers/overridesHelper.ts index 9bb0369a31a..0ed1511255b 100644 --- a/test/testUtils/helpers/overridesHelper.ts +++ b/test/testUtils/helpers/overridesHelper.ts @@ -1,4 +1,4 @@ -import type { Variant } from "#app/data/variant"; +import type { Variant } from "#app/sprites/variant"; import { Weather } from "#app/data/weather"; import { Abilities } from "#app/enums/abilities"; import type { ModifierOverride } from "#app/modifier/modifier-type"; From 1a7442511c4011118172824a97bc627712887327 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 10 Apr 2025 23:22:42 -0700 Subject: [PATCH 24/33] [Bug] Fix Biome selection RNG (#5645) --- src/phases/select-biome-phase.ts | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/phases/select-biome-phase.ts b/src/phases/select-biome-phase.ts index 2d67cb87405..de705728c50 100644 --- a/src/phases/select-biome-phase.ts +++ b/src/phases/select-biome-phase.ts @@ -33,23 +33,19 @@ export class SelectBiomePhase extends BattlePhase { } else if (globalScene.gameMode.hasRandomBiomes) { setNextBiome(this.generateNextBiome()); } else if (Array.isArray(biomeLinks[currentBiome])) { - let biomes: Biome[] = []; - globalScene.executeWithSeedOffset(() => { - biomes = (biomeLinks[currentBiome] as (Biome | [Biome, number])[]) - .filter(b => !Array.isArray(b) || !randSeedInt(b[1])) - .map(b => (!Array.isArray(b) ? b : b[0])); - }, globalScene.currentBattle.waveIndex); + const biomes: Biome[] = (biomeLinks[currentBiome] as (Biome | [Biome, number])[]) + .filter(b => !Array.isArray(b) || !randSeedInt(b[1])) + .map(b => (!Array.isArray(b) ? b : b[0])); + if (biomes.length > 1 && globalScene.findModifier(m => m instanceof MapModifier)) { - let biomeChoices: Biome[] = []; - globalScene.executeWithSeedOffset(() => { - biomeChoices = ( - !Array.isArray(biomeLinks[currentBiome]) - ? [biomeLinks[currentBiome] as Biome] - : (biomeLinks[currentBiome] as (Biome | [Biome, number])[]) - ) - .filter(b => !Array.isArray(b) || !randSeedInt(b[1])) - .map(b => (Array.isArray(b) ? b[0] : b)); - }, globalScene.currentBattle.waveIndex); + const biomeChoices: Biome[] = ( + !Array.isArray(biomeLinks[currentBiome]) + ? [biomeLinks[currentBiome] as Biome] + : (biomeLinks[currentBiome] as (Biome | [Biome, number])[]) + ) + .filter(b => !Array.isArray(b) || !randSeedInt(b[1])) + .map(b => (Array.isArray(b) ? b[0] : b)); + const biomeSelectItems = biomeChoices.map(b => { const ret: OptionSelectItem = { label: getBiomeName(b), From 81f424dc71f25b58b6b460691a5c8ee0471ad8b0 Mon Sep 17 00:00:00 2001 From: Blitzy <118096277+Blitz425@users.noreply.github.com> Date: Fri, 11 Apr 2025 15:33:25 -0500 Subject: [PATCH 25/33] [Balance] Fix and Adjust TM Compatibility Curse Skill Swap Aqua Tail Zen Headbutt Hidden Power Tera Blast --- src/data/balance/tms.ts | 56 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/data/balance/tms.ts b/src/data/balance/tms.ts index 788ffd4f273..62199fd6968 100644 --- a/src/data/balance/tms.ts +++ b/src/data/balance/tms.ts @@ -19126,6 +19126,8 @@ export const tmSpecies: TmSpecies = { Species.KROOKODILE, Species.SCRAGGY, Species.SCRAFTY, + Species.YAMASK, + Species.COFAGRIGUS, Species.SAWSBUCK, Species.LITWICK, Species.LAMPENT, @@ -19163,6 +19165,7 @@ export const tmSpecies: TmSpecies = { Species.SINISTEA, Species.POLTEAGEIST, Species.PERRSERKER, + Species.RUNERIGUS, Species.PINCURCHIN, Species.STONJOURNER, Species.CUFANT, @@ -19228,6 +19231,7 @@ export const tmSpecies: TmSpecies = { Species.GALAR_SLOWBRO, Species.GALAR_WEEZING, Species.GALAR_SLOWKING, + Species.GALAR_YAMASK, Species.HISUI_ELECTRODE, Species.HISUI_TYPHLOSION, Species.HISUI_QWILFISH, @@ -30922,6 +30926,7 @@ export const tmSpecies: TmSpecies = { Species.MURKROW, Species.SLOWKING, Species.MISDREAVUS, + Species.UNOWN, Species.GIRAFARIG, Species.PINECO, Species.FORRETRESS, @@ -40134,6 +40139,8 @@ export const tmSpecies: TmSpecies = { Species.MEOWSTIC, Species.SPRITZEE, Species.AROMATISSE, + Species.INKAY, + Species.MALAMAR, Species.SYLVEON, Species.CARBINK, Species.PHANTUMP, @@ -49173,6 +49180,7 @@ export const tmSpecies: TmSpecies = { Species.KANGASKHAN, Species.GOLDEEN, Species.SEAKING, + Species.GYARADOS, Species.LAPRAS, Species.VAPOREON, Species.KABUTOPS, @@ -52587,6 +52595,7 @@ export const tmSpecies: TmSpecies = { Species.SNORLAX, Species.MEWTWO, Species.MEW, + Species.MEGANIUM, Species.CYNDAQUIL, Species.QUILAVA, Species.TYPHLOSION, @@ -66205,7 +66214,11 @@ export const tmSpecies: TmSpecies = { Species.SQUIRTLE, Species.WARTORTLE, Species.BLASTOISE, + Species.CATERPIE, + Species.METAPOD, Species.BUTTERFREE, + Species.WEEDLE, + Species.KAKUNA, Species.BEEDRILL, Species.PIDGEY, Species.PIDGEOTTO, @@ -66451,7 +66464,10 @@ export const tmSpecies: TmSpecies = { Species.MIGHTYENA, Species.ZIGZAGOON, Species.LINOONE, + Species.WURMPLE, + Species.SILCOON, Species.BEAUTIFLY, + Species.CASCOON, Species.DUSTOX, Species.LOTAD, Species.LOMBRE, @@ -66987,6 +67003,8 @@ export const tmSpecies: TmSpecies = { Species.STAKATAKA, Species.BLACEPHALON, Species.ZERAORA, + Species.MELTAN, + Species.MELMETAL, Species.ALOLA_RATTATA, Species.ALOLA_RATICATE, Species.ALOLA_RAICHU, @@ -67020,8 +67038,19 @@ export const tmSpecies: TmSpecies = { Species.ROOKIDEE, Species.CORVISQUIRE, Species.CORVIKNIGHT, + Species.BLIPBUG, + Species.DOTTLER, + Species.ORBEETLE, + Species.NICKIT, + Species.THIEVUL, + Species.GOSSIFLEUR, + Species.ELDEGOSS, + Species.WOOLOO, + Species.DUBWOOL, Species.CHEWTLE, Species.DREDNAW, + Species.YAMPER, + Species.BOLTUND, Species.ROLYCOLY, Species.CARKOL, Species.COALOSSAL, @@ -67035,6 +67064,10 @@ export const tmSpecies: TmSpecies = { Species.BARRASKEWDA, Species.TOXEL, Species.TOXTRICITY, + Species.SIZZLIPEDE, + Species.CENTISKORCH, + Species.CLOBBOPUS, + Species.GRAPPLOCT, Species.SINISTEA, Species.POLTEAGEIST, Species.HATENNA, @@ -67043,7 +67076,14 @@ export const tmSpecies: TmSpecies = { Species.IMPIDIMP, Species.MORGREM, Species.GRIMMSNARL, + Species.OBSTAGOON, Species.PERRSERKER, + Species.CURSOLA, + Species.SIRFETCHD, + Species.MR_RIME, + Species.RUNERIGUS, + Species.MILCERY, + Species.ALCREMIE, Species.FALINKS, Species.PINCURCHIN, Species.SNOM, @@ -67054,6 +67094,11 @@ export const tmSpecies: TmSpecies = { Species.MORPEKO, Species.CUFANT, Species.COPPERAJAH, + Species.DRACOZOLT, + Species.ARCTOZOLT, + Species.DRACOVISH, + Species.ARCTOVISH, + Species.DURALUDON, Species.DREEPY, Species.DRAKLOAK, Species.DRAGAPULT, @@ -67195,13 +67240,24 @@ export const tmSpecies: TmSpecies = { Species.IRON_CROWN, Species.PECHARUNT, Species.GALAR_MEOWTH, + Species.GALAR_PONYTA, + Species.GALAR_RAPIDASH, Species.GALAR_SLOWPOKE, Species.GALAR_SLOWBRO, + Species.GALAR_FARFETCHD, Species.GALAR_WEEZING, + Species.GALAR_MR_MIME, Species.GALAR_ARTICUNO, Species.GALAR_ZAPDOS, Species.GALAR_MOLTRES, Species.GALAR_SLOWKING, + Species.GALAR_CORSOLA, + Species.GALAR_ZIGZAGOON, + Species.GALAR_LINOONE, + Species.GALAR_DARUMAKA, + Species.GALAR_DARMANITAN, + Species.GALAR_YAMASK, + Species.GALAR_STUNFISK, Species.HISUI_GROWLITHE, Species.HISUI_ARCANINE, Species.HISUI_VOLTORB, From 6f56dce7712c609dc78f7ff11650eb6a34f8f661 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Fri, 11 Apr 2025 22:31:56 -0700 Subject: [PATCH 26/33] [Biome] Add and apply `lint/style/noNamespaceImport` (#5650) * Add `lint/style/noNamespaceImport` Biome rule * Apply Biome rule, add exception for `*.test.ts` files --- biome.jsonc | 6 +- src/account.ts | 6 +- src/battle-scene.ts | 107 ++-- src/battle.ts | 34 +- src/data/ability.ts | 483 +++++++++--------- src/data/balance/biomes.ts | 10 +- src/data/balance/egg-moves.ts | 6 +- src/data/balance/pokemon-evolutions.ts | 6 +- src/data/battler-tags.ts | 4 +- src/data/berry.ts | 18 +- src/data/challenge.ts | 132 +++-- src/data/daily-run.ts | 14 +- src/data/egg.ts | 31 +- src/data/moves/move.ts | 316 ++++++------ .../mysterious-challengers-encounter.ts | 8 +- .../mystery-encounters/mystery-encounter.ts | 8 +- .../utils/encounter-phase-utils.ts | 23 +- src/data/nature.ts | 4 +- src/data/pokemon-species.ts | 20 +- src/data/trainer-names.ts | 4 +- src/data/trainers/trainer-config.ts | 36 +- src/data/weather.ts | 4 +- src/field/arena.ts | 23 +- src/field/damage-number-handler.ts | 30 +- src/field/pokemon-sprite-sparkle-handler.ts | 8 +- src/field/pokemon.ts | 236 +++++---- src/field/trainer.ts | 18 +- src/game-mode.ts | 8 +- src/inputs-controller.ts | 5 +- src/loading-scene.ts | 12 +- src/phases/move-effect-phase.ts | 58 +-- src/phases/revival-blessing-phase.ts | 6 +- src/phases/select-starter-phase.ts | 6 +- src/pipelines/field-sprite.ts | 4 +- src/pipelines/sprite.ts | 6 +- src/system/achv.ts | 8 +- src/system/game-data.ts | 25 +- src/system/game-speed.ts | 8 +- .../version_migration/version_converter.ts | 3 + src/ui/abstact-option-select-ui-handler.ts | 8 +- src/ui/arena-flyout.ts | 8 +- src/ui/base-stats-overlay.ts | 4 +- src/ui/battle-flyout.ts | 4 +- src/ui/battle-info.ts | 6 +- src/ui/bgm-bar.ts | 4 +- src/ui/candy-bar.ts | 6 +- src/ui/challenges-select-ui-handler.ts | 4 +- src/ui/char-sprite.ts | 4 +- src/ui/daily-run-scoreboard.ts | 8 +- src/ui/egg-gacha-ui-handler.ts | 14 +- src/ui/fight-ui-handler.ts | 12 +- src/ui/form-modal-ui-handler.ts | 4 +- src/ui/game-stats-ui-handler.ts | 8 +- src/ui/login-form-ui-handler.ts | 6 +- src/ui/menu-ui-handler.ts | 20 +- src/ui/message-ui-handler.ts | 6 +- src/ui/modifier-select-ui-handler.ts | 7 +- src/ui/move-info-overlay.ts | 14 +- src/ui/mystery-encounter-ui-handler.ts | 21 +- src/ui/party-ui-handler.ts | 10 +- src/ui/pokedex-info-overlay.ts | 10 +- src/ui/pokedex-page-ui-handler.ts | 6 +- src/ui/pokemon-hatch-info-container.ts | 8 +- src/ui/pokemon-icon-anim-handler.ts | 4 +- src/ui/pokemon-info-container.ts | 14 +- src/ui/run-history-ui-handler.ts | 8 +- src/ui/run-info-ui-handler.ts | 19 +- src/ui/save-slot-select-ui-handler.ts | 13 +- src/ui/saving-icon-handler.ts | 8 +- src/ui/starter-select-ui-handler.ts | 13 +- src/ui/summary-ui-handler.ts | 57 ++- src/ui/target-select-ui-handler.ts | 12 +- src/ui/time-of-day-widget.ts | 10 +- src/ui/title-ui-handler.ts | 12 +- src/ui/ui.ts | 4 +- src/ui/unavailable-modal-ui-handler.ts | 4 +- test/escape-calculations.test.ts | 10 +- test/items/exp_booster.test.ts | 4 +- test/items/leek.test.ts | 6 +- test/items/light_ball.test.ts | 18 +- test/items/metal_powder.test.ts | 10 +- test/items/quick_powder.test.ts | 10 +- test/items/thick_club.test.ts | 18 +- test/moves/multi_target.test.ts | 10 +- .../mystery-encounter/encounter-test-utils.ts | 1 + test/testUtils/gameWrapper.ts | 4 +- 86 files changed, 1112 insertions(+), 1103 deletions(-) diff --git a/biome.jsonc b/biome.jsonc index c5e1d713d86..da80d8ee127 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -65,7 +65,8 @@ "useDefaultParameterLast": "off", // TODO: Fix spots in the codebase where this flag would be triggered, and then enable "useSingleVarDeclarator": "off", "useNodejsImportProtocol": "off", - "useTemplate": "off" // string concatenation is faster: https://stackoverflow.com/questions/29055518/are-es6-template-literals-faster-than-string-concatenation + "useTemplate": "off", // string concatenation is faster: https://stackoverflow.com/questions/29055518/are-es6-template-literals-faster-than-string-concatenation + "noNamespaceImport": "error" }, "suspicious": { "noDoubleEquals": "error", @@ -99,6 +100,9 @@ "rules": { "performance": { "noDelete": "off" + }, + "style": { + "noNamespaceImport": "off" } } } diff --git a/src/account.ts b/src/account.ts index 96ce32714bb..7baa7d10a1a 100644 --- a/src/account.ts +++ b/src/account.ts @@ -1,11 +1,11 @@ import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import type { UserInfo } from "#app/@types/UserInfo"; -import { bypassLogin } from "./battle-scene"; -import * as Utils from "./utils"; +import { bypassLogin } from "#app/battle-scene"; +import { randomString } from "#app/utils"; export let loggedInUser: UserInfo | null = null; // This is a random string that is used to identify the client session - unique per session (tab or window) so that the game will only save on the one that the server is expecting -export const clientSessionId = Utils.randomString(32); +export const clientSessionId = randomString(32); export function initLoggedInUser(): void { loggedInUser = { diff --git a/src/battle-scene.ts b/src/battle-scene.ts index acc8dafdd35..8ae2be5af43 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -5,9 +5,20 @@ import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import type { PokemonSpeciesFilter } from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species"; import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; -import type { Constructor } from "#app/utils"; -import { isNullOrUndefined, randSeedInt } from "#app/utils"; -import * as Utils from "#app/utils"; +import { + fixedInt, + deepMergeObjects, + getIvsFromId, + randSeedInt, + getEnumValues, + randomString, + NumberHolder, + shiftCharCodes, + formatMoney, + isNullOrUndefined, + BooleanHolder, + type Constructor, +} from "#app/utils"; import type { Modifier, ModifierPredicate, TurnHeldItemTransferModifier } from "./modifier/modifier"; import { ConsumableModifier, @@ -733,7 +744,7 @@ export default class BattleScene extends SceneBase { } this.playTimeTimer = this.time.addEvent({ - delay: Utils.fixedInt(1000), + delay: fixedInt(1000), repeat: -1, callback: () => { if (this.gameData) { @@ -783,7 +794,7 @@ export default class BattleScene extends SceneBase { return; } const expVariantData = await this.cachedFetch("./images/pokemon/variant/_exp_masterlist.json").then(r => r.json()); - Utils.deepMergeObjects(variantData, expVariantData); + deepMergeObjects(variantData, expVariantData); } cachedFetch(url: string, init?: RequestInit): Promise { @@ -988,7 +999,7 @@ export default class BattleScene extends SceneBase { } if (boss && !dataSource) { - const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967296)); + const secondaryIvs = getIvsFromId(randSeedInt(4294967296)); for (let s = 0; s < pokemon.ivs.length; s++) { pokemon.ivs[s] = Math.round( @@ -1147,7 +1158,7 @@ export default class BattleScene extends SceneBase { * Generates a random number using the current battle's seed * * This calls {@linkcode Battle.randSeedInt}({@linkcode range}, {@linkcode min}) in `src/battle.ts` - * which calls {@linkcode Utils.randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts` + * which calls {@linkcode randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts` * * @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min} * @param min The minimum integer to pick, default `0` @@ -1172,7 +1183,7 @@ export default class BattleScene extends SceneBase { this.lockModifierTiers = false; this.pokeballCounts = Object.fromEntries( - Utils.getEnumValues(PokeballType) + getEnumValues(PokeballType) .filter(p => p <= PokeballType.MASTER_BALL) .map(t => [t, 0]), ); @@ -1204,7 +1215,7 @@ export default class BattleScene extends SceneBase { // Reset RNG after end of game or save & quit. // This needs to happen after clearing this.currentBattle or the seed will be affected by the last wave played - this.setSeed(Overrides.SEED_OVERRIDE || Utils.randomString(24)); + this.setSeed(Overrides.SEED_OVERRIDE || randomString(24)); console.log("Seed:", this.seed); this.resetSeed(); @@ -1245,7 +1256,7 @@ export default class BattleScene extends SceneBase { ...allSpecies, ...allMoves, ...allAbilities, - ...Utils.getEnumValues(ModifierPoolType) + ...getEnumValues(ModifierPoolType) .map(mpt => getModifierPoolForType(mpt)) .flatMap(mp => Object.values(mp) @@ -1285,7 +1296,7 @@ export default class BattleScene extends SceneBase { } getDoubleBattleChance(newWaveIndex: number, playerField: PlayerPokemon[]) { - const doubleChance = new Utils.NumberHolder(newWaveIndex % 10 === 0 ? 32 : 8); + const doubleChance = new NumberHolder(newWaveIndex % 10 === 0 ? 32 : 8); this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance); for (const p of playerField) { applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance); @@ -1342,7 +1353,7 @@ export default class BattleScene extends SceneBase { if (trainerConfigs[trainerType].doubleOnly) { doubleTrainer = true; } else if (trainerConfigs[trainerType].hasDouble) { - doubleTrainer = !Utils.randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField)); + doubleTrainer = !randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField)); // Add a check that special trainers can't be double except for tate and liza - they should use the normal double chance if ( trainerConfigs[trainerType].trainerTypeDouble && @@ -1353,7 +1364,7 @@ export default class BattleScene extends SceneBase { } const variant = doubleTrainer ? TrainerVariant.DOUBLE - : Utils.randSeedInt(2) + : randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT; newTrainer = trainerData !== undefined ? trainerData.toTrainer() : new Trainer(trainerType, variant); @@ -1371,7 +1382,7 @@ export default class BattleScene extends SceneBase { if (double === undefined && newWaveIndex > 1) { if (newBattleType === BattleType.WILD && !this.gameMode.isWaveFinal(newWaveIndex)) { - newDouble = !Utils.randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField)); + newDouble = !randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField)); } else if (newBattleType === BattleType.TRAINER) { newDouble = newTrainer?.variant === TrainerVariant.DOUBLE; } @@ -1559,7 +1570,7 @@ export default class BattleScene extends SceneBase { scale: scale, x: (defaultWidth - scaledWidth) / 2, y: defaultHeight - scaledHeight, - duration: !instant ? Utils.fixedInt(Math.abs(this.field.scale - scale) * 200) : 0, + duration: !instant ? fixedInt(Math.abs(this.field.scale - scale) * 200) : 0, ease: "Sine.easeInOut", onComplete: () => resolve(), }); @@ -1656,12 +1667,12 @@ export default class BattleScene extends SceneBase { case Species.SQUAWKABILLY: case Species.TATSUGIRI: case Species.PALDEA_TAUROS: - return Utils.randSeedInt(species.forms.length); + return randSeedInt(species.forms.length); case Species.PIKACHU: if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30) { return 0; // Ban Cosplay and Partner Pika from Trainers before wave 30 } - return Utils.randSeedInt(8); + return randSeedInt(8); case Species.EEVEE: if ( this.currentBattle?.battleType === BattleType.TRAINER && @@ -1670,22 +1681,22 @@ export default class BattleScene extends SceneBase { ) { return 0; // No Partner Eevee for Wave 12 Preschoolers } - return Utils.randSeedInt(2); + return randSeedInt(2); case Species.FROAKIE: case Species.FROGADIER: case Species.GRENINJA: if (this.currentBattle?.battleType === BattleType.TRAINER && !isEggPhase) { return 0; // Don't give trainers Battle Bond Greninja, Froakie or Frogadier } - return Utils.randSeedInt(2); + return randSeedInt(2); case Species.URSHIFU: - return Utils.randSeedInt(2); + return randSeedInt(2); case Species.ZYGARDE: - return Utils.randSeedInt(4); + return randSeedInt(4); case Species.MINIOR: - return Utils.randSeedInt(7); + return randSeedInt(7); case Species.ALCREMIE: - return Utils.randSeedInt(9); + return randSeedInt(9); case Species.MEOWSTIC: case Species.INDEEDEE: case Species.BASCULEGION: @@ -1716,7 +1727,7 @@ export default class BattleScene extends SceneBase { if (this.gameMode.hasMysteryEncounters && !isEggPhase) { return 1; // Wandering form } - return Utils.randSeedInt(species.forms.length); + return randSeedInt(species.forms.length); } if (ignoreArena) { @@ -1725,7 +1736,7 @@ export default class BattleScene extends SceneBase { case Species.WORMADAM: case Species.ROTOM: case Species.LYCANROC: - return Utils.randSeedInt(species.forms.length); + return randSeedInt(species.forms.length); } return 0; } @@ -1737,7 +1748,7 @@ export default class BattleScene extends SceneBase { let ret = false; this.executeWithSeedOffset( () => { - ret = !Utils.randSeedInt(2); + ret = !randSeedInt(2); }, 0, this.seed.toString(), @@ -1749,7 +1760,7 @@ export default class BattleScene extends SceneBase { let ret = 0; this.executeWithSeedOffset( () => { - ret = Utils.randSeedInt(8) * 5; + ret = randSeedInt(8) * 5; }, 0, this.seed.toString(), @@ -1778,7 +1789,7 @@ export default class BattleScene extends SceneBase { isBoss = waveIndex % 10 === 0 || (this.gameMode.hasRandomBosses && - Utils.randSeedInt(100) < Math.min(Math.max(Math.ceil((waveIndex - 250) / 50), 0) * 2, 30)); + randSeedInt(100) < Math.min(Math.max(Math.ceil((waveIndex - 250) / 50), 0) * 2, 30)); }, waveIndex << 2); } if (!isBoss) { @@ -1805,7 +1816,7 @@ export default class BattleScene extends SceneBase { const infectedIndexes: number[] = []; const spread = (index: number, spreadTo: number) => { const partyMember = party[index + spreadTo]; - if (!partyMember.pokerus && !Utils.randSeedInt(10)) { + if (!partyMember.pokerus && !randSeedInt(10)) { partyMember.pokerus = true; infectedIndexes.push(index + spreadTo); } @@ -1831,7 +1842,7 @@ export default class BattleScene extends SceneBase { resetSeed(waveIndex?: number): void { const wave = waveIndex || this.currentBattle?.waveIndex || 0; - this.waveSeed = Utils.shiftCharCodes(this.seed, wave); + this.waveSeed = shiftCharCodes(this.seed, wave); Phaser.Math.RND.sow([this.waveSeed]); console.log("Wave Seed:", this.waveSeed, wave); this.rngCounter = 0; @@ -1850,7 +1861,7 @@ export default class BattleScene extends SceneBase { const tempRngOffset = this.rngOffset; const tempRngSeedOverride = this.rngSeedOverride; const state = Phaser.Math.RND.state(); - Phaser.Math.RND.sow([Utils.shiftCharCodes(seedOverride || this.seed, offset)]); + Phaser.Math.RND.sow([shiftCharCodes(seedOverride || this.seed, offset)]); this.rngCounter = 0; this.rngOffset = offset; this.rngSeedOverride = seedOverride || ""; @@ -1995,7 +2006,7 @@ export default class BattleScene extends SceneBase { if (this.money === undefined) { return; } - const formattedMoney = Utils.formatMoney(this.moneyFormat, this.money); + const formattedMoney = formatMoney(this.moneyFormat, this.money); this.moneyText.setText(i18next.t("battleScene:moneyOwned", { formattedMoney })); this.fieldUI.moveAbove(this.moneyText, this.luckText); if (forceVisible) { @@ -2152,12 +2163,12 @@ export default class BattleScene extends SceneBase { ), ] : allSpecies.filter(s => s.isCatchable()); - return filteredSpecies[Utils.randSeedInt(filteredSpecies.length)]; + return filteredSpecies[randSeedInt(filteredSpecies.length)]; } generateRandomBiome(waveIndex: number): Biome { const relWave = waveIndex % 250; - const biomes = Utils.getEnumValues(Biome).filter(b => b !== Biome.TOWN && b !== Biome.END); + const biomes = getEnumValues(Biome).filter(b => b !== Biome.TOWN && b !== Biome.END); const maxDepth = biomeDepths[Biome.END][0] - 2; const depthWeights = new Array(maxDepth + 1) .fill(null) @@ -2169,7 +2180,7 @@ export default class BattleScene extends SceneBase { biomeThresholds.push(totalWeight); } - const randInt = Utils.randSeedInt(totalWeight); + const randInt = randSeedInt(totalWeight); for (let i = 0; i < biomes.length; i++) { if (randInt < biomeThresholds[i]) { @@ -2177,7 +2188,7 @@ export default class BattleScene extends SceneBase { } } - return biomes[Utils.randSeedInt(biomes.length)]; + return biomes[randSeedInt(biomes.length)]; } isBgmPlaying(): boolean { @@ -2362,7 +2373,7 @@ export default class BattleScene extends SceneBase { this.bgmResumeTimer.destroy(); } if (resumeBgm) { - this.bgmResumeTimer = this.time.delayedCall(pauseDuration || Utils.fixedInt(sound.totalDuration * 1000), () => { + this.bgmResumeTimer = this.time.delayedCall(pauseDuration || fixedInt(sound.totalDuration * 1000), () => { this.resumeBgm(); this.bgmResumeTimer = null; }); @@ -2955,7 +2966,7 @@ export default class BattleScene extends SceneBase { const args: unknown[] = []; if (modifier instanceof PokemonHpRestoreModifier) { if (!(modifier as PokemonHpRestoreModifier).fainted) { - const hpRestoreMultiplier = new Utils.NumberHolder(1); + const hpRestoreMultiplier = new NumberHolder(1); this.applyModifiers(HealingBoosterModifier, true, hpRestoreMultiplier); args.push(hpRestoreMultiplier.value); } else { @@ -2963,7 +2974,7 @@ export default class BattleScene extends SceneBase { } } else if (modifier instanceof FusePokemonModifier) { args.push(this.getPokemonById(modifier.fusePokemonId) as PlayerPokemon); - } else if (modifier instanceof RememberMoveModifier && !Utils.isNullOrUndefined(cost)) { + } else if (modifier instanceof RememberMoveModifier && !isNullOrUndefined(cost)) { args.push(cost); } @@ -3032,7 +3043,7 @@ export default class BattleScene extends SceneBase { itemLost = true, ): boolean { const source = itemModifier.pokemonId ? itemModifier.getPokemon() : null; - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); if (source && source.isPlayer() !== target.isPlayer()) { applyAbAttrs(BlockItemTheftAbAttr, source, cancelled); @@ -3101,7 +3112,7 @@ export default class BattleScene extends SceneBase { canTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, transferQuantity = 1): boolean { const mod = itemModifier.clone() as PokemonHeldItemModifier; const source = mod.pokemonId ? mod.getPokemon() : null; - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); if (source && source.isPlayer() !== target.isPlayer()) { applyAbAttrs(BlockItemTheftAbAttr, source, cancelled); @@ -3195,7 +3206,7 @@ export default class BattleScene extends SceneBase { } let count = 0; for (let c = 0; c < chances; c++) { - if (!Utils.randSeedInt(this.gameMode.getEnemyModifierChance(isBoss))) { + if (!randSeedInt(this.gameMode.getEnemyModifierChance(isBoss))) { count++; } } @@ -3371,7 +3382,7 @@ export default class BattleScene extends SceneBase { if (mods.length < 1) { return mods; } - const rand = Utils.randSeedInt(mods.length); + const rand = randSeedInt(mods.length); return [mods[rand], ...shuffleModifiers(mods.filter((_, i) => i !== rand))]; }; modifiers = shuffleModifiers(modifiers); @@ -3597,7 +3608,7 @@ export default class BattleScene extends SceneBase { */ initFinalBossPhaseTwo(pokemon: Pokemon): void { if (pokemon instanceof EnemyPokemon && pokemon.isBoss() && !pokemon.formIndex && pokemon.bossSegmentIndex < 1) { - this.fadeOutBgm(Utils.fixedInt(2000), false); + this.fadeOutBgm(fixedInt(2000), false); this.ui.showDialogue( battleSpecDialogue[BattleSpec.FINAL_BOSS].firstStageWin, pokemon.species.name, @@ -3700,7 +3711,7 @@ export default class BattleScene extends SceneBase { if (Overrides.XP_MULTIPLIER_OVERRIDE !== null) { expMultiplier = Overrides.XP_MULTIPLIER_OVERRIDE; } - const pokemonExp = new Utils.NumberHolder(expValue * expMultiplier); + const pokemonExp = new NumberHolder(expValue * expMultiplier); this.applyModifiers(PokemonExpBoosterModifier, true, partyMember, pokemonExp); partyMemberExp.push(Math.floor(pokemonExp.value)); } @@ -3849,7 +3860,7 @@ export default class BattleScene extends SceneBase { while (i < this.mysteryEncounterSaveData.queuedEncounters.length && !!encounter) { const candidate = this.mysteryEncounterSaveData.queuedEncounters[i]; const forcedChance = candidate.spawnPercent; - if (Utils.randSeedInt(100) < forcedChance) { + if (randSeedInt(100) < forcedChance) { encounter = allMysteryEncounters[candidate.type]; } @@ -3882,7 +3893,7 @@ export default class BattleScene extends SceneBase { } const totalWeight = tierWeights.reduce((a, b) => a + b); - const tierValue = Utils.randSeedInt(totalWeight); + const tierValue = randSeedInt(totalWeight); const commonThreshold = totalWeight - tierWeights[0]; const greatThreshold = totalWeight - tierWeights[0] - tierWeights[1]; const ultraThreshold = totalWeight - tierWeights[0] - tierWeights[1] - tierWeights[2]; @@ -3974,7 +3985,7 @@ export default class BattleScene extends SceneBase { console.log("No Mystery Encounters found, falling back to Mysterious Challengers."); return allMysteryEncounters[MysteryEncounterType.MYSTERIOUS_CHALLENGERS]; } - encounter = availableEncounters[Utils.randSeedInt(availableEncounters.length)]; + encounter = availableEncounters[randSeedInt(availableEncounters.length)]; // New encounter object to not dirty flags encounter = new MysteryEncounter(encounter); encounter.populateDialogueTokensFromRequirements(); diff --git a/src/battle.ts b/src/battle.ts index 367c52568dc..fb5af223b8f 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -1,6 +1,14 @@ import { globalScene } from "#app/global-scene"; import type { Command } from "./ui/command-ui-handler"; -import * as Utils from "./utils"; +import { + randomString, + getEnumValues, + NumberHolder, + randSeedInt, + shiftCharCodes, + randSeedItem, + randInt, +} from "#app/utils"; import Trainer, { TrainerVariant } from "./field/trainer"; import type { GameMode } from "./game-mode"; import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier"; @@ -99,7 +107,7 @@ export default class Battle { public postBattleLoot: PokemonHeldItemModifier[] = []; public escapeAttempts = 0; public lastMove: Moves; - public battleSeed: string = Utils.randomString(16, true); + public battleSeed: string = randomString(16, true); private battleSeedState: string | null = null; public moneyScattered = 0; /** Primarily for double battles, keeps track of last enemy and player pokemon that triggered its ability or used a move */ @@ -181,8 +189,8 @@ export default class Battle { incrementTurn(): void { this.turn++; - this.turnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [bt, null])); - this.preTurnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [bt, null])); + this.turnCommands = Object.fromEntries(getEnumValues(BattlerIndex).map(bt => [bt, null])); + this.preTurnCommands = Object.fromEntries(getEnumValues(BattlerIndex).map(bt => [bt, null])); this.battleSeedState = null; } @@ -211,7 +219,7 @@ export default class Battle { } pickUpScatteredMoney(): void { - const moneyAmount = new Utils.NumberHolder(globalScene.currentBattle.moneyScattered); + const moneyAmount = new NumberHolder(globalScene.currentBattle.moneyScattered); globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); if (globalScene.arena.getTag(ArenaTagType.HAPPY_HOUR)) { @@ -448,7 +456,7 @@ export default class Battle { } /** - * Generates a random number using the current battle's seed. Calls {@linkcode Utils.randSeedInt} + * Generates a random number using the current battle's seed. Calls {@linkcode randSeedInt} * @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min} * @param min The minimum integer to pick, default `0` * @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1) @@ -463,12 +471,12 @@ export default class Battle { if (this.battleSeedState) { Phaser.Math.RND.state(this.battleSeedState); } else { - Phaser.Math.RND.sow([Utils.shiftCharCodes(this.battleSeed, this.turn << 6)]); + Phaser.Math.RND.sow([shiftCharCodes(this.battleSeed, this.turn << 6)]); console.log("Battle Seed:", this.battleSeed); } globalScene.rngCounter = this.rngCounter++; globalScene.rngSeedOverride = this.battleSeed; - const ret = Utils.randSeedInt(range, min); + const ret = randSeedInt(range, min); this.battleSeedState = Phaser.Math.RND.state(); Phaser.Math.RND.state(state); globalScene.rngCounter = tempRngCounter; @@ -554,19 +562,19 @@ export function getRandomTrainerFunc( seedOffset = 0, ): GetTrainerFunc { return () => { - const rand = Utils.randSeedInt(trainerPool.length); + const rand = randSeedInt(trainerPool.length); const trainerTypes: TrainerType[] = []; globalScene.executeWithSeedOffset(() => { for (const trainerPoolEntry of trainerPool) { - const trainerType = Array.isArray(trainerPoolEntry) ? Utils.randSeedItem(trainerPoolEntry) : trainerPoolEntry; + const trainerType = Array.isArray(trainerPoolEntry) ? randSeedItem(trainerPoolEntry) : trainerPoolEntry; trainerTypes.push(trainerType); } }, seedOffset); let trainerGender = TrainerVariant.DEFAULT; if (randomGender) { - trainerGender = Utils.randInt(2) === 0 ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT; + trainerGender = randInt(2) === 0 ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT; } /* 1/3 chance for evil team grunts to be double battles */ @@ -585,7 +593,7 @@ export function getRandomTrainerFunc( const isEvilTeamGrunt = evilTeamGrunts.includes(trainerTypes[rand]); if (trainerConfigs[trainerTypes[rand]].hasDouble && isEvilTeamGrunt) { - return new Trainer(trainerTypes[rand], Utils.randInt(3) === 0 ? TrainerVariant.DOUBLE : trainerGender); + return new Trainer(trainerTypes[rand], randInt(3) === 0 ? TrainerVariant.DOUBLE : trainerGender); } return new Trainer(trainerTypes[rand], trainerGender); @@ -608,7 +616,7 @@ export const classicFixedBattles: FixedBattleConfigs = { [ClassicFixedBossWaves.TOWN_YOUNGSTER]: new FixedBattleConfig() .setBattleType(BattleType.TRAINER) .setGetTrainerFunc( - () => new Trainer(TrainerType.YOUNGSTER, Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT), + () => new Trainer(TrainerType.YOUNGSTER, randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT), ), [ClassicFixedBossWaves.RIVAL_1]: new FixedBattleConfig() .setBattleType(BattleType.TRAINER) diff --git a/src/data/ability.ts b/src/data/ability.ts index f8c9b4cb8fe..b07f13c18e9 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2,8 +2,7 @@ import type { EnemyPokemon, PokemonMove } from "../field/pokemon"; import type Pokemon from "../field/pokemon"; import { HitResult, MoveResult, PlayerPokemon } from "../field/pokemon"; import { PokemonType } from "#enums/pokemon-type"; -import type { Constructor } from "#app/utils"; -import * as Utils from "../utils"; +import { BooleanHolder, NumberHolder, toDmgValue, isNullOrUndefined, randSeedItem, randSeedInt, type Constructor } from "#app/utils"; import { getPokemonNameWithAffix } from "../messages"; import type { Weather } from "#app/data/weather"; import type { BattlerTag } from "./battler-tags"; @@ -196,7 +195,7 @@ export abstract class AbAttr { * @param args - Extra args passed to the function. Handled by child classes. * @see {@linkcode canApply} */ - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder | null, args: any[]): void {} + apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder | null, args: any[]): void {} getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null { return null; @@ -230,7 +229,7 @@ export class BlockRecoilDamageAttr extends AbAttr { super(false); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; } @@ -251,10 +250,10 @@ export class DoubleBattleChanceAbAttr extends AbAttr { /** * Increases the chance of a double battle occurring - * @param args [0] {@linkcode Utils.NumberHolder} for double battle chance + * @param args [0] {@linkcode NumberHolder} for double battle chance */ - override apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, args: any[]): void { - const doubleBattleChance = args[0] as Utils.NumberHolder; + override apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder, args: any[]): void { + const doubleBattleChance = args[0] as NumberHolder; // This is divided because the chance is generated as a number from 0 to doubleBattleChance.value using Utils.randSeedInt // A double battle will initiate if the generated number is 0 doubleBattleChance.value = doubleBattleChance.value / 4; @@ -299,7 +298,7 @@ export class PostTeraFormChangeStatChangeAbAttr extends AbAttr { this.stages = stages; } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder | null, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder | null, args: any[]): void { const statStageChangePhases: StatStageChangePhase[] = []; if (!simulated) { @@ -331,7 +330,7 @@ export class ClearWeatherAbAttr extends AbAttr { return globalScene.arena.canSetWeather(WeatherType.NONE); } - public override apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + public override apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: BooleanHolder, args: any[]): void { if (!simulated) { globalScene.arena.trySetWeather(WeatherType.NONE, pokemon); } @@ -357,7 +356,7 @@ export class ClearTerrainAbAttr extends AbAttr { return globalScene.arena.canSetTerrain(TerrainType.NONE); } - public override apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + public override apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: BooleanHolder, args: any[]): void { if (!simulated) { globalScene.arena.trySetTerrain(TerrainType.NONE, true, pokemon); } @@ -373,7 +372,7 @@ export class PreDefendAbAttr extends AbAttr { simulated: boolean, attacker: Pokemon, move: Move | null, - cancelled: Utils.BooleanHolder | null, + cancelled: BooleanHolder | null, args: any[]): boolean { return true; } @@ -384,19 +383,19 @@ export class PreDefendAbAttr extends AbAttr { simulated: boolean, attacker: Pokemon, move: Move | null, - cancelled: Utils.BooleanHolder | null, + cancelled: BooleanHolder | null, args: any[], ): void {} } export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr { - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move | null, cancelled: BooleanHolder | null, args: any[]): boolean { return pokemon.isFullHp() && pokemon.getMaxHp() > 1 //Checks if pokemon has wonder_guard (which forces 1hp) - && (args[0] as Utils.NumberHolder).value >= pokemon.hp; //Damage >= hp + && (args[0] as NumberHolder).value >= pokemon.hp; //Damage >= hp } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { if (!simulated) { pokemon.addTag(BattlerTagType.STURDY, 1); } @@ -404,7 +403,7 @@ export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr { } export class BlockItemTheftAbAttr extends AbAttr { - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; } @@ -422,11 +421,11 @@ export class StabBoostAbAttr extends AbAttr { } override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return (args[0] as Utils.NumberHolder).value > 1; + return (args[0] as NumberHolder).value > 1; } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { - (args[0] as Utils.NumberHolder).value += 0.5; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + (args[0] as NumberHolder).value += 0.5; } } @@ -441,12 +440,12 @@ export class ReceivedMoveDamageMultiplierAbAttr extends PreDefendAbAttr { this.damageMultiplier = damageMultiplier; } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { return this.condition(pokemon, attacker, move); } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { - (args[0] as Utils.NumberHolder).value = Utils.toDmgValue((args[0] as Utils.NumberHolder).value * this.damageMultiplier); + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { + (args[0] as NumberHolder).value = toDmgValue((args[0] as NumberHolder).value * this.damageMultiplier); } } @@ -465,11 +464,11 @@ export class AlliedFieldDamageReductionAbAttr extends PreDefendAbAttr { /** * Handles the damage reduction * @param args - * - `[0]` {@linkcode Utils.NumberHolder} - The damage being dealt + * - `[0]` {@linkcode NumberHolder} - The damage being dealt */ - override applyPreDefend(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _attacker: Pokemon, _move: Move, _cancelled: Utils.BooleanHolder, args: any[]): void { - const damage = args[0] as Utils.NumberHolder; - damage.value = Utils.toDmgValue(damage.value * this.damageMultiplier); + override applyPreDefend(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _attacker: Pokemon, _move: Move, _cancelled: BooleanHolder, args: any[]): void { + const damage = args[0] as NumberHolder; + damage.value = toDmgValue(damage.value * this.damageMultiplier); } } @@ -496,7 +495,7 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr { this.condition = condition ?? null; } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { return ![ MoveTarget.BOTH_SIDES, MoveTarget.ENEMY_SIDE, MoveTarget.USER_SIDE ].includes(move.moveTarget) && attacker !== pokemon && attacker.getMoveType(move) === this.immuneType; } @@ -506,12 +505,12 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr { * @param passive - Whether the ability is passive. * @param attacker {@linkcode Pokemon} The attacking Pokemon. * @param move {@linkcode Move} The attacking move. - * @param cancelled {@linkcode Utils.BooleanHolder} - A holder for a boolean value indicating if the move was cancelled. - * @param args [0] {@linkcode Utils.NumberHolder} gets set to 0 if move is immuned by an ability. + * @param cancelled {@linkcode BooleanHolder} - A holder for a boolean value indicating if the move was cancelled. + * @param args [0] {@linkcode NumberHolder} gets set to 0 if move is immuned by an ability. * @param args [1] - Whether the move is simulated. */ - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { - (args[0] as Utils.NumberHolder).value = 0; + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { + (args[0] as NumberHolder).value = 0; } getImmuneType(): PokemonType | null { @@ -528,7 +527,7 @@ export class AttackTypeImmunityAbAttr extends TypeImmunityAbAttr { super(immuneType, condition); } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { return move.category !== MoveCategory.STATUS && !move.hasAttr(NeutralDamageAgainstFlyingTypeMultiplierAttr) && super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); } @@ -538,7 +537,7 @@ export class AttackTypeImmunityAbAttr extends TypeImmunityAbAttr { * Type immunity abilities that do not give additional benefits (HP recovery, stat boosts, etc) are not immune to status moves of the type * Example: Levitate */ - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { // this is a hacky way to fix the Levitate/Thousand Arrows interaction, but it works for now... super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); } @@ -549,16 +548,16 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr { super(immuneType); } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { return super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); if (!pokemon.isFullHp() && !simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), - Utils.toDmgValue(pokemon.getMaxHp() / 4), i18next.t("abilityTriggers:typeImmunityHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); + toDmgValue(pokemon.getMaxHp() / 4), i18next.t("abilityTriggers:typeImmunityHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); cancelled.value = true; // Suppresses "No Effect" message } } @@ -575,11 +574,11 @@ class TypeImmunityStatStageChangeAbAttr extends TypeImmunityAbAttr { this.stages = stages; } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { return super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); cancelled.value = true; // Suppresses "No Effect" message if (!simulated) { @@ -599,11 +598,11 @@ class TypeImmunityAddBattlerTagAbAttr extends TypeImmunityAbAttr { this.turnCount = turnCount; } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { return super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); cancelled.value = true; // Suppresses "No Effect" message if (!simulated) { @@ -617,16 +616,16 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr { super(null, condition); } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { const modifierValue = args.length > 0 - ? (args[0] as Utils.NumberHolder).value + ? (args[0] as NumberHolder).value : pokemon.getAttackTypeEffectiveness(attacker.getMoveType(move), attacker, undefined, undefined, move); return move instanceof AttackMove && modifierValue < 2; } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; // Suppresses "No Effect" message - (args[0] as Utils.NumberHolder).value = 0; + (args[0] as NumberHolder).value = 0; } getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { @@ -644,9 +643,9 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr { */ export class FullHpResistTypeAbAttr extends PreDefendAbAttr { - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move | null, cancelled: BooleanHolder | null, args: any[]): boolean { const typeMultiplier = args[0]; - return (typeMultiplier && typeMultiplier instanceof Utils.NumberHolder) && !(move && move.hasAttr(FixedDamageAttr)) && pokemon.isFullHp() && typeMultiplier.value > 0.5; + return (typeMultiplier && typeMultiplier instanceof NumberHolder) && !(move && move.hasAttr(FixedDamageAttr)) && pokemon.isFullHp() && typeMultiplier.value > 0.5; } /** @@ -665,7 +664,7 @@ export class FullHpResistTypeAbAttr extends PreDefendAbAttr { simulated: boolean, attacker: Pokemon, move: Move | null, - cancelled: Utils.BooleanHolder | null, + cancelled: BooleanHolder | null, args: any[]): void { const typeMultiplier = args[0]; typeMultiplier.value = 0.5; @@ -704,11 +703,11 @@ export class PostDefendAbAttr extends AbAttr { export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr { - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { return !(move.moveTarget === MoveTarget.USER || move.moveTarget === MoveTarget.NEAR_ALLY) && move.getPriority(attacker) > 0 && !move.isMultiTarget(); } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; } } @@ -743,11 +742,11 @@ export class MoveImmunityAbAttr extends PreDefendAbAttr { this.immuneCondition = immuneCondition; } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { return this.immuneCondition(pokemon, attacker, move); } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; } @@ -768,13 +767,13 @@ export class WonderSkinAbAttr extends PreDefendAbAttr { super(false); } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { - const moveAccuracy = args[0] as Utils.NumberHolder; + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { + const moveAccuracy = args[0] as NumberHolder; return move.category === MoveCategory.STATUS && moveAccuracy.value >= 50; } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { - const moveAccuracy = args[0] as Utils.NumberHolder; + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { + const moveAccuracy = args[0] as NumberHolder; moveAccuracy.value = 50; } } @@ -789,11 +788,11 @@ export class MoveImmunityStatStageChangeAbAttr extends MoveImmunityAbAttr { this.stages = stages; } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { return !simulated && super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); } - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); } @@ -855,7 +854,7 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr { if (this.allOthers) { const ally = pokemon.getAlly(); - const otherPokemon = !Utils.isNullOrUndefined(ally) ? pokemon.getOpponents().concat([ ally ]) : pokemon.getOpponents(); + const otherPokemon = !isNullOrUndefined(ally) ? pokemon.getOpponents().concat([ ally ]) : pokemon.getOpponents(); for (const other of otherPokemon) { globalScene.unshiftPhase(new StatStageChangePhase((other).getBattlerIndex(), false, [ this.stat ], this.stages)); } @@ -1090,8 +1089,8 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr { } override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { - attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), { result: HitResult.INDIRECT }); - attacker.turnData.damageTaken += Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)); + attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), { result: HitResult.INDIRECT }); + attacker.turnData.damageTaken += toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)); } override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { @@ -1291,16 +1290,16 @@ export class MoveEffectChanceMultiplierAbAttr extends AbAttr { override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const exceptMoves = [ Moves.ORDER_UP, Moves.ELECTRO_SHOT ]; - return !((args[0] as Utils.NumberHolder).value <= 0 || exceptMoves.includes((args[1] as Move).id)); + return !((args[0] as NumberHolder).value <= 0 || exceptMoves.includes((args[1] as Move).id)); } /** - * @param args [0]: {@linkcode Utils.NumberHolder} Move additional effect chance. Has to be higher than or equal to 0. + * @param args [0]: {@linkcode NumberHolder} Move additional effect chance. Has to be higher than or equal to 0. * [1]: {@linkcode Moves } Move used by the ability user. */ - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { - (args[0] as Utils.NumberHolder).value *= this.chanceMultiplier; - (args[0] as Utils.NumberHolder).value = Math.min((args[0] as Utils.NumberHolder).value, 100); + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + (args[0] as NumberHolder).value *= this.chanceMultiplier; + (args[0] as NumberHolder).value = Math.min((args[0] as NumberHolder).value, 100); } } @@ -1314,15 +1313,15 @@ export class IgnoreMoveEffectsAbAttr extends PreDefendAbAttr { super(showAbility); } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { - return (args[0] as Utils.NumberHolder).value > 0; + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move | null, cancelled: BooleanHolder | null, args: any[]): boolean { + return (args[0] as NumberHolder).value > 0; } /** - * @param args [0]: {@linkcode Utils.NumberHolder} Move additional effect chance. + * @param args [0]: {@linkcode NumberHolder} Move additional effect chance. */ - override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { - (args[0] as Utils.NumberHolder).value = 0; + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder, args: any[]): void { + (args[0] as NumberHolder).value = 0; } } @@ -1337,7 +1336,7 @@ export class FieldPreventExplosiveMovesAbAttr extends AbAttr { pokemon: Pokemon, passive: boolean, simulated: boolean, - cancelled: Utils.BooleanHolder, + cancelled: BooleanHolder, args: any[], ): void { cancelled.value = true; @@ -1349,7 +1348,7 @@ export class FieldPreventExplosiveMovesAbAttr extends AbAttr { * If this ability cannot stack, a BooleanHolder can be used to prevent this from stacking. * @see {@link applyFieldStatMultiplierAbAttrs} * @see {@link applyFieldStat} - * @see {@link Utils.BooleanHolder} + * @see {@link BooleanHolder} */ export class FieldMultiplyStatAbAttr extends AbAttr { private stat: Stat; @@ -1364,7 +1363,7 @@ export class FieldMultiplyStatAbAttr extends AbAttr { this.canStack = canStack; } - canApplyFieldStat(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: Stat, statValue: Utils.NumberHolder, checkedPokemon: Pokemon, hasApplied: Utils.BooleanHolder, args: any[]): boolean { + canApplyFieldStat(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: Stat, statValue: NumberHolder, checkedPokemon: Pokemon, hasApplied: BooleanHolder, args: any[]): boolean { return this.canStack || !hasApplied.value && this.stat === stat && checkedPokemon.getAbilityAttrs(FieldMultiplyStatAbAttr).every(attr => (attr as FieldMultiplyStatAbAttr).stat !== stat); } @@ -1374,12 +1373,12 @@ export class FieldMultiplyStatAbAttr extends AbAttr { * @param pokemon {@linkcode Pokemon} the Pokemon using this ability * @param passive {@linkcode boolean} unused * @param stat {@linkcode Stat} the type of the checked stat - * @param statValue {@linkcode Utils.NumberHolder} the value of the checked stat + * @param statValue {@linkcode NumberHolder} the value of the checked stat * @param checkedPokemon {@linkcode Pokemon} the Pokemon this ability is targeting - * @param hasApplied {@linkcode Utils.BooleanHolder} whether or not another multiplier has been applied to this stat + * @param hasApplied {@linkcode BooleanHolder} whether or not another multiplier has been applied to this stat * @param args {any[]} unused */ - applyFieldStat(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: Stat, statValue: Utils.NumberHolder, checkedPokemon: Pokemon, hasApplied: Utils.BooleanHolder, args: any[]): void { + applyFieldStat(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: Stat, statValue: NumberHolder, checkedPokemon: Pokemon, hasApplied: BooleanHolder, args: any[]): void { statValue.value *= this.multiplier; hasApplied.value = true; } @@ -1401,10 +1400,10 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr { // TODO: Decouple this into two attributes (type change / power boost) override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void { - if (args[0] && args[0] instanceof Utils.NumberHolder) { + if (args[0] && args[0] instanceof NumberHolder) { args[0].value = this.newType; } - if (args[1] && args[1] instanceof Utils.NumberHolder) { + if (args[1] && args[1] instanceof NumberHolder) { args[1].value *= this.powerMultiplier; } } @@ -1482,12 +1481,12 @@ export class AddSecondStrikeAbAttr extends PreAttackAbAttr { * @param defender n/a * @param move the {@linkcode Move} used by the ability source * @param args Additional arguments: - * - `[0]` the number of strikes this move currently has ({@linkcode Utils.NumberHolder}) - * - `[1]` the damage multiplier for the current strike ({@linkcode Utils.NumberHolder}) + * - `[0]` the number of strikes this move currently has ({@linkcode NumberHolder}) + * - `[1]` the damage multiplier for the current strike ({@linkcode NumberHolder}) */ override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void { - const hitCount = args[0] as Utils.NumberHolder; - const multiplier = args[1] as Utils.NumberHolder; + const hitCount = args[0] as NumberHolder; + const multiplier = args[1] as NumberHolder; if (hitCount?.value) { hitCount.value += 1; } @@ -1527,8 +1526,8 @@ export class DamageBoostAbAttr extends PreAttackAbAttr { * @param args Utils.NumberHolder as damage */ override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void { - const power = args[0] as Utils.NumberHolder; - power.value = Utils.toDmgValue(power.value * this.damageMultiplier); + const power = args[0] as NumberHolder; + power.value = toDmgValue(power.value * this.damageMultiplier); } } @@ -1547,7 +1546,7 @@ export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr { } override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void { - (args[0] as Utils.NumberHolder).value *= this.powerMultiplier; + (args[0] as NumberHolder).value *= this.powerMultiplier; } } @@ -1590,7 +1589,7 @@ export class VariableMovePowerBoostAbAttr extends VariableMovePowerAbAttr { override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void { const multiplier = this.mult(pokemon, defender, move); - (args[0] as Utils.NumberHolder).value *= multiplier; + (args[0] as NumberHolder).value *= multiplier; } } @@ -1619,7 +1618,7 @@ export class FieldMovePowerBoostAbAttr extends AbAttr { applyPreAttack(pokemon: Pokemon | null, passive: boolean | null, simulated: boolean, defender: Pokemon | null, move: Move, args: any[]): void { if (this.condition(pokemon, defender, move)) { - (args[0] as Utils.NumberHolder).value *= this.powerMultiplier; + (args[0] as NumberHolder).value *= this.powerMultiplier; } } } @@ -1682,7 +1681,7 @@ export class StatMultiplierAbAttr extends AbAttr { _passive: boolean, simulated: boolean, stat: BattleStat, - statValue: Utils.NumberHolder, + statValue: NumberHolder, args: any[]): boolean { const move = (args[0] as Move); return stat === this.stat && (!this.condition || this.condition(pokemon, null, move)); @@ -1693,7 +1692,7 @@ export class StatMultiplierAbAttr extends AbAttr { _passive: boolean, simulated: boolean, stat: BattleStat, - statValue: Utils.NumberHolder, + statValue: NumberHolder, args: any[]): void { statValue.value *= this.multiplier; } @@ -1766,13 +1765,13 @@ export class AllyStatMultiplierAbAttr extends AbAttr { * @param passive - unused * @param _simulated - Whether the ability is being simulated (unused) * @param _stat - The type of the checked {@linkcode Stat} (unused) - * @param statValue - {@linkcode Utils.NumberHolder} containing the value of the checked stat + * @param statValue - {@linkcode NumberHolder} containing the value of the checked stat * @param _checkedPokemon - The {@linkcode Pokemon} this ability is targeting (unused) * @param _ignoreAbility - Whether the ability should be ignored if possible * @param _args - unused * @returns `true` if this changed the checked stat, `false` otherwise. */ - applyAllyStat(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _stat: BattleStat, statValue: Utils.NumberHolder, _checkedPokemon: Pokemon, _ignoreAbility: boolean, _args: any[]) { + applyAllyStat(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _stat: BattleStat, statValue: NumberHolder, _checkedPokemon: Pokemon, _ignoreAbility: boolean, _args: any[]) { statValue.value *= this.multiplier; } @@ -1782,13 +1781,13 @@ export class AllyStatMultiplierAbAttr extends AbAttr { * @param passive - unused * @param simulated - Whether the ability is being simulated (unused) * @param stat - The type of the checked {@linkcode Stat} - * @param statValue - {@linkcode Utils.NumberHolder} containing the value of the checked stat + * @param statValue - {@linkcode NumberHolder} containing the value of the checked stat * @param checkedPokemon - The {@linkcode Pokemon} this ability is targeting (unused) * @param ignoreAbility - Whether the ability should be ignored if possible * @param args - unused * @returns `true` if this can apply to the checked stat, `false` otherwise. */ - canApplyAllyStat(pokemon: Pokemon, _passive: boolean, simulated: boolean, stat: BattleStat, statValue: Utils.NumberHolder, checkedPokemon: Pokemon, ignoreAbility: boolean, args: any[]): boolean { + canApplyAllyStat(pokemon: Pokemon, _passive: boolean, simulated: boolean, stat: BattleStat, statValue: NumberHolder, checkedPokemon: Pokemon, ignoreAbility: boolean, args: any[]): boolean { return stat === this.stat && !(ignoreAbility && this.ignorable); } } @@ -2220,8 +2219,8 @@ export class IgnoreOpponentStatStagesAbAttr extends AbAttr { * @param _cancelled n/a * @param args A BooleanHolder that represents whether or not to ignore a stat's stat changes */ - override apply(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _cancelled: Utils.BooleanHolder, args: any[]): void { - (args[1] as Utils.BooleanHolder).value = true; + override apply(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _cancelled: BooleanHolder, args: any[]): void { + (args[1] as BooleanHolder).value = true; } } @@ -2230,7 +2229,7 @@ export class IntimidateImmunityAbAttr extends AbAttr { super(false); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; } @@ -2254,7 +2253,7 @@ export class PostIntimidateStatStageChangeAbAttr extends AbAttr { this.overwrites = !!overwrites; } - override apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: BooleanHolder, args: any[]): void { if (!simulated) { globalScene.pushPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages)); } @@ -2461,7 +2460,7 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages)); } else { for (const opponent of pokemon.getOpponents()) { - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); if (this.intimidate) { applyAbAttrs(IntimidateImmunityAbAttr, opponent, cancelled, simulated); applyAbAttrs(PostIntimidateStatStageChangeAbAttr, opponent, cancelled, simulated); @@ -2495,9 +2494,9 @@ export class PostSummonAllyHealAbAttr extends PostSummonAbAttr { override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { const target = pokemon.getAlly(); - if (!simulated && !Utils.isNullOrUndefined(target)) { + if (!simulated && !isNullOrUndefined(target)) { globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), - Utils.toDmgValue(pokemon.getMaxHp() / this.healRatio), i18next.t("abilityTriggers:postSummonAllyHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(target), pokemonName: pokemon.name }), true, !this.showAnim)); + toDmgValue(pokemon.getMaxHp() / this.healRatio), i18next.t("abilityTriggers:postSummonAllyHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(target), pokemonName: pokemon.name }), true, !this.showAnim)); } } } @@ -2521,7 +2520,7 @@ export class PostSummonClearAllyStatStagesAbAttr extends PostSummonAbAttr { override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { const target = pokemon.getAlly(); - if (!simulated && !Utils.isNullOrUndefined(target)) { + if (!simulated && !isNullOrUndefined(target)) { for (const s of BATTLE_STATS) { target.setStatStage(s, 0); } @@ -2639,12 +2638,12 @@ export class PostSummonHealStatusAbAttr extends PostSummonRemoveEffectAbAttr { public override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const status = pokemon.status?.effect; - return !Utils.isNullOrUndefined(status) && (this.immuneEffects.length < 1 || this.immuneEffects.includes(status)) + return !isNullOrUndefined(status) && (this.immuneEffects.length < 1 || this.immuneEffects.includes(status)) } public override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { const status = pokemon.status?.effect; - if (!Utils.isNullOrUndefined(status)) { + if (!isNullOrUndefined(status)) { this.statusHealed = status; pokemon.resetStatus(false); pokemon.updateInfo(); @@ -2692,7 +2691,7 @@ export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr { let target: Pokemon; if (targets.length > 1) { - globalScene.executeWithSeedOffset(() => target = Utils.randSeedItem(targets), globalScene.currentBattle.waveIndex); + globalScene.executeWithSeedOffset(() => target = randSeedItem(targets), globalScene.currentBattle.waveIndex); } else { target = targets[0]; } @@ -2779,7 +2778,7 @@ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { } const ally = pokemon.getAlly(); - if (Utils.isNullOrUndefined(ally) || ally.getStatStages().every(s => s === 0)) { + if (isNullOrUndefined(ally) || ally.getStatStages().every(s => s === 0)) { return false; } @@ -2788,7 +2787,7 @@ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { const ally = pokemon.getAlly(); - if (!simulated && !Utils.isNullOrUndefined(ally)) { + if (!simulated && !isNullOrUndefined(ally)) { for (const s of BATTLE_STATS) { pokemon.setStatStage(s, ally.getStatStage(s)); } @@ -2825,7 +2824,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { target = targets[0]; return; } - target = Utils.randSeedItem(targets); + target = randSeedItem(targets); }, globalScene.currentBattle.waveIndex); } else { target = targets[0]; @@ -2934,7 +2933,7 @@ export class CommanderAbAttr extends AbAttr { // TODO: Should this work with X + Dondozo fusions? const ally = pokemon.getAlly(); - return globalScene.currentBattle?.double && !Utils.isNullOrUndefined(ally) && ally.species.speciesId === Species.DONDOZO + return globalScene.currentBattle?.double && !isNullOrUndefined(ally) && ally.species.speciesId === Species.DONDOZO && !(ally.isFainted() || ally.getTag(BattlerTagType.COMMANDED)); } @@ -2970,7 +2969,7 @@ export class PreSwitchOutResetStatusAbAttr extends PreSwitchOutAbAttr { } override canApplyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return !Utils.isNullOrUndefined(pokemon.status); + return !isNullOrUndefined(pokemon.status); } override applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { @@ -3052,7 +3051,7 @@ export class PreSwitchOutHealAbAttr extends PreSwitchOutAbAttr { override applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { if (!simulated) { - const healAmount = Utils.toDmgValue(pokemon.getMaxHp() * 0.33); + const healAmount = toDmgValue(pokemon.getMaxHp() * 0.33); pokemon.heal(healAmount); pokemon.updateInfo(); } @@ -3166,7 +3165,7 @@ export class PreStatStageChangeAbAttr extends AbAttr { passive: boolean, simulated: boolean, stat: BattleStat, - cancelled: Utils.BooleanHolder, + cancelled: BooleanHolder, args: any[]): boolean { return true; } @@ -3176,7 +3175,7 @@ export class PreStatStageChangeAbAttr extends AbAttr { passive: boolean, simulated: boolean, stat: BattleStat, - cancelled: Utils.BooleanHolder, + cancelled: BooleanHolder, args: any[], ): void {} } @@ -3195,10 +3194,10 @@ export class ReflectStatStageChangeAbAttr extends PreStatStageChangeAbAttr { * @param _passive N/A * @param simulated `true` if the ability is being simulated by the AI * @param stat the {@linkcode BattleStat} being affected - * @param cancelled The {@linkcode Utils.BooleanHolder} that will be set to true due to reflection + * @param cancelled The {@linkcode BooleanHolder} that will be set to true due to reflection * @param args */ - override applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): void { + override applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, simulated: boolean, stat: BattleStat, cancelled: BooleanHolder, args: any[]): void { const attacker: Pokemon = args[0]; const stages = args[1]; this.reflectedStat = stat; @@ -3230,8 +3229,8 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr { this.protectedStat = protectedStat; } - override canApplyPreStatStageChange(pokemon: Pokemon | null, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean { - return Utils.isNullOrUndefined(this.protectedStat) || stat === this.protectedStat; + override canApplyPreStatStageChange(pokemon: Pokemon | null, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: BooleanHolder, args: any[]): boolean { + return isNullOrUndefined(this.protectedStat) || stat === this.protectedStat; } /** @@ -3240,10 +3239,10 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr { * @param _passive * @param simulated * @param stat the {@linkcode BattleStat} being affected - * @param cancelled The {@linkcode Utils.BooleanHolder} that will be set to true if the stat is protected + * @param cancelled The {@linkcode BooleanHolder} that will be set to true if the stat is protected * @param _args */ - override applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, _args: any[]): void { + override applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, stat: BattleStat, cancelled: BooleanHolder, _args: any[]): void { cancelled.value = true; } @@ -3302,7 +3301,7 @@ export class PreSetStatusAbAttr extends AbAttr { passive: boolean, simulated: boolean, effect: StatusEffect | undefined, - cancelled: Utils.BooleanHolder, + cancelled: BooleanHolder, args: any[]): boolean { return true; } @@ -3312,7 +3311,7 @@ export class PreSetStatusAbAttr extends AbAttr { passive: boolean, simulated: boolean, effect: StatusEffect | undefined, - cancelled: Utils.BooleanHolder, + cancelled: BooleanHolder, args: any[], ): void {} } @@ -3332,7 +3331,7 @@ export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr { this.immuneEffects = immuneEffects; } - override canApplyPreSetStatus(pokemon: Pokemon, passive: boolean, simulated: boolean, effect: StatusEffect, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override canApplyPreSetStatus(pokemon: Pokemon, passive: boolean, simulated: boolean, effect: StatusEffect, cancelled: BooleanHolder, args: any[]): boolean { return effect !== StatusEffect.FAINT && this.immuneEffects.length < 1 || this.immuneEffects.includes(effect); } @@ -3345,7 +3344,7 @@ export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr { * @param cancelled - A holder for a boolean value indicating if the status application was cancelled. * @param args - n/a */ - override applyPreSetStatus(pokemon: Pokemon, passive: boolean, simulated: boolean, effect: StatusEffect, cancelled: Utils.BooleanHolder, args: any[]): void { + override applyPreSetStatus(pokemon: Pokemon, passive: boolean, simulated: boolean, effect: StatusEffect, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; } @@ -3400,7 +3399,7 @@ export class ConditionalUserFieldStatusEffectImmunityAbAttr extends UserFieldSta * @param args `Args[0]` is the target of the status effect, `Args[1]` is the source. * @returns Whether the ability can be applied to cancel the status effect. */ - override canApplyPreSetStatus(pokemon: Pokemon, passive: boolean, simulated: boolean, effect: StatusEffect, cancelled: Utils.BooleanHolder, args: [Pokemon, Pokemon | null, ...any]): boolean { + override canApplyPreSetStatus(pokemon: Pokemon, passive: boolean, simulated: boolean, effect: StatusEffect, cancelled: BooleanHolder, args: [Pokemon, Pokemon | null, ...any]): boolean { return (!cancelled.value && effect !== StatusEffect.FAINT && this.immuneEffects.length < 1 || this.immuneEffects.includes(effect)) && this.condition(args[0], args[1]); } @@ -3438,12 +3437,12 @@ export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbA * @param args Args[0] is the target pokemon of the stat change. * @returns */ - override canApplyPreStatStageChange(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: [Pokemon, ...any]): boolean { + override canApplyPreStatStageChange(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: BooleanHolder, args: [Pokemon, ...any]): boolean { const target = args[0]; if (!target) { return false; } - return !cancelled.value && (Utils.isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) && this.condition(target); + return !cancelled.value && (isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) && this.condition(target); } /** @@ -3455,7 +3454,7 @@ export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbA * @param cancelled Will be set to true if the stat change is prevented * @param _args unused */ - override applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _stat: BattleStat, cancelled: Utils.BooleanHolder, _args: any[]): void { + override applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _stat: BattleStat, cancelled: BooleanHolder, _args: any[]): void { cancelled.value = true; } } @@ -3467,7 +3466,7 @@ export class PreApplyBattlerTagAbAttr extends AbAttr { passive: boolean, simulated: boolean, tag: BattlerTag, - cancelled: Utils.BooleanHolder, + cancelled: BooleanHolder, args: any[], ): boolean { return true; @@ -3478,7 +3477,7 @@ export class PreApplyBattlerTagAbAttr extends AbAttr { passive: boolean, simulated: boolean, tag: BattlerTag, - cancelled: Utils.BooleanHolder, + cancelled: BooleanHolder, args: any[], ): void {} } @@ -3496,13 +3495,13 @@ export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr { this.immuneTagTypes = Array.isArray(immuneTagTypes) ? immuneTagTypes : [ immuneTagTypes ]; } - override canApplyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, simulated: boolean, tag: BattlerTag, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override canApplyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, simulated: boolean, tag: BattlerTag, cancelled: BooleanHolder, args: any[]): boolean { this.battlerTag = tag; return !cancelled.value && this.immuneTagTypes.includes(tag.tagType); } - override applyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, simulated: boolean, tag: BattlerTag, cancelled: Utils.BooleanHolder, args: any[]): void { + override applyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, simulated: boolean, tag: BattlerTag, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; } @@ -3540,7 +3539,7 @@ export class ConditionalUserFieldBattlerTagImmunityAbAttr extends UserFieldBattl * @param args Args[0] is the target that the tag is attempting to be applied to * @returns Whether the ability can be used to cancel the battler tag */ - override canApplyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, simulated: boolean, tag: BattlerTag, cancelled: Utils.BooleanHolder, args: [Pokemon, ...any]): boolean { + override canApplyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, simulated: boolean, tag: BattlerTag, cancelled: BooleanHolder, args: [Pokemon, ...any]): boolean { return super.canApplyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args) && this.condition(args[0]); } @@ -3556,8 +3555,8 @@ export class BlockCritAbAttr extends AbAttr { super(false); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { - (args[0] as Utils.BooleanHolder).value = true; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + (args[0] as BooleanHolder).value = true; } } @@ -3575,8 +3574,8 @@ export class BonusCritAbAttr extends AbAttr { * @param cancelled Unused * @param args Args[0] is a number holder containing the crit stage. */ - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: [Utils.NumberHolder, ...any]): void { - (args[0] as Utils.NumberHolder).value += 1; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: [NumberHolder, ...any]): void { + (args[0] as NumberHolder).value += 1; } } @@ -3590,12 +3589,12 @@ export class MultCritAbAttr extends AbAttr { } override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const critMult = args[0] as Utils.NumberHolder; + const critMult = args[0] as NumberHolder; return critMult.value > 1; } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { - const critMult = args[0] as Utils.NumberHolder; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + const critMult = args[0] as NumberHolder; critMult.value *= this.multAmount; } } @@ -3622,12 +3621,12 @@ export class ConditionalCritAbAttr extends AbAttr { /** * @param pokemon {@linkcode Pokemon} user. - * @param args [0] {@linkcode Utils.BooleanHolder} If true critical hit is guaranteed. + * @param args [0] {@linkcode BooleanHolder} If true critical hit is guaranteed. * [1] {@linkcode Pokemon} Target. * [2] {@linkcode Move} used by ability user. */ - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { - (args[0] as Utils.BooleanHolder).value = true; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + (args[0] as BooleanHolder).value = true; } } @@ -3636,7 +3635,7 @@ export class BlockNonDirectDamageAbAttr extends AbAttr { super(false); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; } } @@ -3666,16 +3665,16 @@ export class BlockStatusDamageAbAttr extends AbAttr { /** * @param {Pokemon} pokemon The pokemon with the ability * @param {boolean} passive N/A - * @param {Utils.BooleanHolder} cancelled Whether to cancel the status damage + * @param {BooleanHolder} cancelled Whether to cancel the status damage * @param {any[]} args N/A */ - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; } } export class BlockOneHitKOAbAttr extends AbAttr { - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; } } @@ -3704,8 +3703,8 @@ export class ChangeMovePriorityAbAttr extends AbAttr { return this.moveFunc(pokemon, args[0] as Move); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { - (args[1] as Utils.NumberHolder).value += this.changeAmount; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + (args[1] as NumberHolder).value += this.changeAmount; } } @@ -3717,7 +3716,7 @@ export class PreWeatherEffectAbAttr extends AbAttr { passive: Boolean, simulated: boolean, weather: Weather | null, - cancelled: Utils.BooleanHolder, + cancelled: BooleanHolder, args: any[]): boolean { return true; } @@ -3727,7 +3726,7 @@ export class PreWeatherEffectAbAttr extends AbAttr { passive: boolean, simulated: boolean, weather: Weather | null, - cancelled: Utils.BooleanHolder, + cancelled: BooleanHolder, args: any[], ): void {} } @@ -3743,11 +3742,11 @@ export class BlockWeatherDamageAttr extends PreWeatherDamageAbAttr { this.weatherTypes = weatherTypes; } - override canApplyPreWeatherEffect(pokemon: Pokemon, passive: Boolean, simulated: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override canApplyPreWeatherEffect(pokemon: Pokemon, passive: Boolean, simulated: boolean, weather: Weather, cancelled: BooleanHolder, args: any[]): boolean { return !this.weatherTypes.length || this.weatherTypes.indexOf(weather?.weatherType) > -1; } - override applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): void { + override applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; } } @@ -3761,11 +3760,11 @@ export class SuppressWeatherEffectAbAttr extends PreWeatherEffectAbAttr { this.affectsImmutable = !!affectsImmutable; } - override canApplyPreWeatherEffect(pokemon: Pokemon, passive: Boolean, simulated: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override canApplyPreWeatherEffect(pokemon: Pokemon, passive: Boolean, simulated: boolean, weather: Weather, cancelled: BooleanHolder, args: any[]): boolean { return this.affectsImmutable || weather.isImmutable(); } - override applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): void { + override applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; } } @@ -4042,7 +4041,7 @@ export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; if (!simulated) { globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), - Utils.toDmgValue(pokemon.getMaxHp() / (16 / this.healFactor)), i18next.t("abilityTriggers:postWeatherLapseHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); + toDmgValue(pokemon.getMaxHp() / (16 / this.healFactor)), i18next.t("abilityTriggers:postWeatherLapseHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); } } } @@ -4064,7 +4063,7 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr { if (!simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; globalScene.queueMessage(i18next.t("abilityTriggers:postWeatherLapseDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName })); - pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / (16 / this.damageFactor)), { result: HitResult.INDIRECT }); + pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / (16 / this.damageFactor)), { result: HitResult.INDIRECT }); } } } @@ -4132,7 +4131,7 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr { } override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return !Utils.isNullOrUndefined(pokemon.status) && this.effects.includes(pokemon.status.effect) && !pokemon.isFullHp(); + return !isNullOrUndefined(pokemon.status) && this.effects.includes(pokemon.status.effect) && !pokemon.isFullHp(); } /** @@ -4144,7 +4143,7 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr { if (!simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), - Utils.toDmgValue(pokemon.getMaxHp() / 8), i18next.t("abilityTriggers:poisonHeal", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName }), true)); + toDmgValue(pokemon.getMaxHp() / 8), i18next.t("abilityTriggers:poisonHeal", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName }), true)); } } } @@ -4168,7 +4167,7 @@ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr { } else { this.target = pokemon; } - return !Utils.isNullOrUndefined(this.target?.status); + return !isNullOrUndefined(this.target?.status); } override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { @@ -4224,7 +4223,7 @@ export class PostTurnLootAbAttr extends PostTurnAbAttr { return true; } - const randomIdx = Utils.randSeedInt(berriesEaten.length); + const randomIdx = randSeedInt(berriesEaten.length); const chosenBerryType = berriesEaten[randomIdx]; const chosenBerry = new BerryModifierType(chosenBerryType); berriesEaten.splice(randomIdx); // Remove berry from memory @@ -4312,7 +4311,7 @@ export class PostTurnHealAbAttr extends PostTurnAbAttr { if (!simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), - Utils.toDmgValue(pokemon.getMaxHp() / 16), i18next.t("abilityTriggers:postTurnHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); + toDmgValue(pokemon.getMaxHp() / 16), i18next.t("abilityTriggers:postTurnHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); } } } @@ -4356,7 +4355,7 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr { for (const opp of pokemon.getOpponents()) { if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus) { if (!simulated) { - opp.damageAndUpdate(Utils.toDmgValue(opp.getMaxHp() / 8), { result: HitResult.INDIRECT }); + opp.damageAndUpdate(toDmgValue(opp.getMaxHp() / 8), { result: HitResult.INDIRECT }); globalScene.queueMessage(i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) })); } } @@ -4375,7 +4374,7 @@ export class FetchBallAbAttr extends PostTurnAbAttr { } override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return !simulated && !Utils.isNullOrUndefined(globalScene.currentBattle.lastUsedPokeball) && !!pokemon.isPlayer; + return !simulated && !isNullOrUndefined(globalScene.currentBattle.lastUsedPokeball) && !!pokemon.isPlayer; } /** @@ -4407,7 +4406,7 @@ export class PostBiomeChangeWeatherChangeAbAttr extends PostBiomeChangeAbAttr { return ((globalScene.arena.weather?.isImmutable() ?? false) && globalScene.arena.canSetWeather(this.weatherType)); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { if (!simulated) { globalScene.arena.trySetWeather(this.weatherType, pokemon); } @@ -4427,7 +4426,7 @@ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr { return globalScene.arena.canSetTerrain(this.terrainType); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { if (!simulated) { globalScene.arena.trySetTerrain(this.terrainType, false, pokemon); } @@ -4562,8 +4561,8 @@ export class StatStageChangeMultiplierAbAttr extends AbAttr { this.multiplier = multiplier; } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { - (args[0] as Utils.NumberHolder).value *= this.multiplier; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + (args[0] as NumberHolder).value *= this.multiplier; } } @@ -4572,7 +4571,7 @@ export class StatStageChangeCopyAbAttr extends AbAttr { pokemon: Pokemon, passive: boolean, simulated: boolean, - cancelled: Utils.BooleanHolder, + cancelled: BooleanHolder, args: any[], ): void { if (!simulated) { @@ -4586,7 +4585,7 @@ export class BypassBurnDamageReductionAbAttr extends AbAttr { super(false); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; } } @@ -4605,21 +4604,21 @@ export class ReduceBurnDamageAbAttr extends AbAttr { * @param pokemon N/A * @param passive N/A * @param cancelled N/A - * @param args `[0]` {@linkcode Utils.NumberHolder} The damage value being modified + * @param args `[0]` {@linkcode NumberHolder} The damage value being modified */ - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { - (args[0] as Utils.NumberHolder).value = Utils.toDmgValue((args[0] as Utils.NumberHolder).value * this.multiplier); + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + (args[0] as NumberHolder).value = toDmgValue((args[0] as NumberHolder).value * this.multiplier); } } export class DoubleBerryEffectAbAttr extends AbAttr { - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { - (args[0] as Utils.NumberHolder).value *= 2; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + (args[0] as NumberHolder).value *= 2; } } export class PreventBerryUseAbAttr extends AbAttr { - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; } } @@ -4640,13 +4639,13 @@ export class HealFromBerryUseAbAttr extends AbAttr { this.healPercent = Math.max(Math.min(healPercent, 1), 0); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, ...args: [Utils.BooleanHolder, any[]]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, ...args: [BooleanHolder, any[]]): void { const { name: abilityName } = passive ? pokemon.getPassiveAbility() : pokemon.getAbility(); if (!simulated) { globalScene.unshiftPhase( new PokemonHealPhase( pokemon.getBattlerIndex(), - Utils.toDmgValue(pokemon.getMaxHp() * this.healPercent), + toDmgValue(pokemon.getMaxHp() * this.healPercent), i18next.t("abilityTriggers:healFromBerryUse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true ) @@ -4656,8 +4655,8 @@ export class HealFromBerryUseAbAttr extends AbAttr { } export class RunSuccessAbAttr extends AbAttr { - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { - (args[0] as Utils.NumberHolder).value = 256; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + (args[0] as NumberHolder).value = 256; } } @@ -4681,7 +4680,7 @@ export class CheckTrappedAbAttr extends AbAttr { pokemon: Pokemon, passive: boolean, simulated: boolean, - trapped: Utils.BooleanHolder, + trapped: BooleanHolder, otherPokemon: Pokemon, args: any[]): boolean { return true; @@ -4691,7 +4690,7 @@ export class CheckTrappedAbAttr extends AbAttr { pokemon: Pokemon, passive: boolean, simulated: boolean, - trapped: Utils.BooleanHolder, + trapped: BooleanHolder, otherPokemon: Pokemon, args: any[], ): void {} @@ -4704,7 +4703,7 @@ export class CheckTrappedAbAttr extends AbAttr { * @see {@linkcode applyCheckTrapped} */ export class ArenaTrapAbAttr extends CheckTrappedAbAttr { - override canApplyCheckTrapped(pokemon: Pokemon, passive: boolean, simulated: boolean, trapped: Utils.BooleanHolder, otherPokemon: Pokemon, args: any[]): boolean { + override canApplyCheckTrapped(pokemon: Pokemon, passive: boolean, simulated: boolean, trapped: BooleanHolder, otherPokemon: Pokemon, args: any[]): boolean { return this.arenaTrapCondition(pokemon, otherPokemon) && !(otherPokemon.getTypes(true).includes(PokemonType.GHOST) || (otherPokemon.getTypes(true).includes(PokemonType.STELLAR) && otherPokemon.getTypes().includes(PokemonType.GHOST))) && !otherPokemon.hasAbility(Abilities.RUN_AWAY); @@ -4718,11 +4717,11 @@ export class ArenaTrapAbAttr extends CheckTrappedAbAttr { * If the user has Arena Trap and the enemy is not grounded, it is not trapped. * @param pokemon The {@link Pokemon} with this {@link AbAttr} * @param passive N/A - * @param trapped {@link Utils.BooleanHolder} indicating whether the other Pokemon is trapped or not + * @param trapped {@link BooleanHolder} indicating whether the other Pokemon is trapped or not * @param otherPokemon The {@link Pokemon} that is affected by an Arena Trap ability * @param args N/A */ - override applyCheckTrapped(pokemon: Pokemon, passive: boolean, simulated: boolean, trapped: Utils.BooleanHolder, otherPokemon: Pokemon, args: any[]): void { + override applyCheckTrapped(pokemon: Pokemon, passive: boolean, simulated: boolean, trapped: BooleanHolder, otherPokemon: Pokemon, args: any[]): void { trapped.value = true; } @@ -4736,8 +4735,8 @@ export class MaxMultiHitAbAttr extends AbAttr { super(false); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { - (args[0] as Utils.NumberHolder).value = 0; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + (args[0] as NumberHolder).value = 0; } } @@ -4759,7 +4758,7 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr { override canApplyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const postBattleLoot = globalScene.currentBattle.postBattleLoot; if (!simulated && postBattleLoot.length && args[0]) { - this.randItem = Utils.randSeedItem(postBattleLoot); + this.randItem = randSeedItem(postBattleLoot); return globalScene.canTransferHeldItemModifier(this.randItem, pokemon, 1); } return false; @@ -4771,7 +4770,7 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr { override applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { const postBattleLoot = globalScene.currentBattle.postBattleLoot; if (!this.randItem) { - this.randItem = Utils.randSeedItem(postBattleLoot); + this.randItem = randSeedItem(postBattleLoot); } if (globalScene.tryTransferHeldItemModifier(this.randItem, pokemon, true, 1, true, undefined, false)) { @@ -4828,7 +4827,7 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr { override canApplyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): boolean { const diedToDirectDamage = move !== undefined && attacker !== undefined && move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}); - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); globalScene.getField(true).map(p => applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled, simulated)); if (!diedToDirectDamage || cancelled.value || attacker!.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { return false; @@ -4839,8 +4838,8 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr { override applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): void { if (!simulated) { - attacker!.damageAndUpdate(Utils.toDmgValue(attacker!.getMaxHp() * (1 / this.damageRatio)), { result: HitResult.INDIRECT }); - attacker!.turnData.damageTaken += Utils.toDmgValue(attacker!.getMaxHp() * (1 / this.damageRatio)); + attacker!.damageAndUpdate(toDmgValue(attacker!.getMaxHp() * (1 / this.damageRatio)), { result: HitResult.INDIRECT }); + attacker!.turnData.damageTaken += toDmgValue(attacker!.getMaxHp() * (1 / this.damageRatio)); } } @@ -4886,13 +4885,13 @@ export class RedirectMoveAbAttr extends AbAttr { if (!this.canRedirect(args[0] as Moves, args[2] as Pokemon)) { return false; } - const target = args[1] as Utils.NumberHolder; + const target = args[1] as NumberHolder; const newTarget = pokemon.getBattlerIndex(); return target.value !== newTarget; } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { - const target = args[1] as Utils.NumberHolder; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + const target = args[1] as NumberHolder; const newTarget = pokemon.getBattlerIndex(); target.value = newTarget; } @@ -4933,7 +4932,7 @@ export class ReduceStatusEffectDurationAbAttr extends AbAttr { } override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return args[1] instanceof Utils.NumberHolder && args[0] === this.statusEffect; + return args[1] instanceof NumberHolder && args[0] === this.statusEffect; } /** @@ -4942,7 +4941,7 @@ export class ReduceStatusEffectDurationAbAttr extends AbAttr { * - `[0]` - The {@linkcode StatusEffect} of the Pokemon * - `[1]` - The number of turns remaining until the status is healed */ - override apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, args: any[]): void { + override apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder, args: any[]): void { args[1].value -= 1; } } @@ -4966,7 +4965,7 @@ export class FlinchStatStageChangeAbAttr extends FlinchEffectAbAttr { this.stages = stages; } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { if (!simulated) { globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages)); } @@ -4976,7 +4975,7 @@ export class FlinchStatStageChangeAbAttr extends FlinchEffectAbAttr { export class IncreasePpAbAttr extends AbAttr { } export class ForceSwitchOutImmunityAbAttr extends AbAttr { - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; } } @@ -4991,7 +4990,7 @@ export class ReduceBerryUseThresholdAbAttr extends AbAttr { return args[0].value < hpRatio; } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { args[0].value *= 2; } } @@ -5009,8 +5008,8 @@ export class WeightMultiplierAbAttr extends AbAttr { this.multiplier = multiplier; } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { - (args[0] as Utils.NumberHolder).value *= this.multiplier; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + (args[0] as NumberHolder).value *= this.multiplier; } } @@ -5019,7 +5018,7 @@ export class SyncEncounterNatureAbAttr extends AbAttr { super(false); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { (args[0] as Pokemon).setNature(pokemon.getNature()); } } @@ -5037,7 +5036,7 @@ export class MoveAbilityBypassAbAttr extends AbAttr { return this.moveIgnoreFunc(pokemon, (args[0] as Move)); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; } } @@ -5057,7 +5056,7 @@ export class InfiltratorAbAttr extends AbAttr { } override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return args[0] instanceof Utils.BooleanHolder; + return args[0] instanceof BooleanHolder; } /** @@ -5066,7 +5065,7 @@ export class InfiltratorAbAttr extends AbAttr { * @param passive n/a * @param simulated n/a * @param cancelled n/a - * @param args `[0]` a {@linkcode Utils.BooleanHolder | BooleanHolder} containing the flag + * @param args `[0]` a {@linkcode BooleanHolder | BooleanHolder} containing the flag */ override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: null, args: any[]): void { const bypassed = args[0]; @@ -5107,7 +5106,7 @@ export class IgnoreTypeImmunityAbAttr extends AbAttr { return this.defenderType === (args[1] as PokemonType) && this.allowedMoveTypes.includes(args[0] as PokemonType); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; } } @@ -5130,7 +5129,7 @@ export class IgnoreTypeStatusEffectImmunityAbAttr extends AbAttr { return this.statusEffect.includes(args[0] as StatusEffect) && this.defenderType.includes(args[1] as PokemonType); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; } } @@ -5223,7 +5222,7 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { this.triggerMessageFunc = triggerMessageFunc; } - override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: BooleanHolder | null, args: any[]): boolean { return this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon); } @@ -5238,9 +5237,9 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { * @param _cancelled n/a * @param args Additional arguments. */ - override applyPreDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _cancelled: Utils.BooleanHolder, args: any[]): void { + override applyPreDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _cancelled: BooleanHolder, args: any[]): void { if (!simulated) { - (args[0] as Utils.NumberHolder).value = this.multiplier; + (args[0] as NumberHolder).value = this.multiplier; pokemon.removeTag(this.tagType); if (this.recoilDamageFunc) { pokemon.damageAndUpdate(this.recoilDamageFunc(pokemon), { result: HitResult.INDIRECT, ignoreSegments: true, ignoreFaintPhase: true }); @@ -5277,7 +5276,7 @@ export class BypassSpeedChanceAbAttr extends AbAttr { } override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const bypassSpeed = args[0] as Utils.BooleanHolder; + const bypassSpeed = args[0] as BooleanHolder; const turnCommand = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; const isCommandFight = turnCommand?.command === Command.FIGHT; const move = turnCommand?.move?.move ? allMoves[turnCommand.move.move] : null; @@ -5289,11 +5288,11 @@ export class BypassSpeedChanceAbAttr extends AbAttr { * bypass move order in their priority bracket when pokemon choose damaging move * @param {Pokemon} pokemon {@linkcode Pokemon} the Pokemon applying this ability * @param {boolean} passive N/A - * @param {Utils.BooleanHolder} cancelled N/A - * @param {any[]} args [0] {@linkcode Utils.BooleanHolder} set to true when the ability activated + * @param {BooleanHolder} cancelled N/A + * @param {any[]} args [0] {@linkcode BooleanHolder} set to true when the ability activated */ - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { - const bypassSpeed = args[0] as Utils.BooleanHolder; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + const bypassSpeed = args[0] as BooleanHolder; bypassSpeed.value = true; } @@ -5328,9 +5327,9 @@ export class PreventBypassSpeedChanceAbAttr extends AbAttr { * @argument {boolean} bypassSpeed - determines if a Pokemon is able to bypass speed at the moment * @argument {boolean} canCheckHeldItems - determines if a Pokemon has access to Quick Claw's effects or not */ - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { - const bypassSpeed = args[0] as Utils.BooleanHolder; - const canCheckHeldItems = args[1] as Utils.BooleanHolder; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { + const bypassSpeed = args[0] as BooleanHolder; + const canCheckHeldItems = args[1] as BooleanHolder; bypassSpeed.value = false; canCheckHeldItems.value = false; } @@ -5349,7 +5348,7 @@ export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr { return !pokemon.isTerastallized; } - override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, _args: any[]): void { + override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder, _args: any[]): void { const currentTerrain = globalScene.arena.getTerrainType(); const typeChange: PokemonType[] = this.determineTypeChange(pokemon, currentTerrain); if (typeChange.length !== 0) { @@ -5400,7 +5399,7 @@ export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr { * Checks if the Pokemon should change types if summoned into an active terrain */ override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { - this.apply(pokemon, passive, simulated, new Utils.BooleanHolder(false), []); + this.apply(pokemon, passive, simulated, new BooleanHolder(false), []); } override getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]) { @@ -5527,7 +5526,7 @@ class ForceSwitchOutHelper { if (switchOutTarget.hp > 0) { switchOutTarget.leaveField(false); globalScene.queueMessage(i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }), null, true, 500); - if (globalScene.currentBattle.double && !Utils.isNullOrUndefined(allyPokemon)) { + if (globalScene.currentBattle.double && !isNullOrUndefined(allyPokemon)) { globalScene.redirectPokemonMoves(switchOutTarget, allyPokemon); } } @@ -5556,7 +5555,7 @@ class ForceSwitchOutHelper { const player = switchOutTarget instanceof PlayerPokemon; if (player) { - const blockedByAbility = new Utils.BooleanHolder(false); + const blockedByAbility = new BooleanHolder(false); applyAbAttrs(ForceSwitchOutImmunityAbAttr, opponent, blockedByAbility); return !blockedByAbility.value; } @@ -5584,7 +5583,7 @@ class ForceSwitchOutHelper { * @returns The failure message, or `null` if no failure. */ public getFailedText(target: Pokemon): string | null { - const blockedByAbility = new Utils.BooleanHolder(false); + const blockedByAbility = new BooleanHolder(false); applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, blockedByAbility); return blockedByAbility.value ? i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) }) : null; } @@ -5603,7 +5602,7 @@ class ForceSwitchOutHelper { function calculateShellBellRecovery(pokemon: Pokemon): number { const shellBellModifier = pokemon.getHeldItems().find(m => m instanceof HitHealModifier); if (shellBellModifier) { - return Utils.toDmgValue(pokemon.turnData.totalDamageDealt / 8) * shellBellModifier.stackCount; + return toDmgValue(pokemon.turnData.totalDamageDealt / 8) * shellBellModifier.stackCount; } return 0; } @@ -5743,7 +5742,7 @@ function applyAbAttrsInternal( export function applyAbAttrs( attrType: Constructor, pokemon: Pokemon, - cancelled: Utils.BooleanHolder | null, + cancelled: BooleanHolder | null, simulated = false, ...args: any[] ): void { @@ -5778,7 +5777,7 @@ export function applyPreDefendAbAttrs( pokemon: Pokemon, attacker: Pokemon, move: Move | null, - cancelled: Utils.BooleanHolder | null, + cancelled: BooleanHolder | null, simulated = false, ...args: any[] ): void { @@ -5833,7 +5832,7 @@ export function applyStatMultiplierAbAttrs( attrType: Constructor, pokemon: Pokemon, stat: BattleStat, - statValue: Utils.NumberHolder, + statValue: NumberHolder, simulated = false, ...args: any[] ): void { @@ -5851,13 +5850,13 @@ export function applyStatMultiplierAbAttrs( * @param attrType - {@linkcode AllyStatMultiplierAbAttr} should always be AllyStatMultiplierAbAttr for the time being * @param pokemon - The {@linkcode Pokemon} with the ability * @param stat - The type of the checked {@linkcode Stat} - * @param statValue - {@linkcode Utils.NumberHolder} containing the value of the checked stat + * @param statValue - {@linkcode NumberHolder} containing the value of the checked stat * @param checkedPokemon - The {@linkcode Pokemon} with the checked stat * @param ignoreAbility - Whether or not the ability should be ignored by the pokemon or its move. * @param args - unused */ export function applyAllyStatMultiplierAbAttrs(attrType: Constructor, - pokemon: Pokemon, stat: BattleStat, statValue: Utils.NumberHolder, simulated: boolean = false, checkedPokemon: Pokemon, ignoreAbility: boolean, ...args: any[] + pokemon: Pokemon, stat: BattleStat, statValue: NumberHolder, simulated: boolean = false, checkedPokemon: Pokemon, ignoreAbility: boolean, ...args: any[] ): void { return applyAbAttrsInternal( attrType, @@ -5910,18 +5909,18 @@ export function applyPostDamageAbAttrs( * @param attrType {@linkcode FieldMultiplyStatAbAttr} should always be FieldMultiplyBattleStatAbAttr for the time being * @param pokemon {@linkcode Pokemon} the Pokemon applying this ability * @param stat {@linkcode Stat} the type of the checked stat - * @param statValue {@linkcode Utils.NumberHolder} the value of the checked stat + * @param statValue {@linkcode NumberHolder} the value of the checked stat * @param checkedPokemon {@linkcode Pokemon} the Pokemon with the checked stat - * @param hasApplied {@linkcode Utils.BooleanHolder} whether or not a FieldMultiplyBattleStatAbAttr has already affected this stat + * @param hasApplied {@linkcode BooleanHolder} whether or not a FieldMultiplyBattleStatAbAttr has already affected this stat * @param args unused */ export function applyFieldStatMultiplierAbAttrs( attrType: Constructor, pokemon: Pokemon, stat: Stat, - statValue: Utils.NumberHolder, + statValue: NumberHolder, checkedPokemon: Pokemon, - hasApplied: Utils.BooleanHolder, + hasApplied: BooleanHolder, simulated = false, ...args: any[] ): void { @@ -6055,7 +6054,7 @@ export function applyPreStatStageChangeAbAttrs, pokemon: Pokemon | null, stat: BattleStat, - cancelled: Utils.BooleanHolder, + cancelled: BooleanHolder, simulated = false, ...args: any[] ): void { @@ -6091,7 +6090,7 @@ export function applyPreSetStatusAbAttrs( attrType: Constructor, pokemon: Pokemon, effect: StatusEffect | undefined, - cancelled: Utils.BooleanHolder, + cancelled: BooleanHolder, simulated = false, ...args: any[] ): void { @@ -6109,7 +6108,7 @@ export function applyPreApplyBattlerTagAbAttrs( attrType: Constructor, pokemon: Pokemon, tag: BattlerTag, - cancelled: Utils.BooleanHolder, + cancelled: BooleanHolder, simulated = false, ...args: any[] ): void { @@ -6127,7 +6126,7 @@ export function applyPreWeatherEffectAbAttrs( attrType: Constructor, pokemon: Pokemon, weather: Weather | null, - cancelled: Utils.BooleanHolder, + cancelled: BooleanHolder, simulated = false, ...args: any[] ): void { @@ -6211,7 +6210,7 @@ export function applyPostTerrainChangeAbAttrs( export function applyCheckTrappedAbAttrs( attrType: Constructor, pokemon: Pokemon, - trapped: Utils.BooleanHolder, + trapped: BooleanHolder, otherPokemon: Pokemon, messages: string[], simulated = false, @@ -6539,7 +6538,7 @@ export function initAbilities() { .bypassFaint() .ignorable(), new Ability(Abilities.SHED_SKIN, 3) - .conditionalAttr(pokemon => !Utils.randSeedInt(3), PostTurnResetStatusAbAttr), + .conditionalAttr(pokemon => !randSeedInt(3), PostTurnResetStatusAbAttr), new Ability(Abilities.GUTS, 3) .attr(BypassBurnDamageReductionAbAttr) .conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(Abilities.COMATOSE), StatMultiplierAbAttr, Stat.ATK, 1.5), @@ -6661,7 +6660,7 @@ export function initAbilities() { .attr(ChangeMovePriorityAbAttr, (pokemon, move: Move) => true, -0.2), new Ability(Abilities.TECHNICIAN, 4) .attr(MovePowerBoostAbAttr, (user, target, move) => { - const power = new Utils.NumberHolder(move.power); + const power = new NumberHolder(move.power); applyMoveAttrs(VariablePowerAttr, user, target, move, power); return power.value <= 60; }, 1.5), @@ -6755,7 +6754,7 @@ export function initAbilities() { .attr(PostDefendMoveDisableAbAttr, 30) .bypassFaint(), new Ability(Abilities.HEALER, 5) - .conditionalAttr(pokemon => !Utils.isNullOrUndefined(pokemon.getAlly()) && Utils.randSeedInt(10) < 3, PostTurnResetStatusAbAttr, true), + .conditionalAttr(pokemon => !isNullOrUndefined(pokemon.getAlly()) && randSeedInt(10) < 3, PostTurnResetStatusAbAttr, true), new Ability(Abilities.FRIEND_GUARD, 5) .attr(AlliedFieldDamageReductionAbAttr, 0.75) .ignorable(), @@ -6809,7 +6808,7 @@ export function initAbilities() { new Ability(Abilities.ANALYTIC, 5) .attr(MovePowerBoostAbAttr, (user, target, move) => { const movePhase = globalScene.findPhase((phase) => phase instanceof MovePhase && phase.pokemon.id !== user?.id); - return Utils.isNullOrUndefined(movePhase); + return isNullOrUndefined(movePhase); }, 1.3), new Ability(Abilities.ILLUSION, 5) .uncopiable() @@ -7032,7 +7031,7 @@ export function initAbilities() { .attr(FormBlockDamageAbAttr, (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getMoveEffectiveness(user, move) > 0, 0, BattlerTagType.DISGUISE, (pokemon, abilityName) => i18next.t("abilityTriggers:disguiseAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }), - (pokemon) => Utils.toDmgValue(pokemon.getMaxHp() / 8)) + (pokemon) => toDmgValue(pokemon.getMaxHp() / 8)) .attr(PostBattleInitFormChangeAbAttr, () => 0) .uncopiable() .unreplaceable() diff --git a/src/data/balance/biomes.ts b/src/data/balance/biomes.ts index 3dff1722af6..c722291c66d 100644 --- a/src/data/balance/biomes.ts +++ b/src/data/balance/biomes.ts @@ -1,5 +1,5 @@ import { PokemonType } from "#enums/pokemon-type"; -import * as Utils from "#app/utils"; +import { randSeedInt, getEnumValues } from "#app/utils"; import type { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import i18next from "i18next"; @@ -7710,7 +7710,7 @@ export function initBiomes() { if (biome === Biome.END) { const biomeList = Object.keys(Biome).filter(key => !Number.isNaN(Number(key))); biomeList.pop(); // Removes Biome.END from the list - const randIndex = Utils.randSeedInt(biomeList.length, 1); // Will never be Biome.TOWN + const randIndex = randSeedInt(biomeList.length, 1); // Will never be Biome.TOWN biome = Biome[biomeList[randIndex]]; } const linkedBiomes: (Biome | [ Biome, number ])[] = Array.isArray(biomeLinks[biome]) @@ -7733,15 +7733,15 @@ export function initBiomes() { traverseBiome(Biome.TOWN, 0); biomeDepths[Biome.END] = [ Object.values(biomeDepths).map(d => d[0]).reduce((max: number, value: number) => Math.max(max, value), 0) + 1, 1 ]; - for (const biome of Utils.getEnumValues(Biome)) { + for (const biome of getEnumValues(Biome)) { biomePokemonPools[biome] = {}; biomeTrainerPools[biome] = {}; - for (const tier of Utils.getEnumValues(BiomePoolTier)) { + for (const tier of getEnumValues(BiomePoolTier)) { biomePokemonPools[biome][tier] = {}; biomeTrainerPools[biome][tier] = []; - for (const tod of Utils.getEnumValues(TimeOfDay)) { + for (const tod of getEnumValues(TimeOfDay)) { biomePokemonPools[biome][tier][tod] = []; } } diff --git a/src/data/balance/egg-moves.ts b/src/data/balance/egg-moves.ts index 19038ad824c..74f6a2c1afb 100644 --- a/src/data/balance/egg-moves.ts +++ b/src/data/balance/egg-moves.ts @@ -1,5 +1,5 @@ import { allMoves } from "#app/data/moves/move"; -import * as Utils from "#app/utils"; +import { getEnumKeys, getEnumValues } from "#app/utils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; @@ -587,8 +587,8 @@ export const speciesEggMoves = { function parseEggMoves(content: string): void { let output = ""; - const speciesNames = Utils.getEnumKeys(Species); - const speciesValues = Utils.getEnumValues(Species); + const speciesNames = getEnumKeys(Species); + const speciesValues = getEnumValues(Species); const lines = content.split(/\n/g); for (const line of lines) { diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index e49bd049cd6..17f71f3c3c9 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -3,7 +3,7 @@ import { Gender } from "#app/data/gender"; import { PokeballType } from "#enums/pokeball"; import type Pokemon from "#app/field/pokemon"; import { PokemonType } from "#enums/pokemon-type"; -import * as Utils from "#app/utils"; +import { randSeedInt } from "#app/utils"; import { WeatherType } from "#enums/weather-type"; import { Nature } from "#enums/nature"; import { Biome } from "#enums/biome"; @@ -333,7 +333,7 @@ class DunsparceEvolutionCondition extends SpeciesEvolutionCondition { super(p => { let ret = false; if (p.moveset.filter(m => m.moveId === Moves.HYPER_DRILL).length > 0) { - globalScene.executeWithSeedOffset(() => ret = !Utils.randSeedInt(4), p.id); + globalScene.executeWithSeedOffset(() => ret = !randSeedInt(4), p.id); } return ret; }); @@ -346,7 +346,7 @@ class TandemausEvolutionCondition extends SpeciesEvolutionCondition { constructor() { super(p => { let ret = false; - globalScene.executeWithSeedOffset(() => ret = !Utils.randSeedInt(4), p.id); + globalScene.executeWithSeedOffset(() => ret = !randSeedInt(4), p.id); return ret; }); } diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 546dbb4a3db..76e91485460 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -42,7 +42,7 @@ import { Species } from "#enums/species"; import { EFFECTIVE_STATS, getStatKey, Stat, type BattleStat, type EffectiveStat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import { WeatherType } from "#enums/weather-type"; -import * as Utils from "../utils"; +import { isNullOrUndefined } from "#app/utils"; export enum BattlerTagLapseType { FAINT, @@ -302,7 +302,7 @@ export class DisabledTag extends MoveRestrictionBattlerTag { super.onAdd(pokemon); const move = pokemon.getLastXMoves(-1).find(m => !m.virtual); - if (Utils.isNullOrUndefined(move) || move.move === Moves.STRUGGLE || move.move === Moves.NONE) { + if (isNullOrUndefined(move) || move.move === Moves.STRUGGLE || move.move === Moves.NONE) { return; } diff --git a/src/data/berry.ts b/src/data/berry.ts index 13820b1277b..8a58d337aa4 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -2,7 +2,7 @@ import { getPokemonNameWithAffix } from "../messages"; import type Pokemon from "../field/pokemon"; import { HitResult } from "../field/pokemon"; import { getStatusEffectHealText } from "./status-effect"; -import * as Utils from "../utils"; +import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils"; import { DoubleBerryEffectAbAttr, PostItemLostAbAttr, @@ -43,7 +43,7 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate { case BerryType.APICOT: case BerryType.SALAC: return (pokemon: Pokemon) => { - const threshold = new Utils.NumberHolder(0.25); + const threshold = new NumberHolder(0.25); // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth const stat: BattleStat = berryType - BerryType.ENIGMA; applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); @@ -51,19 +51,19 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate { }; case BerryType.LANSAT: return (pokemon: Pokemon) => { - const threshold = new Utils.NumberHolder(0.25); + const threshold = new NumberHolder(0.25); applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST); }; case BerryType.STARF: return (pokemon: Pokemon) => { - const threshold = new Utils.NumberHolder(0.25); + const threshold = new NumberHolder(0.25); applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); return pokemon.getHpRatio() < 0.25; }; case BerryType.LEPPA: return (pokemon: Pokemon) => { - const threshold = new Utils.NumberHolder(0.25); + const threshold = new NumberHolder(0.25); applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); return !!pokemon.getMoveset().find(m => !m.getPpRatio()); }; @@ -80,7 +80,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { if (pokemon.battleData) { pokemon.battleData.berriesEaten.push(berryType); } - const hpHealed = new Utils.NumberHolder(Utils.toDmgValue(pokemon.getMaxHp() / 4)); + const hpHealed = new NumberHolder(toDmgValue(pokemon.getMaxHp() / 4)); applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed); globalScene.unshiftPhase( new PokemonHealPhase( @@ -118,7 +118,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { } // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth const stat: BattleStat = berryType - BerryType.ENIGMA; - const statStages = new Utils.NumberHolder(1); + const statStages = new NumberHolder(1); applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages); globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [stat], statStages.value)); applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false); @@ -136,8 +136,8 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { if (pokemon.battleData) { pokemon.battleData.berriesEaten.push(berryType); } - const randStat = Utils.randSeedInt(Stat.SPD, Stat.ATK); - const stages = new Utils.NumberHolder(2); + const randStat = randSeedInt(Stat.SPD, Stat.ATK); + const stages = new NumberHolder(2); applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages); globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [randStat], stages.value)); applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false); diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 868fc7d2e60..51616c3f00f 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -1,4 +1,4 @@ -import * as Utils from "#app/utils"; +import { BooleanHolder, type NumberHolder, randSeedItem, deepCopy } from "#app/utils"; import i18next from "i18next"; import type { DexAttrProps, GameData } from "#app/system/game-data"; import { defaultStarterSpecies } from "#app/system/game-data"; @@ -283,30 +283,30 @@ export abstract class Challenge { /** * An apply function for STARTER_CHOICE challenges. Derived classes should alter this. * @param _pokemon {@link PokemonSpecies} The pokemon to check the validity of. - * @param _valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. + * @param _valid {@link BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. * @param _dexAttr {@link DexAttrProps} The dex attributes of the pokemon. * @returns {@link boolean} Whether this function did anything. */ - applyStarterChoice(_pokemon: PokemonSpecies, _valid: Utils.BooleanHolder, _dexAttr: DexAttrProps): boolean { + applyStarterChoice(_pokemon: PokemonSpecies, _valid: BooleanHolder, _dexAttr: DexAttrProps): boolean { return false; } /** * An apply function for STARTER_POINTS challenges. Derived classes should alter this. - * @param _points {@link Utils.NumberHolder} The amount of points you have available. + * @param _points {@link NumberHolder} The amount of points you have available. * @returns {@link boolean} Whether this function did anything. */ - applyStarterPoints(_points: Utils.NumberHolder): boolean { + applyStarterPoints(_points: NumberHolder): boolean { return false; } /** * An apply function for STARTER_COST challenges. Derived classes should alter this. * @param _species {@link Species} The pokemon to change the cost of. - * @param _cost {@link Utils.NumberHolder} The cost of the starter. + * @param _cost {@link NumberHolder} The cost of the starter. * @returns {@link boolean} Whether this function did anything. */ - applyStarterCost(_species: Species, _cost: Utils.NumberHolder): boolean { + applyStarterCost(_species: Species, _cost: NumberHolder): boolean { return false; } @@ -322,10 +322,10 @@ export abstract class Challenge { /** * An apply function for POKEMON_IN_BATTLE challenges. Derived classes should alter this. * @param _pokemon {@link Pokemon} The pokemon to check the validity of. - * @param _valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. + * @param _valid {@link BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. * @returns {@link boolean} Whether this function did anything. */ - applyPokemonInBattle(_pokemon: Pokemon, _valid: Utils.BooleanHolder): boolean { + applyPokemonInBattle(_pokemon: Pokemon, _valid: BooleanHolder): boolean { return false; } @@ -341,42 +341,42 @@ export abstract class Challenge { /** * An apply function for TYPE_EFFECTIVENESS challenges. Derived classes should alter this. - * @param _effectiveness {@linkcode Utils.NumberHolder} The current effectiveness of the move. + * @param _effectiveness {@linkcode NumberHolder} The current effectiveness of the move. * @returns Whether this function did anything. */ - applyTypeEffectiveness(_effectiveness: Utils.NumberHolder): boolean { + applyTypeEffectiveness(_effectiveness: NumberHolder): boolean { return false; } /** * An apply function for AI_LEVEL challenges. Derived classes should alter this. - * @param _level {@link Utils.NumberHolder} The generated level. + * @param _level {@link NumberHolder} The generated level. * @param _levelCap {@link Number} The current level cap. * @param _isTrainer {@link Boolean} Whether this is a trainer pokemon. * @param _isBoss {@link Boolean} Whether this is a non-trainer boss pokemon. * @returns {@link boolean} Whether this function did anything. */ - applyLevelChange(_level: Utils.NumberHolder, _levelCap: number, _isTrainer: boolean, _isBoss: boolean): boolean { + applyLevelChange(_level: NumberHolder, _levelCap: number, _isTrainer: boolean, _isBoss: boolean): boolean { return false; } /** * An apply function for AI_MOVE_SLOTS challenges. Derived classes should alter this. * @param pokemon {@link Pokemon} The pokemon that is being considered. - * @param moveSlots {@link Utils.NumberHolder} The amount of move slots. + * @param moveSlots {@link NumberHolder} The amount of move slots. * @returns {@link boolean} Whether this function did anything. */ - applyMoveSlot(_pokemon: Pokemon, _moveSlots: Utils.NumberHolder): boolean { + applyMoveSlot(_pokemon: Pokemon, _moveSlots: NumberHolder): boolean { return false; } /** * An apply function for PASSIVE_ACCESS challenges. Derived classes should alter this. * @param pokemon {@link Pokemon} The pokemon to change. - * @param hasPassive {@link Utils.BooleanHolder} Whether it should have its passive. + * @param hasPassive {@link BooleanHolder} Whether it should have its passive. * @returns {@link boolean} Whether this function did anything. */ - applyPassiveAccess(_pokemon: Pokemon, _hasPassive: Utils.BooleanHolder): boolean { + applyPassiveAccess(_pokemon: Pokemon, _hasPassive: BooleanHolder): boolean { return false; } @@ -393,15 +393,10 @@ export abstract class Challenge { * @param _pokemon {@link Pokemon} What pokemon would learn the move. * @param _moveSource {@link MoveSourceType} What source the pokemon would get the move from. * @param _move {@link Moves} The move in question. - * @param _level {@link Utils.NumberHolder} The level threshold for access. + * @param _level {@link NumberHolder} The level threshold for access. * @returns {@link boolean} Whether this function did anything. */ - applyMoveAccessLevel( - _pokemon: Pokemon, - _moveSource: MoveSourceType, - _move: Moves, - _level: Utils.NumberHolder, - ): boolean { + applyMoveAccessLevel(_pokemon: Pokemon, _moveSource: MoveSourceType, _move: Moves, _level: NumberHolder): boolean { return false; } @@ -410,10 +405,10 @@ export abstract class Challenge { * @param _pokemon {@link Pokemon} What pokemon would learn the move. * @param _moveSource {@link MoveSourceType} What source the pokemon would get the move from. * @param _move {@link Moves} The move in question. - * @param _weight {@link Utils.NumberHolder} The base weight of the move + * @param _weight {@link NumberHolder} The base weight of the move * @returns {@link boolean} Whether this function did anything. */ - applyMoveWeight(_pokemon: Pokemon, _moveSource: MoveSourceType, _move: Moves, _level: Utils.NumberHolder): boolean { + applyMoveWeight(_pokemon: Pokemon, _moveSource: MoveSourceType, _move: Moves, _level: NumberHolder): boolean { return false; } @@ -438,7 +433,7 @@ export class SingleGenerationChallenge extends Challenge { super(Challenges.SINGLE_GENERATION, 9); } - applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder): boolean { + applyStarterChoice(pokemon: PokemonSpecies, valid: BooleanHolder): boolean { if (pokemon.generation !== this.value) { valid.value = false; return true; @@ -446,7 +441,7 @@ export class SingleGenerationChallenge extends Challenge { return false; } - applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean { + applyPokemonInBattle(pokemon: Pokemon, valid: BooleanHolder): boolean { const baseGeneration = getPokemonSpecies(pokemon.species.speciesId).generation; const fusionGeneration = pokemon.isFusion() ? getPokemonSpecies(pokemon.fusionSpecies!.speciesId).generation : 0; if ( @@ -575,7 +570,7 @@ export class SingleGenerationChallenge extends Challenge { TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, - Utils.randSeedItem([TrainerType.HALA, TrainerType.MOLAYNE]), + randSeedItem([TrainerType.HALA, TrainerType.MOLAYNE]), TrainerType.MARNIE_ELITE, TrainerType.RIKA, ]; @@ -602,7 +597,7 @@ export class SingleGenerationChallenge extends Challenge { TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, - Utils.randSeedItem([TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE]), + randSeedItem([TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE]), TrainerType.LARRY_ELITE, ]; break; @@ -622,14 +617,14 @@ export class SingleGenerationChallenge extends Challenge { case ClassicFixedBossWaves.CHAMPION: trainerTypes = [ TrainerType.BLUE, - Utils.randSeedItem([TrainerType.RED, TrainerType.LANCE_CHAMPION]), - Utils.randSeedItem([TrainerType.STEVEN, TrainerType.WALLACE]), + randSeedItem([TrainerType.RED, TrainerType.LANCE_CHAMPION]), + randSeedItem([TrainerType.STEVEN, TrainerType.WALLACE]), TrainerType.CYNTHIA, - Utils.randSeedItem([TrainerType.ALDER, TrainerType.IRIS]), + randSeedItem([TrainerType.ALDER, TrainerType.IRIS]), TrainerType.DIANTHA, - Utils.randSeedItem([TrainerType.KUKUI, TrainerType.HAU]), - Utils.randSeedItem([TrainerType.LEON, TrainerType.MUSTARD]), - Utils.randSeedItem([TrainerType.GEETA, TrainerType.NEMONA]), + randSeedItem([TrainerType.KUKUI, TrainerType.HAU]), + randSeedItem([TrainerType.LEON, TrainerType.MUSTARD]), + randSeedItem([TrainerType.GEETA, TrainerType.NEMONA]), ]; break; } @@ -718,7 +713,7 @@ export class SingleTypeChallenge extends Challenge { super(Challenges.SINGLE_TYPE, 18); } - override applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps): boolean { + override applyStarterChoice(pokemon: PokemonSpecies, valid: BooleanHolder, dexAttr: DexAttrProps): boolean { const speciesForm = getPokemonSpeciesForm(pokemon.speciesId, dexAttr.formIndex); const types = [speciesForm.type1, speciesForm.type2]; if (!types.includes(this.value - 1)) { @@ -728,7 +723,7 @@ export class SingleTypeChallenge extends Challenge { return false; } - applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean { + applyPokemonInBattle(pokemon: Pokemon, valid: BooleanHolder): boolean { if ( pokemon.isPlayer() && !pokemon.isOfType(this.value - 1, false, false, true) && @@ -798,7 +793,7 @@ export class FreshStartChallenge extends Challenge { super(Challenges.FRESH_START, 1); } - applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder): boolean { + applyStarterChoice(pokemon: PokemonSpecies, valid: BooleanHolder): boolean { if (!defaultStarterSpecies.includes(pokemon.speciesId)) { valid.value = false; return true; @@ -806,7 +801,7 @@ export class FreshStartChallenge extends Challenge { return false; } - applyStarterCost(species: Species, cost: Utils.NumberHolder): boolean { + applyStarterCost(species: Species, cost: NumberHolder): boolean { if (defaultStarterSpecies.includes(species)) { cost.value = speciesStarterCosts[species]; return true; @@ -864,7 +859,7 @@ export class InverseBattleChallenge extends Challenge { return 0; } - applyTypeEffectiveness(effectiveness: Utils.NumberHolder): boolean { + applyTypeEffectiveness(effectiveness: NumberHolder): boolean { if (effectiveness.value < 1) { effectiveness.value = 2; return true; @@ -887,7 +882,7 @@ export class FlipStatChallenge extends Challenge { } override applyFlipStat(_pokemon: Pokemon, baseStats: number[]) { - const origStats = Utils.deepCopy(baseStats); + const origStats = deepCopy(baseStats); baseStats[0] = origStats[5]; baseStats[1] = origStats[4]; baseStats[2] = origStats[3]; @@ -923,7 +918,7 @@ export class LowerStarterMaxCostChallenge extends Challenge { return (DEFAULT_PARTY_MAX_COST - overrideValue).toString(); } - applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder): boolean { + applyStarterChoice(pokemon: PokemonSpecies, valid: BooleanHolder): boolean { if (speciesStarterCosts[pokemon.speciesId] > DEFAULT_PARTY_MAX_COST - this.value) { valid.value = false; return true; @@ -957,7 +952,7 @@ export class LowerStarterPointsChallenge extends Challenge { return (DEFAULT_PARTY_MAX_COST - overrideValue).toString(); } - applyStarterPoints(points: Utils.NumberHolder): boolean { + applyStarterPoints(points: NumberHolder): boolean { points.value -= this.value; return true; } @@ -974,34 +969,34 @@ export class LowerStarterPointsChallenge extends Challenge { * Apply all challenges that modify starter choice. * @param challengeType {@link ChallengeType} ChallengeType.STARTER_CHOICE * @param pokemon {@link PokemonSpecies} The pokemon to check the validity of. - * @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. + * @param valid {@link BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. * @param dexAttr {@link DexAttrProps} The dex attributes of the pokemon. * @returns True if any challenge was successfully applied. */ export function applyChallenges( challengeType: ChallengeType.STARTER_CHOICE, pokemon: PokemonSpecies, - valid: Utils.BooleanHolder, + valid: BooleanHolder, dexAttr: DexAttrProps, ): boolean; /** * Apply all challenges that modify available total starter points. * @param challengeType {@link ChallengeType} ChallengeType.STARTER_POINTS - * @param points {@link Utils.NumberHolder} The amount of points you have available. + * @param points {@link NumberHolder} The amount of points you have available. * @returns True if any challenge was successfully applied. */ -export function applyChallenges(challengeType: ChallengeType.STARTER_POINTS, points: Utils.NumberHolder): boolean; +export function applyChallenges(challengeType: ChallengeType.STARTER_POINTS, points: NumberHolder): boolean; /** * Apply all challenges that modify the cost of a starter. * @param challengeType {@link ChallengeType} ChallengeType.STARTER_COST * @param species {@link Species} The pokemon to change the cost of. - * @param points {@link Utils.NumberHolder} The cost of the pokemon. + * @param points {@link NumberHolder} The cost of the pokemon. * @returns True if any challenge was successfully applied. */ export function applyChallenges( challengeType: ChallengeType.STARTER_COST, species: Species, - cost: Utils.NumberHolder, + cost: NumberHolder, ): boolean; /** * Apply all challenges that modify a starter after selection. @@ -1014,13 +1009,13 @@ export function applyChallenges(challengeType: ChallengeType.STARTER_MODIFY, pok * Apply all challenges that what pokemon you can have in battle. * @param challengeType {@link ChallengeType} ChallengeType.POKEMON_IN_BATTLE * @param pokemon {@link Pokemon} The pokemon to check the validity of. - * @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. + * @param valid {@link BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. * @returns True if any challenge was successfully applied. */ export function applyChallenges( challengeType: ChallengeType.POKEMON_IN_BATTLE, pokemon: Pokemon, - valid: Utils.BooleanHolder, + valid: BooleanHolder, ): boolean; /** * Apply all challenges that modify what fixed battles there are. @@ -1037,17 +1032,14 @@ export function applyChallenges( /** * Apply all challenges that modify type effectiveness. * @param challengeType {@linkcode ChallengeType} ChallengeType.TYPE_EFFECTIVENESS - * @param effectiveness {@linkcode Utils.NumberHolder} The current effectiveness of the move. + * @param effectiveness {@linkcode NumberHolder} The current effectiveness of the move. * @returns True if any challenge was successfully applied. */ -export function applyChallenges( - challengeType: ChallengeType.TYPE_EFFECTIVENESS, - effectiveness: Utils.NumberHolder, -): boolean; +export function applyChallenges(challengeType: ChallengeType.TYPE_EFFECTIVENESS, effectiveness: NumberHolder): boolean; /** * Apply all challenges that modify what level AI are. * @param challengeType {@link ChallengeType} ChallengeType.AI_LEVEL - * @param level {@link Utils.NumberHolder} The generated level of the pokemon. + * @param level {@link NumberHolder} The generated level of the pokemon. * @param levelCap {@link Number} The maximum level cap for the current wave. * @param isTrainer {@link Boolean} Whether this is a trainer pokemon. * @param isBoss {@link Boolean} Whether this is a non-trainer boss pokemon. @@ -1055,7 +1047,7 @@ export function applyChallenges( */ export function applyChallenges( challengeType: ChallengeType.AI_LEVEL, - level: Utils.NumberHolder, + level: NumberHolder, levelCap: number, isTrainer: boolean, isBoss: boolean, @@ -1064,25 +1056,25 @@ export function applyChallenges( * Apply all challenges that modify how many move slots the AI has. * @param challengeType {@link ChallengeType} ChallengeType.AI_MOVE_SLOTS * @param pokemon {@link Pokemon} The pokemon being considered. - * @param moveSlots {@link Utils.NumberHolder} The amount of move slots. + * @param moveSlots {@link NumberHolder} The amount of move slots. * @returns True if any challenge was successfully applied. */ export function applyChallenges( challengeType: ChallengeType.AI_MOVE_SLOTS, pokemon: Pokemon, - moveSlots: Utils.NumberHolder, + moveSlots: NumberHolder, ): boolean; /** * Apply all challenges that modify whether a pokemon has its passive. * @param challengeType {@link ChallengeType} ChallengeType.PASSIVE_ACCESS * @param pokemon {@link Pokemon} The pokemon to modify. - * @param hasPassive {@link Utils.BooleanHolder} Whether it has its passive. + * @param hasPassive {@link BooleanHolder} Whether it has its passive. * @returns True if any challenge was successfully applied. */ export function applyChallenges( challengeType: ChallengeType.PASSIVE_ACCESS, pokemon: Pokemon, - hasPassive: Utils.BooleanHolder, + hasPassive: BooleanHolder, ): boolean; /** * Apply all challenges that modify the game modes settings. @@ -1096,7 +1088,7 @@ export function applyChallenges(challengeType: ChallengeType.GAME_MODE_MODIFY): * @param pokemon {@link Pokemon} What pokemon would learn the move. * @param moveSource {@link MoveSourceType} What source the pokemon would get the move from. * @param move {@link Moves} The move in question. - * @param level {@link Utils.NumberHolder} The level threshold for access. + * @param level {@link NumberHolder} The level threshold for access. * @returns True if any challenge was successfully applied. */ export function applyChallenges( @@ -1104,7 +1096,7 @@ export function applyChallenges( pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, - level: Utils.NumberHolder, + level: NumberHolder, ): boolean; /** * Apply all challenges that modify what weight a pokemon gives to move generation @@ -1112,7 +1104,7 @@ export function applyChallenges( * @param pokemon {@link Pokemon} What pokemon would learn the move. * @param moveSource {@link MoveSourceType} What source the pokemon would get the move from. * @param move {@link Moves} The move in question. - * @param weight {@link Utils.NumberHolder} The weight of the move. + * @param weight {@link NumberHolder} The weight of the move. * @returns True if any challenge was successfully applied. */ export function applyChallenges( @@ -1120,7 +1112,7 @@ export function applyChallenges( pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, - weight: Utils.NumberHolder, + weight: NumberHolder, ): boolean; export function applyChallenges(challengeType: ChallengeType.FLIP_STAT, pokemon: Pokemon, baseStats: number[]): boolean; @@ -1225,7 +1217,7 @@ export function initChallenges() { */ export function checkStarterValidForChallenge(species: PokemonSpecies, props: DexAttrProps, soft: boolean) { if (!soft) { - const isValidForChallenge = new Utils.BooleanHolder(true); + const isValidForChallenge = new BooleanHolder(true); applyChallenges(ChallengeType.STARTER_CHOICE, species, isValidForChallenge, props); return isValidForChallenge.value; } @@ -1263,7 +1255,7 @@ export function checkStarterValidForChallenge(species: PokemonSpecies, props: De * @returns `true` if the species is considered valid. */ function checkSpeciesValidForChallenge(species: PokemonSpecies, props: DexAttrProps, soft: boolean) { - const isValidForChallenge = new Utils.BooleanHolder(true); + const isValidForChallenge = new BooleanHolder(true); applyChallenges(ChallengeType.STARTER_CHOICE, species, isValidForChallenge, props); if (!soft || !pokemonFormChanges.hasOwnProperty(species.speciesId)) { return isValidForChallenge.value; @@ -1282,7 +1274,7 @@ function checkSpeciesValidForChallenge(species: PokemonSpecies, props: DexAttrPr return species.forms.some((f2, formIndex) => { if (f1.formKey === f2.formKey) { const formProps = { ...props, formIndex }; - const isFormValidForChallenge = new Utils.BooleanHolder(true); + const isFormValidForChallenge = new BooleanHolder(true); applyChallenges(ChallengeType.STARTER_CHOICE, species, isFormValidForChallenge, formProps); return isFormValidForChallenge.value; } diff --git a/src/data/daily-run.ts b/src/data/daily-run.ts index 22fb7db10ae..3438510d613 100644 --- a/src/data/daily-run.ts +++ b/src/data/daily-run.ts @@ -3,7 +3,7 @@ import type { Species } from "#enums/species"; import { globalScene } from "#app/global-scene"; import { PlayerPokemon } from "#app/field/pokemon"; import type { Starter } from "#app/ui/starter-select-ui-handler"; -import * as Utils from "#app/utils"; +import { randSeedGauss, randSeedInt, randSeedItem, getEnumValues } from "#app/utils"; import type { PokemonSpeciesForm } from "#app/data/pokemon-species"; import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; @@ -43,8 +43,8 @@ export function getDailyRunStarters(seed: string): Starter[] { } const starterCosts: number[] = []; - starterCosts.push(Math.min(Math.round(3.5 + Math.abs(Utils.randSeedGauss(1))), 8)); - starterCosts.push(Utils.randSeedInt(9 - starterCosts[0], 1)); + starterCosts.push(Math.min(Math.round(3.5 + Math.abs(randSeedGauss(1))), 8)); + starterCosts.push(randSeedInt(9 - starterCosts[0], 1)); starterCosts.push(10 - (starterCosts[0] + starterCosts[1])); for (let c = 0; c < starterCosts.length; c++) { @@ -52,7 +52,7 @@ export function getDailyRunStarters(seed: string): Starter[] { const costSpecies = Object.keys(speciesStarterCosts) .map(s => Number.parseInt(s) as Species) .filter(s => speciesStarterCosts[s] === cost); - const randPkmSpecies = getPokemonSpecies(Utils.randSeedItem(costSpecies)); + const randPkmSpecies = getPokemonSpecies(randSeedItem(costSpecies)); const starterSpecies = getPokemonSpecies( randPkmSpecies.getTrainerSpeciesForLevel(startingLevel, true, PartyMemberStrength.STRONGER), ); @@ -143,7 +143,7 @@ const dailyBiomeWeights: BiomeWeights = { }; export function getDailyStartingBiome(): Biome { - const biomes = Utils.getEnumValues(Biome).filter(b => b !== Biome.TOWN && b !== Biome.END); + const biomes = getEnumValues(Biome).filter(b => b !== Biome.TOWN && b !== Biome.END); let totalWeight = 0; const biomeThresholds: number[] = []; @@ -155,7 +155,7 @@ export function getDailyStartingBiome(): Biome { biomeThresholds.push(totalWeight); } - const randInt = Utils.randSeedInt(totalWeight); + const randInt = randSeedInt(totalWeight); for (let i = 0; i < biomes.length; i++) { if (randInt < biomeThresholds[i]) { @@ -164,5 +164,5 @@ export function getDailyStartingBiome(): Biome { } // Fallback in case something went wrong - return biomes[Utils.randSeedInt(biomes.length)]; + return biomes[randSeedInt(biomes.length)]; } diff --git a/src/data/egg.ts b/src/data/egg.ts index 0dabf8f1119..13ab0bec479 100644 --- a/src/data/egg.ts +++ b/src/data/egg.ts @@ -4,7 +4,7 @@ import type PokemonSpecies from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { VariantTier } from "#enums/variant-tier"; -import * as Utils from "#app/utils"; +import { randInt, randomString, randSeedInt, getIvsFromId } from "#app/utils"; import Overrides from "#app/overrides"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import type { PlayerPokemon } from "#app/field/pokemon"; @@ -171,7 +171,7 @@ export class Egg { this.checkForPityTierOverrides(); } - this._id = eggOptions?.id ?? Utils.randInt(EGG_SEED, EGG_SEED * this._tier); + this._id = eggOptions?.id ?? randInt(EGG_SEED, EGG_SEED * this._tier); this._sourceType = eggOptions?.sourceType ?? undefined; this._hatchWaves = eggOptions?.hatchWaves ?? this.getEggTierDefaultHatchWaves(); @@ -203,7 +203,7 @@ export class Egg { } }; - const seedOverride = Utils.randomString(24); + const seedOverride = randomString(24); globalScene.executeWithSeedOffset( () => { generateEggProperties(eggOptions); @@ -248,18 +248,15 @@ export class Egg { let pokemonSpecies = getPokemonSpecies(this._species); // Special condition to have Phione eggs also have a chance of generating Manaphy if (this._species === Species.PHIONE && this._sourceType === EggSourceType.SAME_SPECIES_EGG) { - pokemonSpecies = getPokemonSpecies( - Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) ? Species.PHIONE : Species.MANAPHY, - ); + pokemonSpecies = getPokemonSpecies(randSeedInt(MANAPHY_EGG_MANAPHY_RATE) ? Species.PHIONE : Species.MANAPHY); } // Sets the hidden ability if a hidden ability exists and // the override is set or the egg hits the chance let abilityIndex: number | undefined = undefined; const sameSpeciesEggHACheck = - this._sourceType === EggSourceType.SAME_SPECIES_EGG && !Utils.randSeedInt(SAME_SPECIES_EGG_HA_RATE); - const gachaEggHACheck = - !(this._sourceType === EggSourceType.SAME_SPECIES_EGG) && !Utils.randSeedInt(GACHA_EGG_HA_RATE); + this._sourceType === EggSourceType.SAME_SPECIES_EGG && !randSeedInt(SAME_SPECIES_EGG_HA_RATE); + const gachaEggHACheck = !(this._sourceType === EggSourceType.SAME_SPECIES_EGG) && !randSeedInt(GACHA_EGG_HA_RATE); if (pokemonSpecies.abilityHidden && (this._overrideHiddenAbility || sameSpeciesEggHACheck || gachaEggHACheck)) { abilityIndex = 2; } @@ -269,7 +266,7 @@ export class Egg { ret.shiny = this._isShiny; ret.variant = this._variantTier; - const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967295)); + const secondaryIvs = getIvsFromId(randSeedInt(4294967295)); for (let s = 0; s < ret.ivs.length; s++) { ret.ivs[s] = Math.max(ret.ivs[s], secondaryIvs[s]); @@ -370,7 +367,7 @@ export class Egg { } const tierMultiplier = this.isManaphyEgg() ? 2 : Math.pow(2, 3 - this.tier); - return Utils.randSeedInt(baseChance * tierMultiplier) ? Utils.randSeedInt(3) : 3; + return randSeedInt(baseChance * tierMultiplier) ? randSeedInt(3) : 3; } private getEggTierDefaultHatchWaves(eggTier?: EggTier): number { @@ -392,7 +389,7 @@ export class Egg { private rollEggTier(): EggTier { const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0; - const tierValue = Utils.randInt(256); + const tierValue = randInt(256); return tierValue >= GACHA_DEFAULT_COMMON_EGG_THRESHOLD + tierValueOffset ? EggTier.COMMON : tierValue >= GACHA_DEFAULT_RARE_EGG_THRESHOLD + tierValueOffset @@ -417,11 +414,11 @@ export class Egg { * when Utils.randSeedInt(8) = 1, and by making the generatePlayerPokemon() species * check pass when Utils.randSeedInt(8) = 0, we can tell them apart during tests. */ - const rand = Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) !== 1; + const rand = randSeedInt(MANAPHY_EGG_MANAPHY_RATE) !== 1; return rand ? Species.PHIONE : Species.MANAPHY; } if (this.tier === EggTier.LEGENDARY && this._sourceType === EggSourceType.GACHA_LEGENDARY) { - if (!Utils.randSeedInt(2)) { + if (!randSeedInt(2)) { return getLegendaryGachaSpeciesForTimestamp(this.timestamp); } } @@ -501,7 +498,7 @@ export class Egg { let species: Species; - const rand = Utils.randSeedInt(totalWeight); + const rand = randSeedInt(totalWeight); for (let s = 0; s < speciesWeights.length; s++) { if (rand < speciesWeights[s]) { species = speciesPool[s]; @@ -539,7 +536,7 @@ export class Egg { break; } - return !Utils.randSeedInt(shinyChance); + return !randSeedInt(shinyChance); } // Uses the same logic as pokemon.generateVariant(). I would like to only have this logic in one @@ -550,7 +547,7 @@ export class Egg { return VariantTier.STANDARD; } - const rand = Utils.randSeedInt(10); + const rand = randSeedInt(10); if (rand >= SHINY_VARIANT_CHANCE) { return VariantTier.STANDARD; // 6/10 } diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 1af4be4fdf0..a0f68dcd5cb 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -29,9 +29,7 @@ import { } from "../status-effect"; import { getTypeDamageMultiplier } from "../type"; import { PokemonType } from "#enums/pokemon-type"; -import type { Constructor } from "#app/utils"; -import { NumberHolder } from "#app/utils"; -import * as Utils from "../../utils"; +import { BooleanHolder, NumberHolder, isNullOrUndefined, toDmgValue, randSeedItem, randSeedInt, getEnumValues, toReadableString, type Constructor } from "#app/utils"; import { WeatherType } from "#enums/weather-type"; import type { ArenaTrapTag } from "../arena-tag"; import { ArenaTagSide, WeakenMoveTypeTag } from "../arena-tag"; @@ -352,7 +350,7 @@ export default class Move implements Localizable { return false; } - const bypassed = new Utils.BooleanHolder(false); + const bypassed = new BooleanHolder(false); // TODO: Allow this to be simulated applyAbAttrs(InfiltratorAbAttr, user, null, false, bypassed); @@ -651,7 +649,7 @@ export default class Move implements Localizable { break; case MoveFlags.IGNORE_ABILITIES: if (user.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) { - const abilityEffectsIgnored = new Utils.BooleanHolder(false); + const abilityEffectsIgnored = new BooleanHolder(false); applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, false, this); if (abilityEffectsIgnored.value) { return true; @@ -755,7 +753,7 @@ export default class Move implements Localizable { * @returns The calculated accuracy of the move. */ calculateBattleAccuracy(user: Pokemon, target: Pokemon, simulated: boolean = false) { - const moveAccuracy = new Utils.NumberHolder(this.accuracy); + const moveAccuracy = new NumberHolder(this.accuracy); applyMoveAttrs(VariableAccuracyAttr, user, target, this, moveAccuracy); applyPreDefendAbAttrs(WonderSkinAbAttr, target, user, this, { value: false }, simulated, moveAccuracy); @@ -797,8 +795,8 @@ export default class Move implements Localizable { return -1; } - const power = new Utils.NumberHolder(this.power); - const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1); + const power = new NumberHolder(this.power); + const typeChangeMovePowerMultiplier = new NumberHolder(1); applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, null, typeChangeMovePowerMultiplier); @@ -809,7 +807,7 @@ export default class Move implements Localizable { applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, target, this, simulated, power); const ally = source.getAlly(); - if (!Utils.isNullOrUndefined(ally)) { + if (!isNullOrUndefined(ally)) { applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, ally, target, this, simulated, power); } @@ -850,7 +848,7 @@ export default class Move implements Localizable { } getPriority(user: Pokemon, simulated: boolean = true) { - const priority = new Utils.NumberHolder(this.priority); + const priority = new NumberHolder(this.priority); applyMoveAttrs(IncrementMovePriorityAttr, user, null, this, priority); applyAbAttrs(ChangeMovePriorityAbAttr, user, null, simulated, this, priority); @@ -932,7 +930,7 @@ export default class Move implements Localizable { // ...and cannot enhance Pollen Puff when targeting an ally. const ally = user.getAlly(); - const exceptPollenPuffAlly: boolean = this.id === Moves.POLLEN_PUFF && !Utils.isNullOrUndefined(ally) && targets.includes(ally.getBattlerIndex()) + const exceptPollenPuffAlly: boolean = this.id === Moves.POLLEN_PUFF && !isNullOrUndefined(ally) && targets.includes(ally.getBattlerIndex()) return (!restrictSpread || !isMultiTarget) && !this.isChargingMove() @@ -971,7 +969,7 @@ export class AttackMove extends Move { const effectiveness = target.getAttackTypeEffectiveness(this.type, user, undefined, undefined, this); attackScore = Math.pow(effectiveness - 1, 2) * (effectiveness < 1 ? -2 : 2); const [ thisStat, offStat ]: EffectiveStat[] = this.category === MoveCategory.PHYSICAL ? [ Stat.ATK, Stat.SPATK ] : [ Stat.SPATK, Stat.ATK ]; - const statHolder = new Utils.NumberHolder(user.getEffectiveStat(thisStat, target)); + const statHolder = new NumberHolder(user.getEffectiveStat(thisStat, target)); const offStatValue = user.getEffectiveStat(offStat, target); applyMoveAttrs(VariableAtkAttr, user, target, move, statHolder); const statRatio = offStatValue / statHolder.value; @@ -981,7 +979,7 @@ export class AttackMove extends Move { attackScore *= 1.5; } - const power = new Utils.NumberHolder(this.calculateEffectivePower()); + const power = new NumberHolder(this.calculateEffectivePower()); applyMoveAttrs(VariablePowerAttr, user, target, move, power); attackScore += Math.floor(power.value / 5); @@ -1252,7 +1250,7 @@ export class MoveEffectAttr extends MoveAttr { * @returns Move effect chance value. */ getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): number { - const moveChance = new Utils.NumberHolder(this.effectChanceOverride ?? move.chance); + const moveChance = new NumberHolder(this.effectChanceOverride ?? move.chance); applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, !showAbility, moveChance, move); @@ -1415,7 +1413,7 @@ export class RespectAttackTypeImmunityAttr extends MoveAttr { } export class IgnoreOpponentStatStagesAttr extends MoveAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.BooleanHolder).value = true; + (args[0] as BooleanHolder).value = true; return true; } @@ -1423,7 +1421,7 @@ export class IgnoreOpponentStatStagesAttr extends MoveAttr { export class HighCritAttr extends MoveAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.NumberHolder).value++; + (args[0] as NumberHolder).value++; return true; } @@ -1435,7 +1433,7 @@ export class HighCritAttr extends MoveAttr { export class CritOnlyAttr extends MoveAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.BooleanHolder).value = true; + (args[0] as BooleanHolder).value = true; return true; } @@ -1455,7 +1453,7 @@ export class FixedDamageAttr extends MoveAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.NumberHolder).value = this.getDamage(user, target, move); + (args[0] as NumberHolder).value = this.getDamage(user, target, move); return true; } @@ -1471,7 +1469,7 @@ export class UserHpDamageAttr extends FixedDamageAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.NumberHolder).value = user.hp; + (args[0] as NumberHolder).value = user.hp; return true; } @@ -1492,7 +1490,7 @@ export class TargetHalfHpDamageAttr extends FixedDamageAttr { const lensCount = user.getHeldItems().find(i => i instanceof PokemonMultiHitModifier)?.getStackCount() ?? 0; if (lensCount <= 0) { // no multi lenses; we can just halve the target's hp and call it a day - (args[0] as Utils.NumberHolder).value = Utils.toDmgValue(target.hp / 2); + (args[0] as NumberHolder).value = toDmgValue(target.hp / 2); return true; } @@ -1503,11 +1501,11 @@ export class TargetHalfHpDamageAttr extends FixedDamageAttr { this.initialHp = target.hp; default: // multi lens added hit; use initialHp tracker to ensure correct damage - (args[0] as Utils.NumberHolder).value = Utils.toDmgValue(this.initialHp / 2); + (args[0] as NumberHolder).value = toDmgValue(this.initialHp / 2); return true; case lensCount + 1: // parental bond added hit; calc damage as normal - (args[0] as Utils.NumberHolder).value = Utils.toDmgValue(target.hp / 2); + (args[0] as NumberHolder).value = toDmgValue(target.hp / 2); return true; } } @@ -1523,7 +1521,7 @@ export class MatchHpAttr extends FixedDamageAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.NumberHolder).value = target.hp - user.hp; + (args[0] as NumberHolder).value = target.hp - user.hp; return true; } @@ -1553,7 +1551,7 @@ export class CounterDamageAttr extends FixedDamageAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const damage = user.turnData.attacksReceived.filter(ar => this.moveFilter(allMoves[ar.move])).reduce((total: number, ar: AttackMoveResult) => total + ar.damage, 0); - (args[0] as Utils.NumberHolder).value = Utils.toDmgValue(damage * this.multiplier); + (args[0] as NumberHolder).value = toDmgValue(damage * this.multiplier); return true; } @@ -1579,13 +1577,13 @@ export class RandomLevelDamageAttr extends FixedDamageAttr { } getDamage(user: Pokemon, target: Pokemon, move: Move): number { - return Utils.toDmgValue(user.level * (user.randSeedIntRange(50, 150) * 0.01)); + return toDmgValue(user.level * (user.randSeedIntRange(50, 150) * 0.01)); } } export class ModifiedDamageAttr extends MoveAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const initialDamage = args[0] as Utils.NumberHolder; + const initialDamage = args[0] as NumberHolder; initialDamage.value = this.getModifiedDamage(user, target, move, initialDamage.value); return true; @@ -1638,7 +1636,7 @@ export class RecoilAttr extends MoveEffectAttr { return false; } - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); if (!this.unblockable) { applyAbAttrs(BlockRecoilDamageAttr, user, cancelled); applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); @@ -1655,7 +1653,7 @@ export class RecoilAttr extends MoveEffectAttr { const damageValue = (!this.useHp ? user.turnData.totalDamageDealt : user.getMaxHp()) * this.damageRatio; const minValue = user.turnData.totalDamageDealt ? 1 : 0; - const recoilDamage = Utils.toDmgValue(damageValue, minValue); + const recoilDamage = toDmgValue(damageValue, minValue); if (!recoilDamage) { return false; } @@ -1772,11 +1770,11 @@ export class HalfSacrificialAttr extends MoveEffectAttr { return false; } - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); // Check to see if the Pokemon has an ability that blocks non-direct damage applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); if (!cancelled.value) { - user.damageAndUpdate(Utils.toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT, ignoreSegments: true }); + user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT, ignoreSegments: true }); globalScene.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", { pokemonName: getPokemonNameWithAffix(user) })); // Queue recoil message } return true; @@ -1882,7 +1880,7 @@ export class HealAttr extends MoveEffectAttr { */ addHealPhase(target: Pokemon, healRatio: number) { globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), - Utils.toDmgValue(target.getMaxHp() * healRatio), i18next.t("moveTriggers:healHp", { pokemonName: getPokemonNameWithAffix(target) }), true, !this.showAnim)); + toDmgValue(target.getMaxHp() * healRatio), i18next.t("moveTriggers:healHp", { pokemonName: getPokemonNameWithAffix(target) }), true, !this.showAnim)); } getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { @@ -1965,9 +1963,9 @@ export class FlameBurstAttr extends MoveEffectAttr { */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const targetAlly = target.getAlly(); - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); - if (!Utils.isNullOrUndefined(targetAlly)) { + if (!isNullOrUndefined(targetAlly)) { applyAbAttrs(BlockNonDirectDamageAbAttr, targetAlly, cancelled); } @@ -1980,7 +1978,7 @@ export class FlameBurstAttr extends MoveEffectAttr { } getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { - return !Utils.isNullOrUndefined(target.getAlly()) ? -5 : 0; + return !isNullOrUndefined(target.getAlly()) ? -5 : 0; } } @@ -2048,11 +2046,11 @@ export class IgnoreWeatherTypeDebuffAttr extends MoveAttr { * @param user {@linkcode Pokemon} that used the move * @param target N/A * @param move {@linkcode Move} with this attribute - * @param args [0] {@linkcode Utils.NumberHolder} for arenaAttackTypeMultiplier + * @param args [0] {@linkcode NumberHolder} for arenaAttackTypeMultiplier * @returns true if the function succeeds */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const weatherModifier = args[0] as Utils.NumberHolder; + const weatherModifier = args[0] as NumberHolder; //If the type-based attack power modifier due to weather (e.g. Water moves in Sun) is below 1, set it to 1 if (globalScene.arena.weather?.weatherType === this.weather) { weatherModifier.value = Math.max(weatherModifier.value, 1); @@ -2203,7 +2201,7 @@ export class HitHealAttr extends MoveEffectAttr { message = i18next.t("battle:drainMessage", { pokemonName: getPokemonNameWithAffix(target) }); } else { // Default healing formula used by draining moves like Absorb, Draining Kiss, Bitter Blade, etc. - healAmount = Utils.toDmgValue(user.turnData.singleHitDamageDealt * this.healRatio); + healAmount = toDmgValue(user.turnData.singleHitDamageDealt * this.healRatio); message = i18next.t("battle:regainHealth", { pokemonName: getPokemonNameWithAffix(user) }); } if (reverseDrain) { @@ -2261,7 +2259,7 @@ export class IncrementMovePriorityAttr extends MoveAttr { * @param user {@linkcode Pokemon} using this move * @param target {@linkcode Pokemon} target of this move * @param move {@linkcode Move} being used - * @param args [0] {@linkcode Utils.NumberHolder} for move priority. + * @param args [0] {@linkcode NumberHolder} for move priority. * @returns true if function succeeds */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { @@ -2269,7 +2267,7 @@ export class IncrementMovePriorityAttr extends MoveAttr { return false; } - (args[0] as Utils.NumberHolder).value += this.increaseAmount; + (args[0] as NumberHolder).value += this.increaseAmount; return true; } } @@ -2307,15 +2305,15 @@ export class MultiHitAttr extends MoveAttr { * @param user {@linkcode Pokemon} that used the attack * @param target {@linkcode Pokemon} targeted by the attack * @param move {@linkcode Move} being used - * @param args [0] {@linkcode Utils.NumberHolder} storing the hit count of the attack + * @param args [0] {@linkcode NumberHolder} storing the hit count of the attack * @returns True */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const hitType = new Utils.NumberHolder(this.intrinsicMultiHitType); + const hitType = new NumberHolder(this.intrinsicMultiHitType); applyMoveAttrs(ChangeMultiHitTypeAttr, user, target, move, hitType); this.multiHitType = hitType.value; - (args[0] as Utils.NumberHolder).value = this.getHitCount(user, target); + (args[0] as NumberHolder).value = this.getHitCount(user, target); return true; } @@ -2336,7 +2334,7 @@ export class MultiHitAttr extends MoveAttr { case MultiHitType._2_TO_5: { const rand = user.randSeedInt(20); - const hitValue = new Utils.NumberHolder(rand); + const hitValue = new NumberHolder(rand); applyAbAttrs(MaxMultiHitAbAttr, user, null, false, hitValue); if (hitValue.value >= 13) { return 2; @@ -2414,7 +2412,7 @@ export class ChangeMultiHitTypeAttr extends MoveAttr { export class WaterShurikenMultiHitTypeAttr extends ChangeMultiHitTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (user.species.speciesId === Species.GRENINJA && user.hasAbility(Abilities.BATTLE_BOND) && user.formIndex === 2) { - (args[0] as Utils.NumberHolder).value = MultiHitType._3; + (args[0] as NumberHolder).value = MultiHitType._3; return true; } return false; @@ -2480,7 +2478,7 @@ export class MultiStatusEffectAttr extends StatusEffectAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - this.effect = Utils.randSeedItem(this.effects); + this.effect = randSeedItem(this.effects); const result = super.apply(user, target, move, args); return result; } @@ -2612,7 +2610,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { return false; } - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); // Check for abilities that block item theft if (cancelled.value === true) { @@ -2686,7 +2684,7 @@ export class EatBerryAttr extends MoveEffectAttr { return false; } this.chosenBerry = heldBerries[user.randSeedInt(heldBerries.length)]; - const preserve = new Utils.BooleanHolder(false); + const preserve = new BooleanHolder(false); globalScene.applyModifiers(PreserveBerryModifier, target.isPlayer(), target, preserve); // check for berry pouch preservation if (!preserve.value) { this.reduceBerryModifier(target); @@ -2709,7 +2707,7 @@ export class EatBerryAttr extends MoveEffectAttr { eatBerry(consumer: Pokemon, berryOwner?: Pokemon) { getBerryEffectFunc(this.chosenBerry!.berryType)(consumer, berryOwner); // consumer eats the berry - applyAbAttrs(HealFromBerryUseAbAttr, consumer, new Utils.BooleanHolder(false)); + applyAbAttrs(HealFromBerryUseAbAttr, consumer, new BooleanHolder(false)); } } @@ -2733,7 +2731,7 @@ export class StealEatBerryAttr extends EatBerryAttr { if (move.hitsSubstitute(user, target)) { return false; } - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); // check for abilities that block item theft if (cancelled.value === true) { return false; @@ -2846,11 +2844,11 @@ export class BypassBurnDamageReductionAttr extends MoveAttr { * @param user N/A * @param target N/A * @param move {@linkcode Move} with this attribute - * @param args [0] {@linkcode Utils.BooleanHolder} for burnDamageReductionCancelled + * @param args [0] {@linkcode BooleanHolder} for burnDamageReductionCancelled * @returns true if the function succeeds */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.BooleanHolder).value = true; + (args[0] as BooleanHolder).value = true; return true; } @@ -2931,14 +2929,14 @@ export class OneHitKOAttr extends MoveAttr { return false; } - (args[0] as Utils.BooleanHolder).value = true; + (args[0] as BooleanHolder).value = true; return true; } getCondition(): MoveConditionFunc { return (user, target, move) => { - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockOneHitKOAbAttr, target, cancelled); return !cancelled.value && user.level >= target.level; }; @@ -2965,12 +2963,12 @@ export class InstantChargeAttr extends MoveAttr { * @param target n/a * @param move the {@linkcode Move} associated with this attribute * @param args - * - `[0]` a {@linkcode Utils.BooleanHolder | BooleanHolder} for the "instant charge" flag + * - `[0]` a {@linkcode BooleanHolder | BooleanHolder} for the "instant charge" flag * @returns `true` if the instant charge condition is met; `false` otherwise. */ override apply(user: Pokemon, target: Pokemon | null, move: Move, args: any[]): boolean { const instantCharge = args[0]; - if (!(instantCharge instanceof Utils.BooleanHolder)) { + if (!(instantCharge instanceof BooleanHolder)) { return false; } @@ -2992,7 +2990,7 @@ export class WeatherInstantChargeAttr extends InstantChargeAttr { super((user, move) => { const currentWeather = globalScene.arena.weather; - if (Utils.isNullOrUndefined(currentWeather?.weatherType)) { + if (isNullOrUndefined(currentWeather?.weatherType)) { return false; } else { return !currentWeather?.isEffectSuppressed() @@ -3035,7 +3033,7 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr { return true; } - const overridden = args[0] as Utils.BooleanHolder; + const overridden = args[0] as BooleanHolder; const virtual = args[1] as boolean; if (!virtual) { @@ -3069,7 +3067,7 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr { * @param target n/a * @param move the {@linkcode Move} being used * @param args - * - [0] a {@linkcode Utils.BooleanHolder} indicating whether the move's base + * - [0] a {@linkcode BooleanHolder} indicating whether the move's base * effects should be overridden this turn. * @returns `true` if base move effects were overridden; `false` otherwise */ @@ -3080,7 +3078,7 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr { return false; } - const overridden = args[0] as Utils.BooleanHolder; + const overridden = args[0] as BooleanHolder; const allyMovePhase = globalScene.findPhase((phase) => phase instanceof MovePhase && phase.pokemon.isPlayer() === user.isPlayer()); if (allyMovePhase) { @@ -3451,7 +3449,7 @@ export class CutHpStatStageBoostAttr extends StatStageChangeAttr { this.messageCallback = messageCallback; } override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - user.damageAndUpdate(Utils.toDmgValue(user.getMaxHp() / this.cutRatio), { result: HitResult.INDIRECT }); + user.damageAndUpdate(toDmgValue(user.getMaxHp() / this.cutRatio), { result: HitResult.INDIRECT }); user.updateInfo(); const ret = super.apply(user, target, move, args); if (this.messageCallback) { @@ -3663,7 +3661,7 @@ export class LessPPMorePowerAttr extends VariablePowerAttr { * @param user {@linkcode Pokemon} using this move * @param target {@linkcode Pokemon} target of this move * @param move {@linkcode Move} being used - * @param args [0] {@linkcode Utils.NumberHolder} of power + * @param args [0] {@linkcode NumberHolder} of power * @returns true if the function succeeds */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { @@ -3676,7 +3674,7 @@ export class LessPPMorePowerAttr extends VariablePowerAttr { ppRemains = 0; } - const power = args[0] as Utils.NumberHolder; + const power = args[0] as NumberHolder; switch (ppRemains) { case 0: @@ -3709,7 +3707,7 @@ export class MovePowerMultiplierAttr extends VariablePowerAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const power = args[0] as Utils.NumberHolder; + const power = args[0] as NumberHolder; power.value *= this.powerMultiplierFunc(user, target, move); return true; @@ -3749,7 +3747,7 @@ export class BeatUpAttr extends VariablePowerAttr { * @returns true if the function succeeds */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const power = args[0] as Utils.NumberHolder; + const power = args[0] as NumberHolder; const party = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); const allyCount = party.filter(pokemon => { @@ -3764,7 +3762,7 @@ export class BeatUpAttr extends VariablePowerAttr { const doublePowerChanceMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => { let message: string = ""; globalScene.executeWithSeedOffset(() => { - const rand = Utils.randSeedInt(100); + const rand = randSeedInt(100); if (rand < move.chance) { message = i18next.t("moveTriggers:goingAllOutForAttack", { pokemonName: getPokemonNameWithAffix(user) }); } @@ -3775,9 +3773,9 @@ const doublePowerChanceMessageFunc = (user: Pokemon, target: Pokemon, move: Move export class DoublePowerChanceAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { let rand: number; - globalScene.executeWithSeedOffset(() => rand = Utils.randSeedInt(100), globalScene.currentBattle.turn << 6, globalScene.waveSeed); + globalScene.executeWithSeedOffset(() => rand = randSeedInt(100), globalScene.currentBattle.turn << 6, globalScene.waveSeed); if (rand! < move.chance) { - const power = args[0] as Utils.NumberHolder; + const power = args[0] as NumberHolder; power.value *= 2; return true; } @@ -3831,7 +3829,7 @@ export class ConsecutiveUseMultiBasePowerAttr extends ConsecutiveUsePowerMultipl export class WeightPowerAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const power = args[0] as Utils.NumberHolder; + const power = args[0] as NumberHolder; const targetWeight = target.getWeight(); const weightThresholds = [ 10, 25, 50, 100, 200 ]; @@ -3865,7 +3863,7 @@ export class ElectroBallPowerAttr extends VariablePowerAttr { * @returns true if the function succeeds */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const power = args[0] as Utils.NumberHolder; + const power = args[0] as NumberHolder; const statRatio = target.getEffectiveStat(Stat.SPD) / user.getEffectiveStat(Stat.SPD); const statThresholds = [ 0.25, 1 / 3, 0.5, 1, -1 ]; @@ -3900,7 +3898,7 @@ export class GyroBallPowerAttr extends VariablePowerAttr { * @returns true if the function succeeds */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const power = args[0] as Utils.NumberHolder; + const power = args[0] as NumberHolder; const userSpeed = user.getEffectiveStat(Stat.SPD); if (userSpeed < 1) { // Gen 6+ always have 1 base power @@ -3915,7 +3913,7 @@ export class GyroBallPowerAttr extends VariablePowerAttr { export class LowHpPowerAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const power = args[0] as Utils.NumberHolder; + const power = args[0] as NumberHolder; const hpRatio = user.getHpRatio(); switch (true) { @@ -3945,7 +3943,7 @@ export class LowHpPowerAttr extends VariablePowerAttr { export class CompareWeightPowerAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const power = args[0] as Utils.NumberHolder; + const power = args[0] as NumberHolder; const userWeight = user.getWeight(); const targetWeight = target.getWeight(); @@ -3979,7 +3977,7 @@ export class CompareWeightPowerAttr extends VariablePowerAttr { export class HpPowerAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.NumberHolder).value = Utils.toDmgValue(150 * user.getHpRatio()); + (args[0] as NumberHolder).value = toDmgValue(150 * user.getHpRatio()); return true; } @@ -4007,7 +4005,7 @@ export class OpponentHighHpPowerAttr extends VariablePowerAttr { * @returns true */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.NumberHolder).value = Utils.toDmgValue(this.maxBasePower * target.getHpRatio()); + (args[0] as NumberHolder).value = toDmgValue(this.maxBasePower * target.getHpRatio()); return true; } @@ -4017,7 +4015,7 @@ export class FirstAttackDoublePowerAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { console.log(target.getLastXMoves(1), globalScene.currentBattle.turn); if (!target.getLastXMoves(1).find(m => m.turn === globalScene.currentBattle.turn)) { - (args[0] as Utils.NumberHolder).value *= 2; + (args[0] as NumberHolder).value *= 2; return true; } @@ -4029,7 +4027,7 @@ export class FirstAttackDoublePowerAttr extends VariablePowerAttr { export class TurnDamagedDoublePowerAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (user.turnData.attacksReceived.find(r => r.damage && r.sourceId === target.id)) { - (args[0] as Utils.NumberHolder).value *= 2; + (args[0] as NumberHolder).value *= 2; return true; } @@ -4042,7 +4040,7 @@ const magnitudeMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => { globalScene.executeWithSeedOffset(() => { const magnitudeThresholds = [ 5, 15, 35, 65, 75, 95 ]; - const rand = Utils.randSeedInt(100); + const rand = randSeedInt(100); let m = 0; for (; m < magnitudeThresholds.length; m++) { @@ -4058,14 +4056,14 @@ const magnitudeMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => { export class MagnitudePowerAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const power = args[0] as Utils.NumberHolder; + const power = args[0] as NumberHolder; const magnitudeThresholds = [ 5, 15, 35, 65, 75, 95 ]; const magnitudePowers = [ 10, 30, 50, 70, 90, 100, 110, 150 ]; let rand: number; - globalScene.executeWithSeedOffset(() => rand = Utils.randSeedInt(100), globalScene.currentBattle.turn << 6, globalScene.waveSeed); + globalScene.executeWithSeedOffset(() => rand = randSeedInt(100), globalScene.currentBattle.turn << 6, globalScene.waveSeed); let m = 0; for (; m < magnitudeThresholds.length; m++) { @@ -4083,7 +4081,7 @@ export class MagnitudePowerAttr extends VariablePowerAttr { export class AntiSunlightPowerDecreaseAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!globalScene.arena.weather?.isEffectSuppressed()) { - const power = args[0] as Utils.NumberHolder; + const power = args[0] as NumberHolder; const weatherType = globalScene.arena.weather?.weatherType || WeatherType.NONE; switch (weatherType) { case WeatherType.RAIN: @@ -4110,7 +4108,7 @@ export class FriendshipPowerAttr extends VariablePowerAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const power = args[0] as Utils.NumberHolder; + const power = args[0] as NumberHolder; const friendshipPower = Math.floor(Math.min(user instanceof PlayerPokemon ? user.friendship : user.species.baseFriendship, 255) / 2.5); power.value = Math.max(!this.invert ? friendshipPower : 102 - friendshipPower, 1); @@ -4126,7 +4124,7 @@ export class FriendshipPowerAttr extends VariablePowerAttr { export class RageFistPowerAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const { hitCount, prevHitCount } = user.battleData; - const basePower: Utils.NumberHolder = args[0]; + const basePower: NumberHolder = args[0]; this.updateHitReceivedCount(user, hitCount, prevHitCount); @@ -4171,7 +4169,7 @@ export class PositiveStatStagePowerAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const positiveStatStages: number = countPositiveStatStages(user); - (args[0] as Utils.NumberHolder).value += positiveStatStages * 20; + (args[0] as NumberHolder).value += positiveStatStages * 20; return true; } } @@ -4194,7 +4192,7 @@ export class PunishmentPowerAttr extends VariablePowerAttr { */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const positiveStatStages: number = countPositiveStatStages(target); - (args[0] as Utils.NumberHolder).value = Math.min( + (args[0] as NumberHolder).value = Math.min( this.PUNISHMENT_MAX_BASE_POWER, this.PUNISHMENT_MIN_BASE_POWER + positiveStatStages * 20 ); @@ -4210,18 +4208,18 @@ export class PresentPowerAttr extends VariablePowerAttr { */ const firstHit = (user.turnData.hitCount === user.turnData.hitsLeft); - const powerSeed = Utils.randSeedInt(firstHit ? 100 : 80); + const powerSeed = randSeedInt(firstHit ? 100 : 80); if (powerSeed <= 40) { - (args[0] as Utils.NumberHolder).value = 40; + (args[0] as NumberHolder).value = 40; } else if (40 < powerSeed && powerSeed <= 70) { - (args[0] as Utils.NumberHolder).value = 80; + (args[0] as NumberHolder).value = 80; } else if (70 < powerSeed && powerSeed <= 80) { - (args[0] as Utils.NumberHolder).value = 120; + (args[0] as NumberHolder).value = 120; } else if (80 < powerSeed && powerSeed <= 100) { // If this move is multi-hit, disable all other hits user.stopMultiHit(); globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), - Utils.toDmgValue(target.getMaxHp() / 4), i18next.t("moveTriggers:regainedHealth", { pokemonName: getPokemonNameWithAffix(target) }), true)); + toDmgValue(target.getMaxHp() / 4), i18next.t("moveTriggers:regainedHealth", { pokemonName: getPokemonNameWithAffix(target) }), true)); } return true; @@ -4231,7 +4229,7 @@ export class PresentPowerAttr extends VariablePowerAttr { export class WaterShurikenPowerAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (user.species.speciesId === Species.GRENINJA && user.hasAbility(Abilities.BATTLE_BOND) && user.formIndex === 2) { - (args[0] as Utils.NumberHolder).value = 20; + (args[0] as NumberHolder).value = 20; return true; } return false; @@ -4253,7 +4251,7 @@ export class SpitUpPowerAttr extends VariablePowerAttr { const stockpilingTag = user.getTag(StockpilingTag); if (stockpilingTag && stockpilingTag.stockpiledCount > 0) { - const power = args[0] as Utils.NumberHolder; + const power = args[0] as NumberHolder; power.value = this.multiplier * stockpilingTag.stockpiledCount; return true; } @@ -4321,12 +4319,12 @@ export class MultiHitPowerIncrementAttr extends VariablePowerAttr { * @param user {@linkcode Pokemon} that used the move * @param target {@linkcode Pokemon} that the move was used on * @param move {@linkcode Move} with this attribute - * @param args [0] {@linkcode Utils.NumberHolder} for final calculated power of move + * @param args [0] {@linkcode NumberHolder} for final calculated power of move * @returns true if attribute application succeeds */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const hitsTotal = user.turnData.hitCount - Math.max(user.turnData.hitsLeft, 0); - const power = args[0] as Utils.NumberHolder; + const power = args[0] as NumberHolder; power.value = move.power * (1 + hitsTotal % this.maxHits); @@ -4358,11 +4356,11 @@ export class LastMoveDoublePowerAttr extends VariablePowerAttr { * @param user {@linkcode Pokemon} that used the move * @param target N/A * @param move N/A - * @param args [0] {@linkcode Utils.NumberHolder} that holds the resulting power of the move + * @param args [0] {@linkcode NumberHolder} that holds the resulting power of the move * @returns true if attribute application succeeds, false otherwise */ apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { - const power = args[0] as Utils.NumberHolder; + const power = args[0] as NumberHolder; const enemy = user.getOpponent(0); const pokemonActed: Pokemon[] = []; @@ -4374,10 +4372,10 @@ export class LastMoveDoublePowerAttr extends VariablePowerAttr { const userAlly = user.getAlly(); const enemyAlly = enemy?.getAlly(); - if (!Utils.isNullOrUndefined(userAlly) && userAlly.turnData.acted) { + if (!isNullOrUndefined(userAlly) && userAlly.turnData.acted) { pokemonActed.push(userAlly); } - if (!Utils.isNullOrUndefined(enemyAlly) && enemyAlly.turnData.acted) { + if (!isNullOrUndefined(enemyAlly) && enemyAlly.turnData.acted) { pokemonActed.push(enemyAlly); } } @@ -4407,7 +4405,7 @@ export class LastMoveDoublePowerAttr extends VariablePowerAttr { export class CombinedPledgePowerAttr extends VariablePowerAttr { override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const power = args[0]; - if (!(power instanceof Utils.NumberHolder)) { + if (!(power instanceof NumberHolder)) { return false; } const combinedPledgeMove = user.turnData.combiningPledge; @@ -4426,7 +4424,7 @@ export class CombinedPledgePowerAttr extends VariablePowerAttr { export class CombinedPledgeStabBoostAttr extends MoveAttr { override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const stabMultiplier = args[0]; - if (!(stabMultiplier instanceof Utils.NumberHolder)) { + if (!(stabMultiplier instanceof NumberHolder)) { return false; } const combinedPledgeMove = user.turnData.combiningPledge; @@ -4447,7 +4445,7 @@ export class CombinedPledgeStabBoostAttr extends MoveAttr { export class RoundPowerAttr extends VariablePowerAttr { override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const power = args[0]; - if (!(power instanceof Utils.NumberHolder)) { + if (!(power instanceof NumberHolder)) { return false; } @@ -4572,7 +4570,7 @@ export class TargetAtkUserAtkAttr extends VariableAtkAttr { super(); } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.NumberHolder).value = target.getEffectiveStat(Stat.ATK, target); + (args[0] as NumberHolder).value = target.getEffectiveStat(Stat.ATK, target); return true; } } @@ -4583,7 +4581,7 @@ export class DefAtkAttr extends VariableAtkAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.NumberHolder).value = user.getEffectiveStat(Stat.DEF, target); + (args[0] as NumberHolder).value = user.getEffectiveStat(Stat.DEF, target); return true; } } @@ -4605,7 +4603,7 @@ export class DefDefAttr extends VariableDefAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.NumberHolder).value = target.getEffectiveStat(Stat.DEF, user); + (args[0] as NumberHolder).value = target.getEffectiveStat(Stat.DEF, user); return true; } } @@ -4623,7 +4621,7 @@ export class VariableAccuracyAttr extends MoveAttr { export class ThunderAccuracyAttr extends VariableAccuracyAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!globalScene.arena.weather?.isEffectSuppressed()) { - const accuracy = args[0] as Utils.NumberHolder; + const accuracy = args[0] as NumberHolder; const weatherType = globalScene.arena.weather?.weatherType || WeatherType.NONE; switch (weatherType) { case WeatherType.SUNNY: @@ -4649,7 +4647,7 @@ export class ThunderAccuracyAttr extends VariableAccuracyAttr { export class StormAccuracyAttr extends VariableAccuracyAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!globalScene.arena.weather?.isEffectSuppressed()) { - const accuracy = args[0] as Utils.NumberHolder; + const accuracy = args[0] as NumberHolder; const weatherType = globalScene.arena.weather?.weatherType || WeatherType.NONE; switch (weatherType) { case WeatherType.RAIN: @@ -4680,7 +4678,7 @@ export class AlwaysHitMinimizeAttr extends VariableAccuracyAttr { */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (target.getTag(BattlerTagType.MINIMIZED)) { - const accuracy = args[0] as Utils.NumberHolder; + const accuracy = args[0] as NumberHolder; accuracy.value = -1; return true; @@ -4693,7 +4691,7 @@ export class AlwaysHitMinimizeAttr extends VariableAccuracyAttr { export class ToxicAccuracyAttr extends VariableAccuracyAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (user.isOfType(PokemonType.POISON)) { - const accuracy = args[0] as Utils.NumberHolder; + const accuracy = args[0] as NumberHolder; accuracy.value = -1; return true; } @@ -4705,7 +4703,7 @@ export class ToxicAccuracyAttr extends VariableAccuracyAttr { export class BlizzardAccuracyAttr extends VariableAccuracyAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!globalScene.arena.weather?.isEffectSuppressed()) { - const accuracy = args[0] as Utils.NumberHolder; + const accuracy = args[0] as NumberHolder; const weatherType = globalScene.arena.weather?.weatherType || WeatherType.NONE; if (weatherType === WeatherType.HAIL || weatherType === WeatherType.SNOW) { accuracy.value = -1; @@ -4725,7 +4723,7 @@ export class VariableMoveCategoryAttr extends MoveAttr { export class PhotonGeyserCategoryAttr extends VariableMoveCategoryAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const category = (args[0] as Utils.NumberHolder); + const category = (args[0] as NumberHolder); if (user.getEffectiveStat(Stat.ATK, target, move) > user.getEffectiveStat(Stat.SPATK, target, move)) { category.value = MoveCategory.PHYSICAL; @@ -4745,7 +4743,7 @@ export class PhotonGeyserCategoryAttr extends VariableMoveCategoryAttr { */ export class TeraMoveCategoryAttr extends VariableMoveCategoryAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const category = (args[0] as Utils.NumberHolder); + const category = (args[0] as NumberHolder); if (user.isTerastallized && user.getEffectiveStat(Stat.ATK, target, move, true, true, false, false, true) > user.getEffectiveStat(Stat.SPATK, target, move, true, true, false, false, true)) { @@ -4769,12 +4767,12 @@ export class TeraBlastPowerAttr extends VariablePowerAttr { * @param target n/a * @param move {@linkcode Move} the Move with this attribute (i.e. Tera Blast) * @param args - * - [0] {@linkcode Utils.NumberHolder} the applied move's power, factoring in + * - [0] {@linkcode NumberHolder} the applied move's power, factoring in * previously applied power modifiers. * @returns */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const power = args[0] as Utils.NumberHolder; + const power = args[0] as NumberHolder; if (user.isTerastallized && user.getTeraType() === PokemonType.STELLAR) { power.value = 100; return true; @@ -4794,11 +4792,11 @@ export class StatusCategoryOnAllyAttr extends VariableMoveCategoryAttr { * @param user {@linkcode Pokemon} using the move * @param target {@linkcode Pokemon} target of the move * @param move {@linkcode Move} with this attribute - * @param args [0] {@linkcode Utils.NumberHolder} The category of the move + * @param args [0] {@linkcode NumberHolder} The category of the move * @returns true if the function succeeds */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const category = (args[0] as Utils.NumberHolder); + const category = (args[0] as NumberHolder); if (user.getAlly() === target) { category.value = MoveCategory.STATUS; @@ -4811,7 +4809,7 @@ export class StatusCategoryOnAllyAttr extends VariableMoveCategoryAttr { export class ShellSideArmCategoryAttr extends VariableMoveCategoryAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const category = (args[0] as Utils.NumberHolder); + const category = (args[0] as NumberHolder); const predictedPhysDmg = target.getBaseDamage(user, move, MoveCategory.PHYSICAL, true, true, true, true); const predictedSpecDmg = target.getBaseDamage(user, move, MoveCategory.SPECIAL, true, true, true, true); @@ -4836,7 +4834,7 @@ export class VariableMoveTypeAttr extends MoveAttr { export class FormChangeItemTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const moveType = args[0]; - if (!(moveType instanceof Utils.NumberHolder)) { + if (!(moveType instanceof NumberHolder)) { return false; } @@ -4854,7 +4852,7 @@ export class FormChangeItemTypeAttr extends VariableMoveTypeAttr { export class TechnoBlastTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const moveType = args[0]; - if (!(moveType instanceof Utils.NumberHolder)) { + if (!(moveType instanceof NumberHolder)) { return false; } @@ -4888,7 +4886,7 @@ export class TechnoBlastTypeAttr extends VariableMoveTypeAttr { export class AuraWheelTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const moveType = args[0]; - if (!(moveType instanceof Utils.NumberHolder)) { + if (!(moveType instanceof NumberHolder)) { return false; } @@ -4913,7 +4911,7 @@ export class AuraWheelTypeAttr extends VariableMoveTypeAttr { export class RagingBullTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const moveType = args[0]; - if (!(moveType instanceof Utils.NumberHolder)) { + if (!(moveType instanceof NumberHolder)) { return false; } @@ -4941,7 +4939,7 @@ export class RagingBullTypeAttr extends VariableMoveTypeAttr { export class IvyCudgelTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const moveType = args[0]; - if (!(moveType instanceof Utils.NumberHolder)) { + if (!(moveType instanceof NumberHolder)) { return false; } @@ -4976,7 +4974,7 @@ export class IvyCudgelTypeAttr extends VariableMoveTypeAttr { export class WeatherBallTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const moveType = args[0]; - if (!(moveType instanceof Utils.NumberHolder)) { + if (!(moveType instanceof NumberHolder)) { return false; } @@ -5018,12 +5016,12 @@ export class TerrainPulseTypeAttr extends VariableMoveTypeAttr { * @param user {@linkcode Pokemon} using this move * @param target N/A * @param move N/A - * @param args [0] {@linkcode Utils.NumberHolder} The move's type to be modified + * @param args [0] {@linkcode NumberHolder} The move's type to be modified * @returns true if the function succeeds */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const moveType = args[0]; - if (!(moveType instanceof Utils.NumberHolder)) { + if (!(moveType instanceof NumberHolder)) { return false; } @@ -5059,7 +5057,7 @@ export class TerrainPulseTypeAttr extends VariableMoveTypeAttr { export class HiddenPowerTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const moveType = args[0]; - if (!(moveType instanceof Utils.NumberHolder)) { + if (!(moveType instanceof NumberHolder)) { return false; } @@ -5094,7 +5092,7 @@ export class TeraBlastTypeAttr extends VariableMoveTypeAttr { */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const moveType = args[0]; - if (!(moveType instanceof Utils.NumberHolder)) { + if (!(moveType instanceof NumberHolder)) { return false; } @@ -5117,12 +5115,12 @@ export class TeraStarstormTypeAttr extends VariableMoveTypeAttr { * @param user the {@linkcode Pokemon} using the move * @param target n/a * @param move n/a - * @param args[0] {@linkcode Utils.NumberHolder} the move type + * @param args[0] {@linkcode NumberHolder} the move type * @returns `true` if the move type is changed to {@linkcode PokemonType.STELLAR}, `false` otherwise */ override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (user.isTerastallized && user.hasSpecies(Species.TERAPAGOS)) { - const moveType = args[0] as Utils.NumberHolder; + const moveType = args[0] as NumberHolder; moveType.value = PokemonType.STELLAR; return true; @@ -5134,7 +5132,7 @@ export class TeraStarstormTypeAttr extends VariableMoveTypeAttr { export class MatchUserTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const moveType = args[0]; - if (!(moveType instanceof Utils.NumberHolder)) { + if (!(moveType instanceof NumberHolder)) { return false; } const userTypes = user.getTypes(true); @@ -5160,7 +5158,7 @@ export class MatchUserTypeAttr extends VariableMoveTypeAttr { export class CombinedPledgeTypeAttr extends VariableMoveTypeAttr { override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const moveType = args[0]; - if (!(moveType instanceof Utils.NumberHolder)) { + if (!(moveType instanceof NumberHolder)) { return false; } @@ -5203,7 +5201,7 @@ export class VariableMoveTypeMultiplierAttr extends MoveAttr { export class NeutralDamageAgainstFlyingTypeMultiplierAttr extends VariableMoveTypeMultiplierAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!target.getTag(BattlerTagType.IGNORE_FLYING)) { - const multiplier = args[0] as Utils.NumberHolder; + const multiplier = args[0] as NumberHolder; //When a flying type is hit, the first hit is always 1x multiplier. if (target.isOfType(PokemonType.FLYING)) { multiplier.value = 1; @@ -5221,11 +5219,11 @@ export class IceNoEffectTypeAttr extends VariableMoveTypeMultiplierAttr { * @param user n/a * @param target The {@linkcode Pokemon} targeted by the move * @param move n/a - * @param args `[0]` a {@linkcode Utils.NumberHolder | NumberHolder} containing a type effectiveness multiplier + * @param args `[0]` a {@linkcode NumberHolder | NumberHolder} containing a type effectiveness multiplier * @returns `true` if this Ice-type immunity applies; `false` otherwise */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const multiplier = args[0] as Utils.NumberHolder; + const multiplier = args[0] as NumberHolder; if (target.isOfType(PokemonType.ICE)) { multiplier.value = 0; return true; @@ -5236,7 +5234,7 @@ export class IceNoEffectTypeAttr extends VariableMoveTypeMultiplierAttr { export class FlyingTypeMultiplierAttr extends VariableMoveTypeMultiplierAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const multiplier = args[0] as Utils.NumberHolder; + const multiplier = args[0] as NumberHolder; multiplier.value *= target.getAttackTypeEffectiveness(PokemonType.FLYING, user); return true; } @@ -5265,7 +5263,7 @@ export class VariableMoveTypeChartAttr extends MoveAttr { */ export class FreezeDryAttr extends VariableMoveTypeChartAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const multiplier = args[0] as Utils.NumberHolder; + const multiplier = args[0] as NumberHolder; const defType = args[1] as PokemonType; if (defType === PokemonType.WATER) { @@ -5279,7 +5277,7 @@ export class FreezeDryAttr extends VariableMoveTypeChartAttr { export class OneHitKOAccuracyAttr extends VariableAccuracyAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const accuracy = args[0] as Utils.NumberHolder; + const accuracy = args[0] as NumberHolder; if (user.level < target.level) { accuracy.value = 0; } else { @@ -5301,7 +5299,7 @@ export class SheerColdAccuracyAttr extends OneHitKOAccuracyAttr { * @returns Returns true if move is successful, false if misses. */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const accuracy = args[0] as Utils.NumberHolder; + const accuracy = args[0] as NumberHolder; if (user.level < target.level) { accuracy.value = 0; } else { @@ -5343,15 +5341,15 @@ export class NoEffectAttr extends MoveAttr { } const crashDamageFunc = (user: Pokemon, move: Move) => { - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); if (cancelled.value) { return false; } - user.damageAndUpdate(Utils.toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT }); + user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT }); globalScene.queueMessage(i18next.t("moveTriggers:keptGoingAndCrashed", { pokemonName: getPokemonNameWithAffix(user) })); - user.turnData.damageTaken += Utils.toDmgValue(user.getMaxHp() / 2); + user.turnData.damageTaken += toDmgValue(user.getMaxHp() / 2); return true; }; @@ -6177,10 +6175,10 @@ export class RevivalBlessingAttr extends MoveEffectAttr { const pokemon = faintedPokemon[user.randSeedInt(faintedPokemon.length)]; const slotIndex = globalScene.getEnemyParty().findIndex((p) => pokemon.id === p.id); pokemon.resetStatus(); - pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp())); + pokemon.heal(Math.min(toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp())); globalScene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: getPokemonNameWithAffix(pokemon) }), 0, true); const allyPokemon = user.getAlly(); - if (globalScene.currentBattle.double && globalScene.getEnemyParty().length > 1 && !Utils.isNullOrUndefined(allyPokemon)) { + if (globalScene.currentBattle.double && globalScene.getEnemyParty().length > 1 && !isNullOrUndefined(allyPokemon)) { // Handle cases where revived pokemon needs to get switched in on same turn if (allyPokemon.isFainted() || allyPokemon === pokemon) { // Enemy switch phase should be removed and replaced with the revived pkmn switching in @@ -6366,7 +6364,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { globalScene.queueMessage(i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }), null, true, 500); // in double battles redirect potential moves off fled pokemon - if (globalScene.currentBattle.double && !Utils.isNullOrUndefined(allyPokemon)) { + if (globalScene.currentBattle.double && !isNullOrUndefined(allyPokemon)) { globalScene.redirectPokemonMoves(switchOutTarget, allyPokemon); } } @@ -6389,7 +6387,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { } getFailedText(_user: Pokemon, target: Pokemon, _move: Move): string | undefined { - const blockedByAbility = new Utils.BooleanHolder(false); + const blockedByAbility = new BooleanHolder(false); applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, blockedByAbility); if (blockedByAbility.value) { return i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) }); @@ -6417,7 +6415,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { return false; } - const blockedByAbility = new Utils.BooleanHolder(false); + const blockedByAbility = new BooleanHolder(false); applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, blockedByAbility); return !blockedByAbility.value; } @@ -6776,7 +6774,7 @@ export class RandomMoveAttr extends CallMoveAttr { * @param args Unused */ override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const moveIds = Utils.getEnumValues(Moves).map(m => !this.invalidMoves.has(m) && !allMoves[m].name.endsWith(" (N)") ? m : Moves.NONE); + const moveIds = getEnumValues(Moves).map(m => !this.invalidMoves.has(m) && !allMoves[m].name.endsWith(" (N)") ? m : Moves.NONE); let moveId: Moves = Moves.NONE; do { moveId = this.getMoveOverride() ?? moveIds[user.randSeedInt(moveIds.length)]; @@ -7047,7 +7045,7 @@ export class RepeatMoveAttr extends MoveEffectAttr { const firstTarget = globalScene.getField()[moveTargets[0]]; if (globalScene.currentBattle.double && moveTargets.length === 1 && firstTarget.isFainted() && firstTarget !== target.getAlly()) { const ally = firstTarget.getAlly(); - if (!Utils.isNullOrUndefined(ally) && ally.isActive()) { // ally exists, is not dead and can sponge the blast + if (!isNullOrUndefined(ally) && ally.isActive()) { // ally exists, is not dead and can sponge the blast moveTargets = [ ally.getBattlerIndex() ]; } } @@ -7423,7 +7421,7 @@ export class AbilityCopyAttr extends MoveEffectAttr { user.setTempAbility(target.getAbility()); const ally = user.getAlly(); - if (this.copyToPartner && globalScene.currentBattle?.double && !Utils.isNullOrUndefined(ally) && ally.hp) { // TODO is this the best way to check that the ally is active? + if (this.copyToPartner && globalScene.currentBattle?.double && !isNullOrUndefined(ally) && ally.hp) { // TODO is this the best way to check that the ally is active? globalScene.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", { pokemonName: getPokemonNameWithAffix(ally), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name })); ally.setTempAbility(target.getAbility()); } @@ -7839,7 +7837,7 @@ export class VariableTargetAttr extends MoveAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const targetVal = args[0] as Utils.NumberHolder; + const targetVal = args[0] as NumberHolder; targetVal.value = this.targetChangeFunc(user, target, move); return true; } @@ -7930,7 +7928,7 @@ const failOnBossCondition: MoveConditionFunc = (user, target, move) => !target.i const failIfSingleBattle: MoveConditionFunc = (user, target, move) => globalScene.currentBattle.double; const failIfDampCondition: MoveConditionFunc = (user, target, move) => { - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); globalScene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled)); // Queue a message if an ability prevented usage of the move if (cancelled.value) { @@ -8065,7 +8063,7 @@ export class UpperHandCondition extends MoveCondition { export class hitsSameTypeAttr extends VariableMoveTypeMultiplierAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const multiplier = args[0] as Utils.NumberHolder; + const multiplier = args[0] as NumberHolder; if (!user.getTypes().some(type => target.getTypes().includes(type))) { multiplier.value = 0; return true; @@ -8116,7 +8114,7 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr { } const type = validTypes[user.randSeedInt(validTypes.length)]; user.summonData.types = [ type ]; - globalScene.queueMessage(i18next.t("battle:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), type: Utils.toReadableString(PokemonType[type]) })); + globalScene.queueMessage(i18next.t("battle:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), type: toReadableString(PokemonType[type]) })); user.updateInfo(); return true; @@ -8190,7 +8188,7 @@ export type MoveTargetSet = { }; export function getMoveTargets(user: Pokemon, move: Moves, replaceTarget?: MoveTarget): MoveTargetSet { - const variableTarget = new Utils.NumberHolder(0); + const variableTarget = new NumberHolder(0); user.getOpponents().forEach(p => applyMoveAttrs(VariableTargetAttr, user, p, allMoves[move], variableTarget)); let moveTarget: MoveTarget | undefined; @@ -8218,7 +8216,7 @@ export function getMoveTargets(user: Pokemon, move: Moves, replaceTarget?: MoveT case MoveTarget.OTHER: case MoveTarget.ALL_NEAR_OTHERS: case MoveTarget.ALL_OTHERS: - set = !Utils.isNullOrUndefined(ally) ? (opponents.concat([ ally ])) : opponents; + set = !isNullOrUndefined(ally) ? (opponents.concat([ ally ])) : opponents; multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS; break; case MoveTarget.NEAR_ENEMY: @@ -8235,21 +8233,21 @@ export function getMoveTargets(user: Pokemon, move: Moves, replaceTarget?: MoveT return { targets: [ -1 as BattlerIndex ], multiple: false }; case MoveTarget.NEAR_ALLY: case MoveTarget.ALLY: - set = !Utils.isNullOrUndefined(ally) ? [ ally ] : []; + set = !isNullOrUndefined(ally) ? [ ally ] : []; break; case MoveTarget.USER_OR_NEAR_ALLY: case MoveTarget.USER_AND_ALLIES: case MoveTarget.USER_SIDE: - set = !Utils.isNullOrUndefined(ally) ? [ user, ally ] : [ user ]; + set = !isNullOrUndefined(ally) ? [ user, ally ] : [ user ]; multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY; break; case MoveTarget.ALL: case MoveTarget.BOTH_SIDES: - set = (!Utils.isNullOrUndefined(ally) ? [ user, ally ] : [ user ]).concat(opponents); + set = (!isNullOrUndefined(ally) ? [ user, ally ] : [ user ]).concat(opponents); multiple = true; break; case MoveTarget.CURSE: - const extraTargets = !Utils.isNullOrUndefined(ally) ? [ ally ] : []; + const extraTargets = !isNullOrUndefined(ally) ? [ ally ] : []; set = user.getTypes(true).includes(PokemonType.GHOST) ? (opponents.concat(extraTargets)) : [ user ]; break; } @@ -8408,7 +8406,7 @@ export function initMoves() { .attr(AddBattlerTagAttr, BattlerTagType.DISABLED, false, true) .condition((user, target, move) => { const lastRealMove = target.getLastXMoves(-1).find(m => !m.virtual); - return !Utils.isNullOrUndefined(lastRealMove) && lastRealMove.move !== Moves.NONE && lastRealMove.move !== Moves.STRUGGLE; + return !isNullOrUndefined(lastRealMove) && lastRealMove.move !== Moves.NONE && lastRealMove.move !== Moves.STRUGGLE; }) .ignoresSubstitute() .reflectable(), @@ -9716,7 +9714,7 @@ export function initMoves() { .condition(failOnGravityCondition) .condition((_user, target, _move) => ![ Species.DIGLETT, Species.DUGTRIO, Species.ALOLA_DIGLETT, Species.ALOLA_DUGTRIO, Species.SANDYGAST, Species.PALOSSAND, Species.WIGLETT, Species.WUGTRIO ].includes(target.species.speciesId)) .condition((_user, target, _move) => !(target.species.speciesId === Species.GENGAR && target.getFormKey() === "mega")) - .condition((_user, target, _move) => Utils.isNullOrUndefined(target.getTag(BattlerTagType.INGRAIN)) && Utils.isNullOrUndefined(target.getTag(BattlerTagType.IGNORE_FLYING))) + .condition((_user, target, _move) => isNullOrUndefined(target.getTag(BattlerTagType.INGRAIN)) && isNullOrUndefined(target.getTag(BattlerTagType.IGNORE_FLYING))) .attr(AddBattlerTagAttr, BattlerTagType.TELEKINESIS, false, true, 3) .attr(AddBattlerTagAttr, BattlerTagType.FLOATING, false, true, 3) .reflectable(), diff --git a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts index 11924f93df4..5f88ca083c0 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -12,7 +12,7 @@ import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { globalScene } from "#app/global-scene"; -import * as Utils from "#app/utils"; +import { randSeedInt } from "#app/utils"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -46,7 +46,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounter const normalConfig = trainerConfigs[normalTrainerType].clone(); let female = false; if (normalConfig.hasGenders) { - female = !!Utils.randSeedInt(2); + female = !!randSeedInt(2); } const normalSpriteKey = normalConfig.getSpriteKey(female, normalConfig.doubleOnly); encounter.enemyPartyConfigs.push({ @@ -76,7 +76,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounter hardConfig.setPartyTemplates(hardTemplate); female = false; if (hardConfig.hasGenders) { - female = !!Utils.randSeedInt(2); + female = !!randSeedInt(2); } const hardSpriteKey = hardConfig.getSpriteKey(female, hardConfig.doubleOnly); encounter.enemyPartyConfigs.push({ @@ -96,7 +96,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounter brutalConfig.partyTemplateFunc = null; // Overrides gym leader party template func female = false; if (brutalConfig.hasGenders) { - female = !!Utils.randSeedInt(2); + female = !!randSeedInt(2); } const brutalSpriteKey = brutalConfig.getSpriteKey(female, brutalConfig.doubleOnly); encounter.enemyPartyConfigs.push({ diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index 53e976cda8a..ff098d4d7dd 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -5,7 +5,7 @@ import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro"; import MysteryEncounterIntroVisuals from "#app/field/mystery-encounter-intro"; -import * as Utils from "#app/utils"; +import { randSeedInt } from "#app/utils"; import type { StatusEffect } from "#enums/status-effect"; import type { OptionTextDisplay } from "./mystery-encounter-dialogue"; import type MysteryEncounterDialogue from "./mystery-encounter-dialogue"; @@ -378,13 +378,13 @@ export default class MysteryEncounter implements IMysteryEncounter { } if (truePrimaryPool.length > 0) { // Always choose from the non-overlapping pokemon first - this.primaryPokemon = truePrimaryPool[Utils.randSeedInt(truePrimaryPool.length, 0)]; + this.primaryPokemon = truePrimaryPool[randSeedInt(truePrimaryPool.length, 0)]; return true; } // If there are multiple overlapping pokemon, we're okay - just choose one and take it out of the primary pokemon pool if (overlap.length > 1 || this.secondaryPokemon.length - overlap.length >= 1) { // is this working? - this.primaryPokemon = overlap[Utils.randSeedInt(overlap.length, 0)]; + this.primaryPokemon = overlap[randSeedInt(overlap.length, 0)]; this.secondaryPokemon = this.secondaryPokemon.filter(supp => supp !== this.primaryPokemon); return true; } @@ -394,7 +394,7 @@ export default class MysteryEncounter implements IMysteryEncounter { return false; } // this means we CAN have the same pokemon be a primary and secondary pokemon, so just choose any qualifying one randomly. - this.primaryPokemon = qualified[Utils.randSeedInt(qualified.length, 0)]; + this.primaryPokemon = qualified[randSeedInt(qualified.length, 0)]; return true; } diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index f3a06242a13..a9f6b787878 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -30,8 +30,7 @@ import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-optio import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler"; import { PartyUiMode } from "#app/ui/party-ui-handler"; import { Mode } from "#app/ui/ui"; -import * as Utils from "#app/utils"; -import { isNullOrUndefined, randSeedInt, randSeedItem } from "#app/utils"; +import { isNullOrUndefined, randSeedInt, randomString, randSeedItem } from "#app/utils"; import type { BattlerTagType } from "#enums/battler-tag-type"; import { Biome } from "#enums/biome"; import type { TrainerType } from "#enums/trainer-type"; @@ -168,7 +167,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): const doubleTrainer = trainerConfig.doubleOnly || (trainerConfig.hasDouble && !!partyConfig.doubleBattle); doubleBattle = doubleTrainer; - const trainerFemale = isNullOrUndefined(partyConfig.female) ? !!Utils.randSeedInt(2) : partyConfig.female; + const trainerFemale = isNullOrUndefined(partyConfig.female) ? !!randSeedInt(2) : partyConfig.female; const newTrainer = new Trainer( trainerConfig.trainerType, doubleTrainer ? TrainerVariant.DOUBLE : trainerFemale ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT, @@ -286,7 +285,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): // Generate new id, reset status and HP in case using data source if (config.dataSource) { - enemyPokemon.id = Utils.randSeedInt(4294967296); + enemyPokemon.id = randSeedInt(4294967296); } // Set form @@ -1115,7 +1114,7 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) { const validMEfloorsByBiome = new Map(biomes.map(b => [b, 0])); let currentBiome = Biome.TOWN; let currentArena = globalScene.newArena(currentBiome); - globalScene.setSeed(Utils.randomString(24)); + globalScene.setSeed(randomString(24)); globalScene.resetSeed(); for (let i = 10; i < 180; i++) { // Boss @@ -1130,16 +1129,16 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) { globalScene.executeWithSeedOffset(() => { biomes = (biomeLinks[currentBiome] as (Biome | [Biome, number])[]) .filter(b => { - return !Array.isArray(b) || !Utils.randSeedInt(b[1]); + return !Array.isArray(b) || !randSeedInt(b[1]); }) .map(b => (!Array.isArray(b) ? b : b[0])); }, i * 100); if (biomes! && biomes.length > 0) { const specialBiomes = biomes.filter(b => alwaysPickTheseBiomes.includes(b)); if (specialBiomes.length > 0) { - currentBiome = specialBiomes[Utils.randSeedInt(specialBiomes.length)]; + currentBiome = specialBiomes[randSeedInt(specialBiomes.length)]; } else { - currentBiome = biomes[Utils.randSeedInt(biomes.length)]; + currentBiome = biomes[randSeedInt(biomes.length)]; } } } else if (biomeLinks.hasOwnProperty(currentBiome)) { @@ -1167,7 +1166,7 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) { // Otherwise, roll encounter - const roll = Utils.randSeedInt(256); + const roll = randSeedInt(256); validMEfloorsByBiome.set(Biome[currentBiome], (validMEfloorsByBiome.get(Biome[currentBiome]) ?? 0) + 1); // If total number of encounters is lower than expected for the run, slightly favor a new encounter @@ -1192,7 +1191,7 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) { tierWeights[1] = tierWeights[1] - 4 * numEncounters[1]; const totalWeight = tierWeights.reduce((a, b) => a + b); - const tierValue = Utils.randSeedInt(totalWeight); + const tierValue = randSeedInt(totalWeight); const commonThreshold = totalWeight - tierWeights[0]; // 64 - 32 = 32 const uncommonThreshold = totalWeight - tierWeights[0] - tierWeights[1]; // 64 - 32 - 16 = 16 const rareThreshold = totalWeight - tierWeights[0] - tierWeights[1] - tierWeights[2]; // 64 - 32 - 16 - 10 = 6 @@ -1281,7 +1280,7 @@ export function calculateRareSpawnAggregateStats(luckValue: number) { const calculateNumRareEncounters = (): any[] => { const bossEncountersByRarity = [0, 0, 0, 0]; - globalScene.setSeed(Utils.randomString(24)); + globalScene.setSeed(randomString(24)); globalScene.resetSeed(); // There are 12 wild boss floors for (let i = 0; i < 12; i++) { @@ -1291,7 +1290,7 @@ export function calculateRareSpawnAggregateStats(luckValue: number) { if (!Number.isNaN(luckValue)) { luckModifier = luckValue * 0.5; } - const tierValue = Utils.randSeedInt(64 - luckModifier); + const tierValue = randSeedInt(64 - luckModifier); const tier = tierValue >= 20 ? BiomePoolTier.BOSS diff --git a/src/data/nature.ts b/src/data/nature.ts index e23d92c14b0..2ab4723c10d 100644 --- a/src/data/nature.ts +++ b/src/data/nature.ts @@ -1,4 +1,4 @@ -import * as Utils from "../utils"; +import { toReadableString } from "#app/utils"; import { TextStyle, getBBCodeFrag } from "../ui/text"; import { Nature } from "#enums/nature"; import { UiTheme } from "#enums/ui-theme"; @@ -12,7 +12,7 @@ export function getNatureName( ignoreBBCode = false, uiTheme: UiTheme = UiTheme.DEFAULT, ): string { - let ret = Utils.toReadableString(Nature[nature]); + let ret = toReadableString(Nature[nature]); //Translating nature if (i18next.exists(`nature:${ret}`)) { ret = i18next.t(`nature:${ret}` as any); diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index a8942a39880..a27c00121dc 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -8,7 +8,7 @@ import type { AnySound } from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; import type { GameMode } from "#app/game-mode"; import { DexAttr, type StarterMoveset } from "#app/system/game-data"; -import * as Utils from "#app/utils"; +import { isNullOrUndefined, capitalizeString, randSeedInt, randSeedGauss, randSeedItem } from "#app/utils"; import { uncatchableSpecies } from "#app/data/balance/biomes"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { GrowthRate } from "#app/data/exp"; @@ -290,7 +290,7 @@ export abstract class PokemonSpeciesForm { * @returns The id of the ability */ getPassiveAbility(formIndex?: number): Abilities { - if (Utils.isNullOrUndefined(formIndex)) { + if (isNullOrUndefined(formIndex)) { formIndex = this.formIndex; } let starterSpeciesId = this.speciesId; @@ -626,7 +626,7 @@ export abstract class PokemonSpeciesForm { const spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant, back) .replace("variant/", "") .replace(/_[1-3]$/, ""); - if (!Utils.isNullOrUndefined(variant)) { + if (!isNullOrUndefined(variant)) { loadPokemonVariantAssets(spriteKey, spritePath, variant).then(() => resolve()); } }); @@ -852,8 +852,8 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali */ getFormNameToDisplay(formIndex = 0, append = false): string { const formKey = this.forms?.[formIndex!]?.formKey; - const formText = Utils.capitalizeString(formKey, "-", false, false) || ""; - const speciesName = Utils.capitalizeString(Species[this.speciesId], "_", true, false); + const formText = capitalizeString(formKey, "-", false, false) || ""; + const speciesName = capitalizeString(Species[this.speciesId], "_", true, false); let ret = ""; const region = this.getRegion(); @@ -884,7 +884,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali if (i18next.exists(i18key)) { ret = i18next.t(i18key); } else { - const rootSpeciesName = Utils.capitalizeString(Species[this.getRootSpeciesId()], "_", true, false); + const rootSpeciesName = capitalizeString(Species[this.getRootSpeciesId()], "_", true, false); const i18RootKey = `pokemonForm:${rootSpeciesName}${formText}`; ret = i18next.exists(i18RootKey) ? i18next.t(i18RootKey) : formText; } @@ -1079,7 +1079,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali return this.speciesId; } - const randValue = evolutionPool.size === 1 ? 0 : Utils.randSeedInt(totalWeight); + const randValue = evolutionPool.size === 1 ? 0 : randSeedInt(totalWeight); for (const weight of evolutionPool.keys()) { if (randValue < weight) { @@ -1164,7 +1164,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali Math.min( Math.max( evolution?.level! + - Math.round(Utils.randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max(evolution?.wildDelay!, 0.5) * 5) - + Math.round(randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max(evolution?.wildDelay!, 0.5) * 5) - 1, 2, evolution?.level!, @@ -1182,7 +1182,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali Math.min( Math.max( lastPrevolutionLevel + - Math.round(Utils.randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max(evolution?.wildDelay!, 0.5) * 5), + Math.round(randSeedGauss(0.5, 1 + levelDiff * 0.2) * Math.max(evolution?.wildDelay!, 0.5) * 5), lastPrevolutionLevel + 1, evolution?.level!, ), @@ -1367,7 +1367,7 @@ export function getPokerusStarters(): PokemonSpecies[] { globalScene.executeWithSeedOffset( () => { while (pokerusStarters.length < POKERUS_STARTER_COUNT) { - const randomSpeciesId = Number.parseInt(Utils.randSeedItem(Object.keys(speciesStarterCosts)), 10); + const randomSpeciesId = Number.parseInt(randSeedItem(Object.keys(speciesStarterCosts)), 10); const species = getPokemonSpecies(randomSpeciesId); if (!pokerusStarters.includes(species)) { pokerusStarters.push(species); diff --git a/src/data/trainer-names.ts b/src/data/trainer-names.ts index 26cea19070f..195e5041d28 100644 --- a/src/data/trainer-names.ts +++ b/src/data/trainer-names.ts @@ -1,12 +1,12 @@ import { TrainerType } from "#enums/trainer-type"; -import * as Utils from "../utils"; +import { toReadableString } from "#app/utils"; class TrainerNameConfig { public urls: string[]; public femaleUrls: string[] | null; constructor(type: TrainerType, ...urls: string[]) { - this.urls = urls.length ? urls : [Utils.toReadableString(TrainerType[type]).replace(/ /g, "_")]; + this.urls = urls.length ? urls : [toReadableString(TrainerType[type]).replace(/ /g, "_")]; } hasGenderVariant(...femaleUrls: string[]): TrainerNameConfig { diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 5fab70971ec..0ab7119dab9 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -1,7 +1,7 @@ import { globalScene } from "#app/global-scene"; import { modifierTypes } from "#app/modifier/modifier-type"; import { PokemonMove } from "#app/field/pokemon"; -import * as Utils from "#app/utils"; +import { toReadableString, isNullOrUndefined, randSeedItem, randSeedInt } from "#app/utils"; import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { tmSpecies } from "#app/data/balance/tms"; @@ -139,7 +139,7 @@ export class TrainerConfig { constructor(trainerType: TrainerType, allowLegendaries?: boolean) { this.trainerType = trainerType; this.trainerAI = new TrainerAI(); - this.name = Utils.toReadableString(TrainerType[this.getDerivedType()]); + this.name = toReadableString(TrainerType[this.getDerivedType()]); this.battleBgm = "battle_trainer"; this.mixedBattleBgm = "battle_trainer"; this.victoryBgm = "victory_trainer"; @@ -482,10 +482,10 @@ export class TrainerConfig { .fill(null) .map((_, i) => i) .filter(i => shedinjaCanTera || party[i].species.speciesId !== Species.SHEDINJA); // Shedinja can only Tera on Bug specialty type (or no specialty type) - const setPartySlot = !Utils.isNullOrUndefined(slot) ? Phaser.Math.Wrap(slot, 0, party.length) : -1; // If we have a tera slot defined, wrap it to party size. + const setPartySlot = !isNullOrUndefined(slot) ? Phaser.Math.Wrap(slot, 0, party.length) : -1; // If we have a tera slot defined, wrap it to party size. for (let t = 0; t < Math.min(count(), party.length); t++) { const randomIndex = - partyMemberIndexes.indexOf(setPartySlot) > -1 ? setPartySlot : Utils.randSeedItem(partyMemberIndexes); + partyMemberIndexes.indexOf(setPartySlot) > -1 ? setPartySlot : randSeedItem(partyMemberIndexes); partyMemberIndexes.splice(partyMemberIndexes.indexOf(randomIndex), 1); if (this.hasSpecialtyType()) { party[randomIndex].teraType = this.specialtyType; @@ -555,7 +555,7 @@ export class TrainerConfig { initI18n(); } - if (!Utils.isNullOrUndefined(specialtyType)) { + if (!isNullOrUndefined(specialtyType)) { this.setSpecialtyType(specialtyType); } @@ -636,7 +636,7 @@ export class TrainerConfig { } this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool)); }); - if (!Utils.isNullOrUndefined(specialtyType)) { + if (!isNullOrUndefined(specialtyType)) { this.setSpeciesFilter(p => p.isOfType(specialtyType)); this.setSpecialtyType(specialtyType); } @@ -749,7 +749,7 @@ export class TrainerConfig { }); // Set species filter and specialty type if provided, otherwise filter by base total. - if (!Utils.isNullOrUndefined(specialtyType)) { + if (!isNullOrUndefined(specialtyType)) { this.setSpeciesFilter(p => p.isOfType(specialtyType) && p.baseTotal >= ELITE_FOUR_MINIMUM_BST); this.setSpecialtyType(specialtyType); } else { @@ -927,7 +927,7 @@ export class TrainerConfig { * @returns true if specialtyType is defined and not Type.UNKNOWN */ hasSpecialtyType(): boolean { - return !Utils.isNullOrUndefined(this.specialtyType) && this.specialtyType !== PokemonType.UNKNOWN; + return !isNullOrUndefined(this.specialtyType) && this.specialtyType !== PokemonType.UNKNOWN; } /** @@ -1006,7 +1006,7 @@ export function getRandomPartyMemberFunc( postProcess?: (enemyPokemon: EnemyPokemon) => void, ) { return (level: number, strength: PartyMemberStrength) => { - let species = Utils.randSeedItem(speciesPool); + let species = randSeedItem(speciesPool); if (!ignoreEvolution) { species = getPokemonSpecies(species).getTrainerSpeciesForLevel( level, @@ -3549,7 +3549,7 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc( 5, getRandomPartyMemberFunc([Species.URSHIFU], TrainerSlot.TRAINER, true, p => { - p.formIndex = Utils.randSeedInt(2, 2); // Random G-Max Urshifu + p.formIndex = randSeedInt(2, 2); // Random G-Max Urshifu p.generateAndPopulateMoveset(); p.generateName(); p.gender = Gender.MALE; @@ -3659,10 +3659,10 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc( 4, getRandomPartyMemberFunc([Species.OGERPON], TrainerSlot.TRAINER, true, p => { - p.formIndex = Utils.randSeedInt(4); // Random Ogerpon Tera Mask + p.formIndex = randSeedInt(4); // Random Ogerpon Tera Mask p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; - if (!p.moveset.some(move => !Utils.isNullOrUndefined(move) && move.moveId === Moves.IVY_CUDGEL)) { + if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === Moves.IVY_CUDGEL)) { // Check if Ivy Cudgel is in the moveset, if not, replace the first move with Ivy Cudgel. p.moveset[0] = new PokemonMove(Moves.IVY_CUDGEL); } @@ -4713,10 +4713,10 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc( 2, getRandomPartyMemberFunc([Species.SILVALLY], TrainerSlot.TRAINER, true, p => { - p.formIndex = Utils.randSeedInt(18); // Random Silvally Form + p.formIndex = randSeedInt(18); // Random Silvally Form p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ROGUE_BALL; - if (!p.moveset.some(move => !Utils.isNullOrUndefined(move) && move.moveId === Moves.MULTI_ATTACK)) { + if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === Moves.MULTI_ATTACK)) { // Check if Multi Attack is in the moveset, if not, replace the first move with Multi Attack. p.moveset[0] = new PokemonMove(Moves.MULTI_ATTACK); } @@ -4833,8 +4833,8 @@ export const trainerConfigs: TrainerConfigs = { p.setBoss(true, 2); p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; - p.formIndex = Utils.randSeedInt(4, 1); // Shock, Burn, Chill, or Douse Drive - if (!p.moveset.some(move => !Utils.isNullOrUndefined(move) && move.moveId === Moves.TECHNO_BLAST)) { + p.formIndex = randSeedInt(4, 1); // Shock, Burn, Chill, or Douse Drive + if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === Moves.TECHNO_BLAST)) { // Check if Techno Blast is in the moveset, if not, replace the first move with Techno Blast. p.moveset[2] = new PokemonMove(Moves.TECHNO_BLAST); } @@ -5006,7 +5006,7 @@ export const trainerConfigs: TrainerConfigs = { 1, getRandomPartyMemberFunc([Species.ROTOM], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); - p.formIndex = Utils.randSeedInt(5, 1); // Heat, Wash, Frost, Fan, or Mow + p.formIndex = randSeedInt(5, 1); // Heat, Wash, Frost, Fan, or Mow }), ) .setPartyMemberFunc( @@ -5019,7 +5019,7 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc( 3, getRandomPartyMemberFunc([Species.REVAVROOM], TrainerSlot.TRAINER, true, p => { - p.formIndex = Utils.randSeedInt(5, 1); // Random Starmobile form + p.formIndex = randSeedInt(5, 1); // Random Starmobile form p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ROGUE_BALL; }), diff --git a/src/data/weather.ts b/src/data/weather.ts index 34978232377..a8dd0a66492 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -5,7 +5,7 @@ import type Pokemon from "../field/pokemon"; import { PokemonType } from "#enums/pokemon-type"; import type Move from "./moves/move"; import { AttackMove } from "./moves/move"; -import * as Utils from "../utils"; +import { randSeedInt } from "#app/utils"; import { SuppressWeatherEffectAbAttr } from "./ability"; import { TerrainType, getTerrainName } from "./terrain"; import i18next from "i18next"; @@ -416,7 +416,7 @@ export function getRandomWeatherType(arena: Arena): WeatherType { totalWeight += w.weight; } - const rand = Utils.randSeedInt(totalWeight); + const rand = randSeedInt(totalWeight); let w = 0; for (const weather of weatherPool) { w += weather.weight; diff --git a/src/field/arena.ts b/src/field/arena.ts index cf48647e45e..adc3123ce81 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -1,8 +1,7 @@ import { globalScene } from "#app/global-scene"; import type { BiomeTierTrainerPools, PokemonPools } from "#app/data/balance/biomes"; import { biomePokemonPools, BiomePoolTier, biomeTrainerPools } from "#app/data/balance/biomes"; -import type { Constructor } from "#app/utils"; -import * as Utils from "#app/utils"; +import { randSeedInt, NumberHolder, isNullOrUndefined, type Constructor } from "#app/utils"; import type PokemonSpecies from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { @@ -124,7 +123,7 @@ export class Arena { if (typeof luckValue !== "undefined") { luckModifier = luckValue * (isBossSpecies ? 0.5 : 2); } - const tierValue = Utils.randSeedInt(randVal - luckModifier); + const tierValue = randSeedInt(randVal - luckModifier); let tier = !isBossSpecies ? tierValue >= 156 ? BiomePoolTier.COMMON @@ -153,7 +152,7 @@ export class Arena { if (!tierPool.length) { ret = globalScene.randomSpecies(waveIndex, level); } else { - const entry = tierPool[Utils.randSeedInt(tierPool.length)]; + const entry = tierPool[randSeedInt(tierPool.length)]; let species: Species; if (typeof entry === "number") { species = entry as Species; @@ -164,7 +163,7 @@ export class Arena { if (level >= levelThreshold) { const speciesIds = entry[levelThreshold]; if (speciesIds.length > 1) { - species = speciesIds[Utils.randSeedInt(speciesIds.length)]; + species = speciesIds[randSeedInt(speciesIds.length)]; } else { species = speciesIds[0]; } @@ -211,7 +210,7 @@ export class Arena { !!this.trainerPool[BiomePoolTier.BOSS].length && (globalScene.gameMode.isTrainerBoss(waveIndex, this.biomeType, globalScene.offsetGym) || isBoss); console.log(isBoss, this.trainerPool); - const tierValue = Utils.randSeedInt(!isTrainerBoss ? 512 : 64); + const tierValue = randSeedInt(!isTrainerBoss ? 512 : 64); let tier = !isTrainerBoss ? tierValue >= 156 ? BiomePoolTier.COMMON @@ -235,7 +234,7 @@ export class Arena { tier--; } const tierPool = this.trainerPool[tier] || []; - return !tierPool.length ? TrainerType.BREEDER : tierPool[Utils.randSeedInt(tierPool.length)]; + return !tierPool.length ? TrainerType.BREEDER : tierPool[randSeedInt(tierPool.length)]; } getSpeciesFormIndex(species: PokemonSpecies): number { @@ -336,9 +335,9 @@ export class Arena { return false; } - const weatherDuration = new Utils.NumberHolder(0); + const weatherDuration = new NumberHolder(0); - if (!Utils.isNullOrUndefined(user)) { + if (!isNullOrUndefined(user)) { weatherDuration.value = 5; globalScene.applyModifier(FieldEffectModifier, user.isPlayer(), user, weatherDuration); } @@ -417,9 +416,9 @@ export class Arena { const oldTerrainType = this.terrain?.terrainType || TerrainType.NONE; - const terrainDuration = new Utils.NumberHolder(0); + const terrainDuration = new NumberHolder(0); - if (!Utils.isNullOrUndefined(user)) { + if (!isNullOrUndefined(user)) { terrainDuration.value = 5; globalScene.applyModifier(FieldEffectModifier, user.isPlayer(), user, terrainDuration); } @@ -1013,7 +1012,7 @@ export class ArenaBase extends Phaser.GameObjects.Container { if (!this.player) { globalScene.executeWithSeedOffset( () => { - this.propValue = propValue === undefined ? (hasProps ? Utils.randSeedInt(8) : 0) : propValue; + this.propValue = propValue === undefined ? (hasProps ? randSeedInt(8) : 0) : propValue; this.props.forEach((prop, p) => { const propKey = `${biomeKey}_b${hasProps ? `_${p + 1}` : ""}`; prop.setTexture(propKey); diff --git a/src/field/damage-number-handler.ts b/src/field/damage-number-handler.ts index 9e0010a0c10..a527b148fff 100644 --- a/src/field/damage-number-handler.ts +++ b/src/field/damage-number-handler.ts @@ -2,7 +2,7 @@ import { TextStyle, addTextObject } from "../ui/text"; import type { DamageResult } from "./pokemon"; import type Pokemon from "./pokemon"; import { HitResult } from "./pokemon"; -import * as Utils from "../utils"; +import { formatStat, fixedInt } from "#app/utils"; import type { BattlerIndex } from "../battle"; import { globalScene } from "#app/global-scene"; @@ -30,7 +30,7 @@ export default class DamageNumberHandler { const damageNumber = addTextObject( target.x, -(globalScene.game.canvas.height / 6) + target.y - target.getSprite().height / 2, - Utils.formatStat(amount, true), + formatStat(amount, true), TextStyle.SUMMARY, ); damageNumber.setName("text-damage-number"); @@ -86,14 +86,14 @@ export default class DamageNumberHandler { if (globalScene.damageNumbersMode === 1) { globalScene.tweens.add({ targets: damageNumber, - duration: Utils.fixedInt(750), + duration: fixedInt(750), alpha: 1, y: "-=32", }); globalScene.tweens.add({ delay: 375, targets: damageNumber, - duration: Utils.fixedInt(625), + duration: fixedInt(625), alpha: 0, ease: "Sine.easeIn", onComplete: () => { @@ -110,7 +110,7 @@ export default class DamageNumberHandler { targets: damageNumber, tweens: [ { - duration: Utils.fixedInt(250), + duration: fixedInt(250), alpha: 1, scaleX: 0.75 * baseScale, scaleY: 1.25 * baseScale, @@ -118,7 +118,7 @@ export default class DamageNumberHandler { ease: "Cubic.easeOut", }, { - duration: Utils.fixedInt(175), + duration: fixedInt(175), alpha: 1, scaleX: 0.875 * baseScale, scaleY: 1.125 * baseScale, @@ -126,59 +126,59 @@ export default class DamageNumberHandler { ease: "Cubic.easeIn", }, { - duration: Utils.fixedInt(100), + duration: fixedInt(100), scaleX: 1.25 * baseScale, scaleY: 0.75 * baseScale, ease: "Cubic.easeOut", }, { - duration: Utils.fixedInt(175), + duration: fixedInt(175), scaleX: 0.875 * baseScale, scaleY: 1.125 * baseScale, y: "-=8", ease: "Cubic.easeOut", }, { - duration: Utils.fixedInt(50), + duration: fixedInt(50), scaleX: 0.925 * baseScale, scaleY: 1.075 * baseScale, y: "+=8", ease: "Cubic.easeIn", }, { - duration: Utils.fixedInt(100), + duration: fixedInt(100), scaleX: 1.125 * baseScale, scaleY: 0.875 * baseScale, ease: "Cubic.easeOut", }, { - duration: Utils.fixedInt(175), + duration: fixedInt(175), scaleX: 0.925 * baseScale, scaleY: 1.075 * baseScale, y: "-=4", ease: "Cubic.easeOut", }, { - duration: Utils.fixedInt(50), + duration: fixedInt(50), scaleX: 0.975 * baseScale, scaleY: 1.025 * baseScale, y: "+=4", ease: "Cubic.easeIn", }, { - duration: Utils.fixedInt(100), + duration: fixedInt(100), scaleX: 1.075 * baseScale, scaleY: 0.925 * baseScale, ease: "Cubic.easeOut", }, { - duration: Utils.fixedInt(25), + duration: fixedInt(25), scaleX: baseScale, scaleY: baseScale, ease: "Cubic.easeOut", }, { - delay: Utils.fixedInt(500), + delay: fixedInt(500), alpha: 0, onComplete: () => { this.damageNumbers diff --git a/src/field/pokemon-sprite-sparkle-handler.ts b/src/field/pokemon-sprite-sparkle-handler.ts index 0d5dcca7989..d2f69500258 100644 --- a/src/field/pokemon-sprite-sparkle-handler.ts +++ b/src/field/pokemon-sprite-sparkle-handler.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; import Pokemon from "./pokemon"; -import * as Utils from "../utils"; +import { fixedInt, randInt } from "#app/utils"; export default class PokemonSpriteSparkleHandler { private sprites: Set; @@ -9,7 +9,7 @@ export default class PokemonSpriteSparkleHandler { this.sprites = new Set(); globalScene.tweens.addCounter({ - duration: Utils.fixedInt(200), + duration: fixedInt(200), from: 0, to: 1, yoyo: true, @@ -36,7 +36,7 @@ export default class PokemonSpriteSparkleHandler { const parent = (pokemon || s).parentContainer; const texture = s.texture; const [width, height] = [texture.source[0].width, texture.source[0].height]; - const [pixelX, pixelY] = [Utils.randInt(width), Utils.randInt(height)]; + const [pixelX, pixelY] = [randInt(width), randInt(height)]; const ratioX = s.width / width; const ratioY = s.height / height; const pixel = texture.manager.getPixel(pixelX, pixelY, texture.key, "__BASE"); @@ -51,7 +51,7 @@ export default class PokemonSpriteSparkleHandler { sparkle.setName("sprite-tera-sparkle"); sparkle.play("tera_sparkle"); parent.add(sparkle); - s.scene.time.delayedCall(Utils.fixedInt(Math.floor((1000 / 12) * 13)), () => sparkle.destroy()); + s.scene.time.delayedCall(fixedInt(Math.floor((1000 / 12) * 13)), () => sparkle.destroy()); } } } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 72da3f1ed6f..8fc75ca657d 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2,7 +2,7 @@ import Phaser from "phaser"; import type { AnySound } from "#app/battle-scene"; import type BattleScene from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; -import type { Variant, VariantSet } from "#app/sprites/variant"; +import type { Variant } from "#app/sprites/variant"; import { populateVariantColors, variantColorCache } from "#app/sprites/variant"; import { variantData } from "#app/sprites/variant"; import BattleInfo, { @@ -55,9 +55,7 @@ import { getStarterValueFriendshipCap, speciesStarterCosts, } from "#app/data/balance/starters"; -import type { Constructor } from "#app/utils"; -import { isNullOrUndefined, randSeedInt, type nil } from "#app/utils"; -import * as Utils from "#app/utils"; +import { NumberHolder, randSeedInt, getIvsFromId, BooleanHolder, randSeedItem, isNullOrUndefined, getEnumValues, toDmgValue, fixedInt, rgbaToInt, rgbHexToRgba, rgbToHsv, deltaRgb, isBetween, type nil, type Constructor } from "#app/utils"; import type { TypeDamageMultiplier } from "#app/data/type"; import { getTypeDamageMultiplier, getTypeRgb } from "#app/data/type"; import { PokemonType } from "#enums/pokemon-type"; @@ -96,7 +94,6 @@ import { } from "#app/modifier/modifier"; import { PokeballType } from "#enums/pokeball"; import { Gender } from "#app/data/gender"; -import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; import { Status, getRandomStatus } from "#app/data/status-effect"; import type { SpeciesFormEvolution, @@ -176,10 +173,7 @@ import { MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, - CheckTrappedAbAttr, - PostSetStatusAbAttr, - applyPostSetStatusAbAttrs, - InfiltratorAbAttr, + CheckTrappedAbAttr, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, @@ -193,7 +187,7 @@ import { PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, applyAllyStatMultiplierAbAttrs, AllyStatMultiplierAbAttr, - MoveAbilityBypassAbAttr, + MoveAbilityBypassAbAttr } from "#app/data/ability"; import type PokemonData from "#app/system/pokemon-data"; import { BattlerIndex } from "#app/battle"; @@ -220,8 +214,7 @@ import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeMoveLearnedTrigger, - SpeciesFormChangePostMoveTrigger, - SpeciesFormChangeStatusEffectTrigger, + SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms"; import { TerrainType } from "#app/data/terrain"; import type { TrainerSlot } from "#enums/trainer-slot"; @@ -263,7 +256,6 @@ import { Nature } from "#enums/nature"; import { StatusEffect } from "#enums/status-effect"; import { doShinySparkleAnim } from "#app/field/anims"; import { MoveFlags } from "#enums/MoveFlags"; -import { hasExpSprite } from "#app/sprites/sprite-utils"; import { timedEventManager } from "#app/global-event-manager"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; import { ResetStatusPhase } from "#app/phases/reset-status-phase"; @@ -369,7 +361,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { throw `Cannot create a player Pokemon for species '${species.getName(formIndex)}'`; } - const hiddenAbilityChance = new Utils.NumberHolder( + const hiddenAbilityChance = new NumberHolder( BASE_HIDDEN_ABILITY_CHANCE, ); if (!this.hasTrainer()) { @@ -390,8 +382,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.abilityIndex = abilityIndex; // Use the provided ability index if it is defined } else { // If abilityIndex is not provided, determine it based on species and hidden ability - const hasHiddenAbility = !Utils.randSeedInt(hiddenAbilityChance.value); - const randAbilityIndex = Utils.randSeedInt(2); + const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value); + const randAbilityIndex = randSeedInt(2); if (species.abilityHidden && hasHiddenAbility) { // If the species has a hidden ability and the hidden ability is present this.abilityIndex = 2; @@ -467,8 +459,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.isTerastallized = dataSource.isTerastallized; this.stellarTypesBoosted = dataSource.stellarTypesBoosted ?? []; } else { - this.id = Utils.randSeedInt(4294967296); - this.ivs = ivs || Utils.getIvsFromId(this.id); + this.id = randSeedInt(4294967296); + this.ivs = ivs || getIvsFromId(this.id); if (this.gender === undefined) { this.generateGender(); @@ -511,7 +503,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.pokerus = false; if (level > 1) { - const fused = new Utils.BooleanHolder( + const fused = new BooleanHolder( globalScene.gameMode.isSplicedOnly, ); if (!fused.value && !this.isPlayer() && !this.hasTrainer()) { @@ -528,7 +520,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { (this.fusionShiny ? this.fusionVariant + 1 : 0); this.fusionLuck = this.luck; - this.teraType = Utils.randSeedItem(this.getTypes(false, false, true)); + this.teraType = randSeedItem(this.getTypes(false, false, true)); this.isTerastallized = false; this.stellarTypesBoosted = []; } @@ -636,7 +628,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns {boolean} `true` if pokemon is allowed in battle */ public isAllowedInChallenge(): boolean { - const challengeAllowed = new Utils.BooleanHolder(true); + const challengeAllowed = new BooleanHolder(true); applyChallenges( ChallengeType.POKEMON_IN_BATTLE, this, @@ -1315,7 +1307,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns the final critical-hit stage value */ getCritStage(source: Pokemon, move: Move): number { - const critStage = new Utils.NumberHolder(0); + const critStage = new NumberHolder(0); applyMoveAttrs(HighCritAttr, source, this, move, critStage); globalScene.applyModifiers( CritBoosterModifier, @@ -1370,7 +1362,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { simulated = true, ignoreHeldItems = false, ): number { - const statValue = new Utils.NumberHolder(this.getStat(stat, false)); + const statValue = new NumberHolder(this.getStat(stat, false)); if (!ignoreHeldItems) { globalScene.applyModifiers( StatBoosterModifier, @@ -1382,7 +1374,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } // The Ruin abilities here are never ignored, but they reveal themselves on summon anyway - const fieldApplied = new Utils.BooleanHolder(false); + const fieldApplied = new BooleanHolder(false); for (const pokemon of globalScene.getField(true)) { applyFieldStatMultiplierAbAttrs( FieldMultiplyStatAbAttr, @@ -1408,7 +1400,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } const ally = this.getAlly(); - if (!Utils.isNullOrUndefined(ally)) { + if (!isNullOrUndefined(ally)) { applyAllyStatMultiplierAbAttrs(AllyStatMultiplierAbAttr, ally, stat, statValue, simulated, this, move?.hasFlag(MoveFlags.IGNORE_ABILITIES) || ignoreAllyAbility); } @@ -1495,7 +1487,7 @@ 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 Utils.NumberHolder( + const statHolder = new NumberHolder( Math.floor((2 * baseStats[s] + this.ivs[s]) * this.level * 0.01), ); if (s === Stat.HP) { @@ -1520,7 +1512,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } else { statHolder.value += 5; - const natureStatMultiplier = new Utils.NumberHolder( + const natureStatMultiplier = new NumberHolder( getNatureStatMultiplier(this.getNature(), s), ); globalScene.applyModifier( @@ -1622,9 +1614,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { generateNature(naturePool?: Nature[]): void { if (naturePool === undefined) { - naturePool = Utils.getEnumValues(Nature); + naturePool = getEnumValues(Nature); } - const nature = naturePool[Utils.randSeedInt(naturePool.length)]; + const nature = naturePool[randSeedInt(naturePool.length)]; this.setNature(nature); } @@ -1708,7 +1700,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns `true` if the pokemon is the species or is fused with it, `false` otherwise */ hasSpecies(species: Species, formKey?: string): boolean { - if (Utils.isNullOrUndefined(formKey)) { + if (isNullOrUndefined(formKey)) { return ( this.species.speciesId === species || this.fusionSpecies?.speciesId === species @@ -1870,7 +1862,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if ( secondType === PokemonType.UNKNOWN && - Utils.isNullOrUndefined(fusionType2) + isNullOrUndefined(fusionType2) ) { // If second pokemon was monotype and shared its primary type secondType = @@ -2240,11 +2232,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public getWeight(): number { const autotomizedTag = this.getTag(AutotomizedTag); let weightRemoved = 0; - if (!Utils.isNullOrUndefined(autotomizedTag)) { + if (!isNullOrUndefined(autotomizedTag)) { weightRemoved = 100 * autotomizedTag!.autotomizeCount; } const minWeight = 0.1; - const weight = new Utils.NumberHolder(this.species.weight - weightRemoved); + const weight = new NumberHolder(this.species.weight - weightRemoved); // This will trigger the ability overlay so only call this function when necessary applyAbAttrs(WeightMultiplierAbAttr, this, null, false, weight); @@ -2315,7 +2307,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return false; } - const trappedByAbility = new Utils.BooleanHolder(false); + const trappedByAbility = new BooleanHolder(false); /** * 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 @@ -2354,7 +2346,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns The {@linkcode PokemonType} of the move after attributes are applied */ public getMoveType(move: Move, simulated = true): PokemonType { - const moveTypeHolder = new Utils.NumberHolder(move.type); + const moveTypeHolder = new NumberHolder(move.type); applyMoveAttrs(VariableMoveTypeAttr, this, null, move, moveTypeHolder); applyPreAttackAbAttrs( @@ -2385,7 +2377,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param move {@linkcode Move} The move being used by the attacking Pokémon. * @param ignoreAbility Whether to ignore abilities that might affect type effectiveness or immunity (defaults to `false`). * @param simulated Whether to apply abilities via simulated calls (defaults to `true`) - * @param cancelled {@linkcode Utils.BooleanHolder} Stores whether the move was cancelled by a non-type-based immunity. + * @param cancelled {@linkcode BooleanHolder} Stores whether the move was cancelled by a non-type-based immunity. * Currently only used by {@linkcode Pokemon.apply} to determine whether a "No effect" message should be shown. * @returns The type damage multiplier, indicating the effectiveness of the move */ @@ -2394,9 +2386,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { move: Move, ignoreAbility = false, simulated = true, - cancelled?: Utils.BooleanHolder, + cancelled?: BooleanHolder, ): TypeDamageMultiplier { - if (!Utils.isNullOrUndefined(this.turnData?.moveEffectiveness)) { + if (!isNullOrUndefined(this.turnData?.moveEffectiveness)) { return this.turnData?.moveEffectiveness; } @@ -2405,7 +2397,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } const moveType = source.getMoveType(move); - const typeMultiplier = new Utils.NumberHolder( + const typeMultiplier = new NumberHolder( move.category !== MoveCategory.STATUS || move.hasAttr(RespectAttackTypeImmunityAttr) ? this.getAttackTypeEffectiveness( @@ -2435,7 +2427,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { typeMultiplier.value *= 2; } - const cancelledHolder = cancelled ?? new Utils.BooleanHolder(false); + const cancelledHolder = cancelled ?? new BooleanHolder(false); if (!ignoreAbility) { applyPreDefendAbAttrs( TypeImmunityAbAttr, @@ -2549,7 +2541,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let multiplier = types .map(defType => { - const multiplier = new Utils.NumberHolder( + const multiplier = new NumberHolder( getTypeDamageMultiplier(moveType, defType), ); applyChallenges( @@ -2567,7 +2559,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ); } if (source) { - const ignoreImmunity = new Utils.BooleanHolder(false); + const ignoreImmunity = new BooleanHolder(false); if ( source.isActive(true) && source.hasAbilityWithAttr(IgnoreTypeImmunityAbAttr) @@ -2600,7 +2592,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }) .reduce((acc, cur) => acc * cur, 1) as TypeDamageMultiplier; - const typeMultiplierAgainstFlying = new Utils.NumberHolder( + const typeMultiplierAgainstFlying = new NumberHolder( getTypeDamageMultiplier(moveType, PokemonType.FLYING), ); applyChallenges( @@ -2943,7 +2935,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const E = globalScene.gameData.trainerId ^ globalScene.gameData.secretId; const F = rand1 ^ rand2; - const shinyThreshold = new Utils.NumberHolder(BASE_SHINY_CHANCE); + const shinyThreshold = new NumberHolder(BASE_SHINY_CHANCE); if (thresholdOverride === undefined) { if (timedEventManager.isEventActive()) { const tchance = timedEventManager.getClassicTrainerShinyChance(); @@ -2986,7 +2978,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { thresholdOverride?: number, applyModifiersToOverride?: boolean, ): boolean { - const shinyThreshold = new Utils.NumberHolder(BASE_SHINY_CHANCE); + const shinyThreshold = new NumberHolder(BASE_SHINY_CHANCE); if (thresholdOverride === undefined || applyModifiersToOverride) { if (thresholdOverride !== undefined && applyModifiersToOverride) { shinyThreshold.value = thresholdOverride; @@ -3041,10 +3033,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ) { return 0; } - const rand = new Utils.NumberHolder(0); + const rand = new NumberHolder(0); globalScene.executeWithSeedOffset( () => { - rand.value = Utils.randSeedInt(10); + rand.value = randSeedInt(10); }, this.id, globalScene.waveSeed, @@ -3074,7 +3066,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!this.species.abilityHidden) { return false; } - const haThreshold = new Utils.NumberHolder(BASE_HIDDEN_ABILITY_CHANCE); + const haThreshold = new NumberHolder(BASE_HIDDEN_ABILITY_CHANCE); if (thresholdOverride === undefined || applyModifiersToOverride) { if (thresholdOverride !== undefined && applyModifiersToOverride) { haThreshold.value = thresholdOverride; @@ -3098,7 +3090,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } public generateFusionSpecies(forStarter?: boolean): void { - const hiddenAbilityChance = new Utils.NumberHolder( + const hiddenAbilityChance = new NumberHolder( BASE_HIDDEN_ABILITY_CHANCE, ); if (!this.hasTrainer()) { @@ -3109,8 +3101,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ); } - const hasHiddenAbility = !Utils.randSeedInt(hiddenAbilityChance.value); - const randAbilityIndex = Utils.randSeedInt(2); + const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value); + const randAbilityIndex = randSeedInt(2); const filter = !forStarter ? this.species.getCompatibleFusionSpeciesFilter() @@ -3427,7 +3419,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (stabMovePool.length) { const totalWeight = stabMovePool.reduce((v, m) => v + m[1], 0); - let rand = Utils.randSeedInt(totalWeight); + let rand = randSeedInt(totalWeight); let index = 0; while (rand > stabMovePool[index][1]) { rand -= stabMovePool[index++][1]; @@ -3441,7 +3433,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ); if (attackMovePool.length) { const totalWeight = attackMovePool.reduce((v, m) => v + m[1], 0); - let rand = Utils.randSeedInt(totalWeight); + let rand = randSeedInt(totalWeight); let index = 0; while (rand > attackMovePool[index][1]) { rand -= attackMovePool[index++][1]; @@ -3493,7 +3485,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { movePool = baseWeights.filter(m => !this.moveset.some(mo => m[0] === mo.moveId)); } const totalWeight = movePool.reduce((v, m) => v + m[1], 0); - let rand = Utils.randSeedInt(totalWeight); + let rand = randSeedInt(totalWeight); let index = 0; while (rand > movePool[index][1]) { rand -= movePool[index++][1]; @@ -3717,8 +3709,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { simulated = true, ignoreHeldItems = false, ): number { - const statStage = new Utils.NumberHolder(this.getStatStage(stat)); - const ignoreStatStage = new Utils.BooleanHolder(false); + const statStage = new NumberHolder(this.getStatStage(stat)); + const ignoreStatStage = new BooleanHolder(false); if (opponent) { if (isCritical) { @@ -3755,7 +3747,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (!ignoreStatStage.value) { - const statStageMultiplier = new Utils.NumberHolder( + const statStageMultiplier = new NumberHolder( Math.max(2, 2 + statStage.value) / Math.max(2, 2 - statStage.value), ); if (!ignoreHeldItems) { @@ -3787,13 +3779,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return 1; } - const userAccStage = new Utils.NumberHolder(this.getStatStage(Stat.ACC)); - const targetEvaStage = new Utils.NumberHolder( + const userAccStage = new NumberHolder(this.getStatStage(Stat.ACC)); + const targetEvaStage = new NumberHolder( target.getStatStage(Stat.EVA), ); - const ignoreAccStatStage = new Utils.BooleanHolder(false); - const ignoreEvaStatStage = new Utils.BooleanHolder(false); + const ignoreAccStatStage = new BooleanHolder(false); + const ignoreEvaStatStage = new BooleanHolder(false); applyAbAttrs( IgnoreOpponentStatStagesAbAttr, @@ -3835,7 +3827,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { targetEvaStage.value = Math.min(0, targetEvaStage.value); } - const accuracyMultiplier = new Utils.NumberHolder(1); + const accuracyMultiplier = new NumberHolder(1); if (userAccStage.value !== targetEvaStage.value) { accuracyMultiplier.value = userAccStage.value > targetEvaStage.value @@ -3852,7 +3844,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { sourceMove, ); - const evasionMultiplier = new Utils.NumberHolder(1); + const evasionMultiplier = new NumberHolder(1); applyStatMultiplierAbAttrs( StatMultiplierAbAttr, target, @@ -3907,7 +3899,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * The attacker's offensive stat for the given move's category. * Critical hits cause negative stat stages to be ignored. */ - const sourceAtk = new Utils.NumberHolder( + const sourceAtk = new NumberHolder( source.getEffectiveStat( isPhysical ? Stat.ATK : Stat.SPATK, this, @@ -3925,7 +3917,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * This Pokemon's defensive stat for the given move's category. * Critical hits cause positive stat stages to be ignored. */ - const targetDef = new Utils.NumberHolder( + const targetDef = new NumberHolder( this.getEffectiveStat( isPhysical ? Stat.DEF : Stat.SPDEF, source, @@ -3986,12 +3978,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { isCritical = false, simulated = true, ): DamageCalculationResult { - const damage = new Utils.NumberHolder(0); + const damage = new NumberHolder(0); const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - const variableCategory = new Utils.NumberHolder(move.category); + const variableCategory = new NumberHolder(move.category); applyMoveAttrs( VariableMoveCategoryAttr, source, @@ -4005,7 +3997,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const moveType = source.getMoveType(move); /** If `value` is `true`, cancels the move and suppresses "No Effect" messages */ - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); /** * The effectiveness of the move being used. Along with type matchups, this @@ -4025,7 +4017,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const isPhysical = moveCategory === MoveCategory.PHYSICAL; /** Combined damage multiplier from field effects such as weather, terrain, etc. */ - const arenaAttackTypeMultiplier = new Utils.NumberHolder( + const arenaAttackTypeMultiplier = new NumberHolder( globalScene.arena.getAttackTypeMultiplier(moveType, source.isGrounded()), ); applyMoveAttrs( @@ -4048,10 +4040,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } // If the attack deals fixed damage, return a result with that much damage - const fixedDamage = new Utils.NumberHolder(0); + const fixedDamage = new NumberHolder(0); applyMoveAttrs(FixedDamageAttr, source, this, move, fixedDamage); if (fixedDamage.value) { - const multiLensMultiplier = new Utils.NumberHolder(1); + const multiLensMultiplier = new NumberHolder(1); globalScene.applyModifiers( PokemonMultiHitModifier, source.isPlayer(), @@ -4060,7 +4052,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { null, multiLensMultiplier, ); - fixedDamage.value = Utils.toDmgValue( + fixedDamage.value = toDmgValue( fixedDamage.value * multiLensMultiplier.value, ); @@ -4072,7 +4064,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } // If the attack is a one-hit KO move, return a result with damage equal to this Pokemon's HP - const isOneHitKo = new Utils.BooleanHolder(false); + const isOneHitKo = new BooleanHolder(false); applyMoveAttrs(OneHitKOAttr, source, this, move, isOneHitKo); if (isOneHitKo.value) { return { @@ -4104,7 +4096,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const targetMultiplier = numTargets > 1 ? 0.75 : 1; /** Multiplier for moves enhanced by Multi-Lens and/or Parental Bond */ - const multiStrikeEnhancementMultiplier = new Utils.NumberHolder(1); + const multiStrikeEnhancementMultiplier = new NumberHolder(1); globalScene.applyModifiers( PokemonMultiHitModifier, source.isPlayer(), @@ -4126,13 +4118,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** Doubles damage if this Pokemon's last move was Glaive Rush */ - const glaiveRushMultiplier = new Utils.NumberHolder(1); + const glaiveRushMultiplier = new NumberHolder(1); if (this.getTag(BattlerTagType.RECEIVE_DOUBLE_DAMAGE)) { glaiveRushMultiplier.value = 2; } /** The damage multiplier when the given move critically hits */ - const criticalMultiplier = new Utils.NumberHolder(isCritical ? 1.5 : 1); + const criticalMultiplier = new NumberHolder(isCritical ? 1.5 : 1); applyAbAttrs(MultCritAbAttr, source, null, simulated, criticalMultiplier); /** @@ -4147,7 +4139,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const sourceTeraType = source.getTeraType(); const matchesSourceType = sourceTypes.includes(moveType); /** A damage multiplier for when the attack is of the attacker's type and/or Tera type. */ - const stabMultiplier = new Utils.NumberHolder(1); + const stabMultiplier = new NumberHolder(1); if (matchesSourceType && moveType !== PokemonType.STELLAR) { stabMultiplier.value += 0.5; } @@ -4188,14 +4180,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { stabMultiplier.value = Math.min(stabMultiplier.value, 2.25); /** Halves damage if the attacker is using a physical attack while burned */ - const burnMultiplier = new Utils.NumberHolder(1); + const burnMultiplier = new NumberHolder(1); if ( isPhysical && source.status && source.status.effect === StatusEffect.BURN ) { if (!move.hasAttr(BypassBurnDamageReductionAttr)) { - const burnDamageReductionCancelled = new Utils.BooleanHolder(false); + const burnDamageReductionCancelled = new BooleanHolder(false); if (!ignoreSourceAbility) { applyAbAttrs( BypassBurnDamageReductionAbAttr, @@ -4211,7 +4203,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** Reduces damage if this Pokemon has a relevant screen (e.g. Light Screen for special attacks) */ - const screenMultiplier = new Utils.NumberHolder(1); + const screenMultiplier = new NumberHolder(1); // Critical hits should bypass screens if (!isCritical) { @@ -4231,7 +4223,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * AND * The move doubles damage when used against that tag */ - const hitsTagMultiplier = new Utils.NumberHolder(1); + const hitsTagMultiplier = new NumberHolder(1); move .getAttrs(HitsTagAttr) .filter(hta => hta.doubleDamage) @@ -4249,7 +4241,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ? 0.5 : 1; - damage.value = Utils.toDmgValue( + damage.value = toDmgValue( baseDamage * targetMultiplier * multiStrikeEnhancementMultiplier.value * @@ -4358,10 +4350,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - const moveCategory = new Utils.NumberHolder(move.category); + const moveCategory = new NumberHolder(move.category); applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, moveCategory); if (moveCategory.value === MoveCategory.STATUS) { - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); const typeMultiplier = this.getMoveEffectiveness( source, move, @@ -4381,7 +4373,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** Determines whether the attack critically hits */ let isCritical: boolean; - const critOnly = new Utils.BooleanHolder(false); + const critOnly = new BooleanHolder(false); const critAlways = source.getTag(BattlerTagType.ALWAYS_CRIT); applyMoveAttrs(CritOnlyAttr, source, this, move, critOnly); applyAbAttrs( @@ -4404,7 +4396,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide); - const blockCrit = new Utils.BooleanHolder(false); + const blockCrit = new BooleanHolder(false); applyAbAttrs(BlockCritAbAttr, this, null, false, blockCrit); if (noCritTag || blockCrit.value || Overrides.NEVER_CRIT_OVERRIDE) { isCritical = false; @@ -4486,7 +4478,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (damage > 0) { if (source.isPlayer()) { - globalScene.validateAchvs(DamageAchv, new Utils.NumberHolder(damage)); + globalScene.validateAchvs(DamageAchv, new NumberHolder(damage)); if (damage > globalScene.gameData.gameStats.highestDamage) { globalScene.gameData.gameStats.highestDamage = damage; } @@ -4510,7 +4502,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { DamageMoneyRewardModifier, true, source, - new Utils.NumberHolder(damage), + new NumberHolder(damage), ); } } @@ -4575,7 +4567,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.isFainted()) { return 0; } - const surviveDamage = new Utils.BooleanHolder(false); + const surviveDamage = new BooleanHolder(false); if (!preventEndure && this.hp - damage <= 0) { if (this.hp >= 1 && this.getTag(BattlerTagType.ENDURING)) { @@ -4710,7 +4702,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const stubTag = new BattlerTag(tagType, 0, 0); - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyPreApplyBattlerTagAbAttrs( BattlerTagImmunityAbAttr, this, @@ -4748,7 +4740,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const newTag = getBattlerTag(tagType, turnCount, sourceMove!, sourceId!); // TODO: are the bangs correct? - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyPreApplyBattlerTagAbAttrs( BattlerTagImmunityAbAttr, this, @@ -5081,12 +5073,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let fusionCry = this.getFusionSpeciesForm().cry(soundConfig, true); duration = Math.min(duration, fusionCry.totalDuration * 1000); fusionCry.destroy(); - scene.time.delayedCall(Utils.fixedInt(Math.ceil(duration * 0.4)), () => { + scene.time.delayedCall(fixedInt(Math.ceil(duration * 0.4)), () => { try { SoundFade.fadeOut( scene, cry, - Utils.fixedInt(Math.ceil(duration * 0.2)), + fixedInt(Math.ceil(duration * 0.2)), ); fusionCry = this.getFusionSpeciesForm().cry( Object.assign( @@ -5097,7 +5089,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { SoundFade.fadeIn( scene, fusionCry, - Utils.fixedInt(Math.ceil(duration * 0.2)), + fixedInt(Math.ceil(duration * 0.2)), scene.masterVolume * scene.fieldVolume, 0, ); @@ -5137,7 +5129,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let faintCryTimer: Phaser.Time.TimerEvent | null = globalScene.time.addEvent({ - delay: Utils.fixedInt(delay), + delay: fixedInt(delay), repeat: -1, callback: () => { frameThreshold = sprite.anims.msPerFrame / rate; @@ -5163,7 +5155,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }); // Failsafe - globalScene.time.delayedCall(Utils.fixedInt(3000), () => { + globalScene.time.delayedCall(fixedInt(3000), () => { if (!faintCryTimer || !globalScene) { return; } @@ -5222,7 +5214,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let faintCryTimer: Phaser.Time.TimerEvent | null = globalScene.time.addEvent({ - delay: Utils.fixedInt(delay), + delay: fixedInt(delay), repeat: -1, callback: () => { ++i; @@ -5239,7 +5231,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { SoundFade.fadeOut( globalScene, cry, - Utils.fixedInt(Math.ceil((duration / rate) * 0.2)), + fixedInt(Math.ceil((duration / rate) * 0.2)), ); fusionCry = globalScene.playSound( fusionCryKey, @@ -5251,7 +5243,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { SoundFade.fadeIn( globalScene, fusionCry, - Utils.fixedInt(Math.ceil((duration / rate) * 0.2)), + fixedInt(Math.ceil((duration / rate) * 0.2)), globalScene.masterVolume * globalScene.fieldVolume, 0, ); @@ -5277,7 +5269,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }); // Failsafe - globalScene.time.delayedCall(Utils.fixedInt(3000), () => { + globalScene.time.delayedCall(fixedInt(3000), () => { if (!faintCryTimer || !globalScene) { return; } @@ -5352,7 +5344,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } // Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity - const cancelImmunity = new Utils.BooleanHolder(false); + const cancelImmunity = new BooleanHolder(false); if (sourcePokemon) { applyAbAttrs( IgnoreTypeStatusEffectImmunityAbAttr, @@ -5408,7 +5400,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { break; } - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyPreSetStatusAbAttrs( StatusEffectImmunityAbAttr, this, @@ -5479,10 +5471,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return true; } - let sleepTurnsRemaining: Utils.NumberHolder; + let sleepTurnsRemaining: NumberHolder; if (effect === StatusEffect.SLEEP) { - sleepTurnsRemaining = new Utils.NumberHolder(this.randSeedIntRange(2, 4)); + sleepTurnsRemaining = new NumberHolder(this.randSeedIntRange(2, 4)); this.setFrameRate(4); @@ -5533,7 +5525,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (globalScene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) { - const bypassed = new Utils.BooleanHolder(false); + const bypassed = new BooleanHolder(false); if (attacker) { applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed); } @@ -5829,9 +5821,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.shiny && variantColors && variantColors[this.variant]) { Object.keys(variantColors[this.variant]).forEach(k => { variantColorSet.set( - Utils.rgbaToInt(Array.from(Object.values(Utils.rgbHexToRgba(k)))), + rgbaToInt(Array.from(Object.values(rgbHexToRgba(k)))), Array.from( - Object.values(Utils.rgbHexToRgba(variantColors[this.variant][k])), + Object.values(rgbHexToRgba(variantColors[this.variant][k])), ), ); }); @@ -5842,7 +5834,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const pixel = pixelData[f].slice(i, i + 4); let [r, g, b, a] = pixel; if (variantColors) { - const color = Utils.rgbaToInt([r, g, b, a]); + const color = rgbaToInt([r, g, b, a]); if (variantColorSet.has(color)) { const mappedPixel = variantColorSet.get(color); if (mappedPixel) { @@ -5891,10 +5883,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ) { for (const k of Object.keys(variantColors[this.fusionVariant])) { variantColorSet.set( - Utils.rgbaToInt(Array.from(Object.values(Utils.rgbHexToRgba(k)))), + rgbaToInt(Array.from(Object.values(rgbHexToRgba(k)))), Array.from( Object.values( - Utils.rgbHexToRgba(variantColors[this.fusionVariant][k]), + rgbHexToRgba(variantColors[this.fusionVariant][k]), ), ), ); @@ -5914,7 +5906,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { pixelData[2 + f][i + 3], ]; if (variantColors) { - const color = Utils.rgbaToInt([r, g, b, a]); + const color = rgbaToInt([r, g, b, a]); if (variantColorSet.has(color)) { const mappedPixel = variantColorSet.get(color); if (mappedPixel) { @@ -5972,7 +5964,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { hsvColors = Array.from(rgbaColors.keys()).reduce( (map: Map, k: number) => { const rgb = rgbaColors.get(k)!.slice(0, 3); - map.set(k, Utils.rgbToHsv(rgb[0], rgb[1], rgb[2])); + map.set(k, rgbToHsv(rgb[0], rgb[1], rgb[2])); return map; }, new Map(), @@ -6052,7 +6044,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { spriteColors.forEach((sc: number[], i: number) => { paletteDeltas.push([]); for (let p = 0; p < palette.length; p++) { - paletteDeltas[i].push(Utils.deltaRgb(sc, palette[p])); + paletteDeltas[i].push(deltaRgb(sc, palette[p])); } }); @@ -6097,8 +6089,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * * This calls either {@linkcode BattleScene.randBattleSeedInt}({@linkcode range}, {@linkcode min}) in `src/battle-scene.ts` * which calls {@linkcode Battle.randSeedInt}({@linkcode range}, {@linkcode min}) in `src/battle.ts` - * which calls {@linkcode Utils.randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts`, - * or it directly calls {@linkcode Utils.randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts` if there is no current battle + * which calls {@linkcode randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts`, + * or it directly calls {@linkcode randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts` if there is no current battle * * @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min} * @param min The minimum integer to pick, default `0` @@ -6107,7 +6099,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { randSeedInt(range: number, min = 0): number { return globalScene.currentBattle ? globalScene.randBattleSeedInt(range, min) - : Utils.randSeedInt(range, min); + : randSeedInt(range, min); } /** @@ -6403,7 +6395,7 @@ export class PlayerPokemon extends Pokemon { ? globalScene.gameData.starterData[fusionStarterSpeciesId] : null, ].filter(d => !!d); - const amount = new Utils.NumberHolder(friendship); + const amount = new NumberHolder(friendship); globalScene.applyModifier( PokemonFriendshipBoosterModifier, true, @@ -6418,7 +6410,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 Utils.NumberHolder( + const starterAmount = new NumberHolder( Math.floor( (amount.value * candyFriendshipMultiplier) / fusionReduction, ), @@ -7381,7 +7373,7 @@ export class EnemyPokemon extends Pokemon { //console.log('damage', damage, 'segment', segmentsBypassed + 1, 'segment size', segmentSize, 'damage needed', Math.round(segmentSize * Math.pow(2, segmentsBypassed + 1))); } - damage = Utils.toDmgValue( + damage = toDmgValue( this.hp - hpThreshold + segmentSize * segmentsBypassed, ); clearedBossSegmentIndex = s - segmentsBypassed; @@ -7459,7 +7451,7 @@ export class EnemyPokemon extends Pokemon { } // Pick a random stat from the leftover stats to increase its stages - const randInt = Utils.randSeedInt(totalWeight); + const randInt = randSeedInt(totalWeight); for (const i in statThresholds) { if (randInt < statThresholds[i]) { boostedStat = leftoverStats[i]; @@ -7530,7 +7522,7 @@ export class EnemyPokemon extends Pokemon { this, ); - if (Utils.isBetween(slotIndex, 0, PLAYER_PARTY_MAX_SIZE - 1)) { + if (isBetween(slotIndex, 0, PLAYER_PARTY_MAX_SIZE - 1)) { party.splice(slotIndex, 0, newPokemon); } else { party.push(newPokemon); @@ -7772,7 +7764,7 @@ export class PokemonMove { getMovePp(): number { return ( this.maxPpOverride || - this.getMove().pp + this.ppUp * Utils.toDmgValue(this.getMove().pp / 5) + this.getMove().pp + this.ppUp * toDmgValue(this.getMove().pp / 5) ); } diff --git a/src/field/trainer.ts b/src/field/trainer.ts index ccd8c83e684..30cf43b54a1 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -11,7 +11,7 @@ import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerPoolTier } from "#enums/trainer-pool-tier"; import { TeraAIMode } from "#enums/tera-ai-mode"; import type { EnemyPokemon } from "#app/field/pokemon"; -import * as Utils from "#app/utils"; +import { randSeedWeightedItem, randSeedItem, randSeedInt } from "#app/utils"; import type { PersistentModifier } from "#app/modifier/modifier"; import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; import { getIsInitialized, initI18n } from "#app/plugins/i18n"; @@ -58,7 +58,7 @@ export default class Trainer extends Phaser.GameObjects.Container { this.partyTemplateIndex = Math.min( partyTemplateIndex !== undefined ? partyTemplateIndex - : Utils.randSeedWeightedItem(this.config.partyTemplates.map((_, i) => i)), + : randSeedWeightedItem(this.config.partyTemplates.map((_, i) => i)), this.config.partyTemplates.length - 1, ); const classKey = `trainersCommon:${TrainerType[trainerType]}`; @@ -71,9 +71,7 @@ export default class Trainer extends Phaser.GameObjects.Container { ? ".FEMALE" : ".MALE" : ""; - const trainerKey = Utils.randSeedItem( - Object.keys(i18next.t(`${classKey}${genderKey}`, { returnObjects: true })), - ); + const trainerKey = randSeedItem(Object.keys(i18next.t(`${classKey}${genderKey}`, { returnObjects: true }))); this.nameKey = `${classKey}${genderKey}.${trainerKey}`; } this.name = i18next.t(this.nameKey); @@ -87,7 +85,7 @@ export default class Trainer extends Phaser.GameObjects.Container { } } else { const partnerGenderKey = i18next.exists(`${classKey}.FEMALE`) ? ".FEMALE" : ""; - const partnerTrainerKey = Utils.randSeedItem( + const partnerTrainerKey = randSeedItem( Object.keys( i18next.t(`${classKey}${partnerGenderKey}`, { returnObjects: true, @@ -420,7 +418,7 @@ export default class Trainer extends Phaser.GameObjects.Container { // If useNewSpeciesPool is true, we need to generate a new species from the new species pool, otherwise we generate a random species let species = useNewSpeciesPool - ? getPokemonSpecies(newSpeciesPool[Math.floor(Utils.randSeedInt(newSpeciesPool.length))]) + ? getPokemonSpecies(newSpeciesPool[Math.floor(randSeedInt(newSpeciesPool.length))]) : template.isSameSpecies(index) && index > offset ? getPokemonSpecies( battle.enemyParty[offset].species.getTrainerSpeciesForLevel( @@ -461,7 +459,7 @@ export default class Trainer extends Phaser.GameObjects.Container { let baseSpecies: PokemonSpecies; if (this.config.speciesPools) { - const tierValue = Utils.randSeedInt(512); + const tierValue = randSeedInt(512); let tier = tierValue >= 156 ? TrainerPoolTier.COMMON @@ -480,7 +478,7 @@ export default class Trainer extends Phaser.GameObjects.Container { tier--; } const tierPool = this.config.speciesPools[tier]; - baseSpecies = getPokemonSpecies(Utils.randSeedItem(tierPool)); + baseSpecies = getPokemonSpecies(randSeedItem(tierPool)); } else { baseSpecies = globalScene.randomSpecies(battle.waveIndex, level, false, this.config.speciesFilter); } @@ -619,7 +617,7 @@ export default class Trainer extends Phaser.GameObjects.Container { if (maxScorePartyMemberIndexes.length > 1) { let rand: number; globalScene.executeWithSeedOffset( - () => (rand = Utils.randSeedInt(maxScorePartyMemberIndexes.length)), + () => (rand = randSeedInt(maxScorePartyMemberIndexes.length)), globalScene.currentBattle.turn << 2, ); return maxScorePartyMemberIndexes[rand!]; diff --git a/src/game-mode.ts b/src/game-mode.ts index c340768ef77..4779fda50e8 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -7,7 +7,7 @@ import type PokemonSpecies from "./data/pokemon-species"; import { allSpecies } from "./data/pokemon-species"; import type { Arena } from "./field/arena"; import Overrides from "#app/overrides"; -import * as Utils from "./utils"; +import { randSeedInt, randSeedItem } from "#app/utils"; import { Biome } from "#enums/biome"; import { Species } from "#enums/species"; import { Challenges } from "./enums/challenges"; @@ -186,7 +186,7 @@ export class GameMode implements GameModeConfig { if (w < waveIndex) { globalScene.executeWithSeedOffset(() => { const waveTrainerChance = arena.getTrainerChance(); - if (!Utils.randSeedInt(waveTrainerChance)) { + if (!randSeedInt(waveTrainerChance)) { allowTrainerBattle = false; } }, w); @@ -196,7 +196,7 @@ export class GameMode implements GameModeConfig { } } } - return Boolean(allowTrainerBattle && trainerChance && !Utils.randSeedInt(trainerChance)); + return Boolean(allowTrainerBattle && trainerChance && !randSeedInt(trainerChance)); } return false; } @@ -222,7 +222,7 @@ export class GameMode implements GameModeConfig { s.speciesId !== Species.ETERNATUS && s.speciesId !== Species.ARCEUS, ); - return Utils.randSeedItem(allFinalBossSpecies); + return randSeedItem(allFinalBossSpecies); } return null; diff --git a/src/inputs-controller.ts b/src/inputs-controller.ts index fb4555084ee..f92ce3957ab 100644 --- a/src/inputs-controller.ts +++ b/src/inputs-controller.ts @@ -1,6 +1,5 @@ import Phaser from "phaser"; -import * as Utils from "./utils"; -import { deepCopy } from "./utils"; +import { deepCopy, getEnumValues } from "#app/utils"; import pad_generic from "./configs/inputs/pad_generic"; import pad_unlicensedSNES from "./configs/inputs/pad_unlicensedSNES"; import pad_xbox360 from "./configs/inputs/pad_xbox360"; @@ -102,7 +101,7 @@ export class InputsController { [Device.KEYBOARD]: "default", }; - for (const b of Utils.getEnumValues(Button)) { + for (const b of getEnumValues(Button)) { this.interactions[b] = { pressTime: false, isPressed: false, diff --git a/src/loading-scene.ts b/src/loading-scene.ts index f99831c53bc..b45cf64ff56 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -4,7 +4,7 @@ import CacheBustedLoaderPlugin from "#app/plugins/cache-busted-loader-plugin"; import { SceneBase } from "#app/scene-base"; import { WindowVariant, getWindowVariantSuffix } from "#app/ui/ui-theme"; import { isMobile } from "#app/touch-controls"; -import * as Utils from "#app/utils"; +import { localPing, getEnumValues, hasAllLocalizedSprites, getEnumKeys } from "#app/utils"; import { initPokemonPrevolutions, initPokemonStarters } from "#app/data/balance/pokemon-evolutions"; import { initBiomes } from "#app/data/balance/biomes"; import { initEggMoves } from "#app/data/balance/egg-moves"; @@ -34,7 +34,7 @@ export class LoadingScene extends SceneBase { } preload() { - Utils.localPing(); + localPing(); this.load["manifest"] = this.game["manifest"]; this.loadImage("loading_bg", "arenas"); @@ -49,7 +49,7 @@ export class LoadingScene extends SceneBase { this.loadImage("friendship_overlay", "ui"); this.loadImage("cursor", "ui"); this.loadImage("cursor_reverse", "ui"); - for (const wv of Utils.getEnumValues(WindowVariant)) { + for (const wv of getEnumValues(WindowVariant)) { for (let w = 1; w <= 5; w++) { this.loadImage(`window_${w}${getWindowVariantSuffix(wv)}`, "ui/windows"); } @@ -177,7 +177,7 @@ export class LoadingScene extends SceneBase { this.loadImage("default_bg", "arenas"); // Load arena images - Utils.getEnumValues(Biome).map(bt => { + getEnumValues(Biome).map(bt => { const btKey = Biome[bt].toLowerCase(); const isBaseAnimated = btKey === "end"; const baseAKey = `${btKey}_a`; @@ -239,7 +239,7 @@ export class LoadingScene extends SceneBase { // Get current lang and load the types atlas for it. English will only load types while all other languages will load types and types_ const lang = i18next.resolvedLanguage; if (lang !== "en") { - if (Utils.hasAllLocalizedSprites(lang)) { + if (hasAllLocalizedSprites(lang)) { this.loadAtlas(`statuses_${lang}`, ""); this.loadAtlas(`types_${lang}`, ""); } else { @@ -268,7 +268,7 @@ export class LoadingScene extends SceneBase { this.loadAtlas("egg_icons", "egg"); this.loadAtlas("egg_shard", "egg"); this.loadAtlas("egg_lightrays", "egg"); - for (const gt of Utils.getEnumKeys(GachaType)) { + for (const gt of getEnumKeys(GachaType)) { const key = gt.toLowerCase(); this.loadImage(`gacha_${key}`, "egg"); this.loadAtlas(`gacha_underlay_${key}`, "egg"); diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 6c46f7ff8c0..acc7ac0f63a 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -297,16 +297,16 @@ export class MoveEffectPhase extends PokemonPhase { ); } - /** Is the target protected by Protect, etc. or a relevant conditional protection effect? */ - const isProtected = - ![MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES].includes(this.move.getMove().moveTarget) && - (bypassIgnoreProtect.value || - !this.move.getMove().doesFlagEffectApply({ flag: MoveFlags.IGNORE_PROTECT, user, target })) && - (hasConditionalProtectApplied.value || - (!target.findTags(t => t instanceof DamageProtectedTag).length && - target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType))) || - (this.move.getMove().category !== MoveCategory.STATUS && - target.findTags(t => t instanceof DamageProtectedTag).find(t => target.lapseTag(t.tagType)))); + /** Is the target protected by Protect, etc. or a relevant conditional protection effect? */ + const isProtected = + ![MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES].includes(this.move.getMove().moveTarget) && + (bypassIgnoreProtect.value || + !this.move.getMove().doesFlagEffectApply({ flag: MoveFlags.IGNORE_PROTECT, user, target })) && + (hasConditionalProtectApplied.value || + (!target.findTags(t => t instanceof DamageProtectedTag).length && + target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType))) || + (this.move.getMove().category !== MoveCategory.STATUS && + target.findTags(t => t instanceof DamageProtectedTag).find(t => target.lapseTag(t.tagType)))); /** Is the target hidden by the effects of its Commander ability? */ const isCommanding = @@ -316,11 +316,11 @@ export class MoveEffectPhase extends PokemonPhase { /** Is the target reflecting status moves from the magic coat move? */ const isReflecting = !!target.getTag(BattlerTagType.MAGIC_COAT); - /** Is the target's magic bounce ability not ignored and able to reflect this move? */ - const canMagicBounce = - !isReflecting && - !move.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user, target }) && - target.hasAbilityWithAttr(ReflectStatusMoveAbAttr); + /** Is the target's magic bounce ability not ignored and able to reflect this move? */ + const canMagicBounce = + !isReflecting && + !move.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user, target }) && + target.hasAbilityWithAttr(ReflectStatusMoveAbAttr); const semiInvulnerableTag = target.getTag(SemiInvulnerableTag); @@ -333,21 +333,19 @@ export class MoveEffectPhase extends PokemonPhase { (isReflecting || canMagicBounce) && !semiInvulnerableTag; - // If the move will bounce, then queue the bounce and move on to the next target - if (!target.switchOutStatus && willBounce) { - const newTargets = move.isMultiTarget() - ? getMoveTargets(target, move.id).targets - : [user.getBattlerIndex()]; - if (!isReflecting) { - // TODO: Ability displays should be handled by the ability - queuedPhases.push( - new ShowAbilityPhase( - target.getBattlerIndex(), - target.getPassiveAbility().hasAttr(ReflectStatusMoveAbAttr), - ), - ); - queuedPhases.push(new HideAbilityPhase()); - } + // If the move will bounce, then queue the bounce and move on to the next target + if (!target.switchOutStatus && willBounce) { + const newTargets = move.isMultiTarget() ? getMoveTargets(target, move.id).targets : [user.getBattlerIndex()]; + if (!isReflecting) { + // TODO: Ability displays should be handled by the ability + queuedPhases.push( + new ShowAbilityPhase( + target.getBattlerIndex(), + target.getPassiveAbility().hasAttr(ReflectStatusMoveAbAttr), + ), + ); + queuedPhases.push(new HideAbilityPhase()); + } queuedPhases.push(new MovePhase(target, newTargets, new PokemonMove(move.id, 0, 0, true), true, true, true)); continue; diff --git a/src/phases/revival-blessing-phase.ts b/src/phases/revival-blessing-phase.ts index e650d714abc..f6fe4d9a3ee 100644 --- a/src/phases/revival-blessing-phase.ts +++ b/src/phases/revival-blessing-phase.ts @@ -4,7 +4,7 @@ import type { PartyOption } from "#app/ui/party-ui-handler"; import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler"; import { Mode } from "#app/ui/ui"; import i18next from "i18next"; -import * as Utils from "#app/utils"; +import { toDmgValue, isNullOrUndefined } from "#app/utils"; import { BattlePhase } from "#app/phases/battle-phase"; import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase"; @@ -33,7 +33,7 @@ export class RevivalBlessingPhase extends BattlePhase { pokemon.resetTurnData(); pokemon.resetStatus(); - pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp())); + pokemon.heal(Math.min(toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp())); globalScene.queueMessage( i18next.t("moveTriggers:revivalBlessing", { pokemonName: pokemon.name, @@ -46,7 +46,7 @@ export class RevivalBlessingPhase extends BattlePhase { if ( globalScene.currentBattle.double && globalScene.getPlayerParty().length > 1 && - !Utils.isNullOrUndefined(allyPokemon) + !isNullOrUndefined(allyPokemon) ) { if (slotIndex <= 1) { // Revived ally pokemon diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index c6ded6be7af..35511531609 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -12,7 +12,7 @@ import type { Starter } from "#app/ui/starter-select-ui-handler"; import { Mode } from "#app/ui/ui"; import type { Species } from "#enums/species"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; -import * as Utils from "../utils"; +import { isNullOrUndefined } from "#app/utils"; export class SelectStarterPhase extends Phase { start() { @@ -49,7 +49,7 @@ export class SelectStarterPhase extends Phase { let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); if ( starter.species.speciesId in Overrides.STARTER_FORM_OVERRIDES && - !Utils.isNullOrUndefined(Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]) && + !isNullOrUndefined(Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]) && starter.species.forms[Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]!] ) { starterFormIndex = Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]!; @@ -87,7 +87,7 @@ export class SelectStarterPhase extends Phase { starterPokemon.nickname = starter.nickname; } - if (!Utils.isNullOrUndefined(starter.teraType)) { + if (!isNullOrUndefined(starter.teraType)) { starterPokemon.teraType = starter.teraType; } else { starterPokemon.teraType = starterPokemon.species.type1; diff --git a/src/pipelines/field-sprite.ts b/src/pipelines/field-sprite.ts index 612c9fae052..a55b6a9adb6 100644 --- a/src/pipelines/field-sprite.ts +++ b/src/pipelines/field-sprite.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; import { TerrainType, getTerrainColor } from "../data/terrain"; -import * as Utils from "../utils"; +import { getCurrentTime } from "#app/utils"; import fieldSpriteFragShader from "./glsl/fieldSpriteFragShader.frag?raw"; import spriteVertShader from "./glsl/spriteShader.vert?raw"; @@ -34,7 +34,7 @@ export default class FieldSpritePipeline extends Phaser.Renderer.WebGL.Pipelines const time = globalScene.currentBattle?.waveIndex ? ((globalScene.currentBattle.waveIndex + globalScene.waveCycleOffset) % 40) / 40 // ((new Date().getSeconds() * 1000 + new Date().getMilliseconds()) % 10000) / 10000 - : Utils.getCurrentTime(); + : getCurrentTime(); this.set1f("time", time); this.set1i("ignoreTimeTint", ignoreTimeTint ? 1 : 0); this.set1i("isOutside", globalScene.arena.isOutside() ? 1 : 0); diff --git a/src/pipelines/sprite.ts b/src/pipelines/sprite.ts index acbaac50476..d97cae1662b 100644 --- a/src/pipelines/sprite.ts +++ b/src/pipelines/sprite.ts @@ -3,7 +3,7 @@ import MysteryEncounterIntroVisuals from "#app/field/mystery-encounter-intro"; import Pokemon from "#app/field/pokemon"; import Trainer from "#app/field/trainer"; import { globalScene } from "#app/global-scene"; -import * as Utils from "#app/utils"; +import { rgbHexToRgba } from "#app/utils"; import FieldSpritePipeline from "./field-sprite"; import spriteFragShader from "./glsl/spriteFragShader.frag?raw"; import spriteVertShader from "./glsl/spriteShader.vert?raw"; @@ -144,8 +144,8 @@ export default class SpritePipeline extends FieldSpritePipeline { const baseColors = Object.keys(variantColors[variant]); for (let c = 0; c < 32; c++) { if (c < baseColors.length) { - const baseColor = Array.from(Object.values(Utils.rgbHexToRgba(baseColors[c]))); - const variantColor = Array.from(Object.values(Utils.rgbHexToRgba(variantColors[variant][baseColors[c]]))); + const baseColor = Array.from(Object.values(rgbHexToRgba(baseColors[c]))); + const variantColor = Array.from(Object.values(rgbHexToRgba(variantColors[variant][baseColors[c]]))); flatBaseColors.splice(flatBaseColors.length, 0, ...baseColor); flatVariantColors.splice(flatVariantColors.length, 0, ...variantColor.map(c => c / 255.0)); } else { diff --git a/src/system/achv.ts b/src/system/achv.ts index bd8595b2f94..62e69e6fbfe 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -2,7 +2,7 @@ import type { Modifier } from "typescript"; import { TurnHeldItemTransferModifier } from "../modifier/modifier"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import i18next from "i18next"; -import * as Utils from "../utils"; +import { NumberHolder } from "#app/utils"; import { PlayerGender } from "#enums/player-gender"; import type { Challenge } from "#app/data/challenge"; import { @@ -138,7 +138,7 @@ export class DamageAchv extends Achv { "", iconImage, score, - (args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.damageAmount, + (args: any[]) => (args[0] instanceof NumberHolder ? args[0].value : args[0]) >= this.damageAmount, ); this.damageAmount = damageAmount; } @@ -154,7 +154,7 @@ export class HealAchv extends Achv { "", iconImage, score, - (args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.healAmount, + (args: any[]) => (args[0] instanceof NumberHolder ? args[0].value : args[0]) >= this.healAmount, ); this.healAmount = healAmount; } @@ -170,7 +170,7 @@ export class LevelAchv extends Achv { "", iconImage, score, - (args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.level, + (args: any[]) => (args[0] instanceof NumberHolder ? args[0].value : args[0]) >= this.level, ); this.level = level; } diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 061a6d3a194..63955b02de8 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -8,7 +8,7 @@ import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import type PokemonSpecies from "#app/data/pokemon-species"; import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; -import * as Utils from "#app/utils"; +import { randInt, getEnumKeys, isLocal, executeIf, fixedInt, randSeedItem, NumberHolder } from "#app/utils"; import Overrides from "#app/overrides"; import PokemonData from "#app/system/pokemon-data"; import PersistentModifierData from "#app/system/modifier-data"; @@ -37,6 +37,7 @@ import { setSettingGamepad, SettingGamepad, settingGamepadDefaults } from "#app/ import type { SettingKeyboard } from "#app/system/settings/settings-keyboard"; import { setSettingKeyboard } from "#app/system/settings/settings-keyboard"; import { TagAddedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena"; +// biome-ignore lint/style/noNamespaceImport: Something weird is going on here and I don't want to touch it import * as Modifier from "#app/modifier/modifier"; import { StatusEffect } from "#enums/status-effect"; import ChallengeData from "#app/system/challenge-data"; @@ -360,8 +361,8 @@ export class GameData { this.loadSettings(); this.loadGamepadSettings(); this.loadMappingConfigs(); - this.trainerId = Utils.randInt(65536); - this.secretId = Utils.randInt(65536); + this.trainerId = randInt(65536); + this.secretId = randInt(65536); this.starterData = {}; this.gameStats = new GameStats(); this.runHistory = {}; @@ -589,7 +590,7 @@ export class GameData { } if (systemData.voucherCounts) { - Utils.getEnumKeys(VoucherType).forEach(key => { + getEnumKeys(VoucherType).forEach(key => { const index = VoucherType[key]; this.voucherCounts[index] = systemData.voucherCounts[index] || 0; }); @@ -617,7 +618,7 @@ export class GameData { * At the moment, only retrievable from locale cache */ async getRunHistoryData(): Promise { - if (!Utils.isLocal) { + if (!isLocal) { /** * Networking Code DO NOT DELETE! * Note: Might have to be migrated to `pokerogue-api.ts` @@ -1035,6 +1036,7 @@ export class GameData { } getSession(slotId: number): Promise { + // biome-ignore lint/suspicious/noAsyncPromiseExecutor: return new Promise(async (resolve, reject) => { if (slotId < 0) { return resolve(null); @@ -1075,6 +1077,7 @@ export class GameData { } loadSession(slotId: number, sessionData?: SessionSaveData): Promise { + // biome-ignore lint/suspicious/noAsyncPromiseExecutor: return new Promise(async (resolve, reject) => { try { const initSessionFromData = async (sessionData: SessionSaveData) => { @@ -1406,7 +1409,7 @@ export class GameData { saveAll(skipVerification = false, sync = false, useCachedSession = false, useCachedSystem = false): Promise { return new Promise(resolve => { - Utils.executeIf(!skipVerification, updateUserInfo).then(success => { + executeIf(!skipVerification, updateUserInfo).then(success => { if (success !== null && !success) { return resolve(false); } @@ -1586,7 +1589,7 @@ export class GameData { } const displayError = (error: string) => - globalScene.ui.showText(error, null, () => globalScene.ui.showText("", 0), Utils.fixedInt(1500)); + globalScene.ui.showText(error, null, () => globalScene.ui.showText("", 0), fixedInt(1500)); dataName = dataName!; // tell TS compiler that dataName is defined! if (!valid) { @@ -1594,7 +1597,7 @@ export class GameData { `Your ${dataName} data could not be loaded. It may be corrupted.`, null, () => globalScene.ui.showText("", 0), - Utils.fixedInt(1500), + fixedInt(1500), ); } @@ -1687,7 +1690,7 @@ export class GameData { () => { const neutralNatures = [Nature.HARDY, Nature.DOCILE, Nature.SERIOUS, Nature.BASHFUL, Nature.QUIRKY]; for (let s = 0; s < defaultStarterSpecies.length; s++) { - defaultStarterNatures.push(Utils.randSeedItem(neutralNatures)); + defaultStarterNatures.push(randSeedItem(neutralNatures)); } }, 0, @@ -2188,7 +2191,7 @@ export class GameData { value = decrementValue(value); } - const cost = new Utils.NumberHolder(value); + const cost = new NumberHolder(value); applyChallenges(ChallengeType.STARTER_COST, speciesId, cost); return cost.value; @@ -2216,7 +2219,7 @@ export class GameData { entry.hatchedCount = 0; } if (!entry.hasOwnProperty("natureAttr") || (entry.caughtAttr && !entry.natureAttr)) { - entry.natureAttr = this.defaultDexData?.[k].natureAttr || 1 << Utils.randInt(25, 1); + entry.natureAttr = this.defaultDexData?.[k].natureAttr || 1 << randInt(25, 1); } } } diff --git a/src/system/game-speed.ts b/src/system/game-speed.ts index d9c48664f80..3df47fafc6c 100644 --- a/src/system/game-speed.ts +++ b/src/system/game-speed.ts @@ -3,7 +3,7 @@ import type FadeIn from "phaser3-rex-plugins/plugins/audio/fade/FadeIn"; import type FadeOut from "phaser3-rex-plugins/plugins/audio/fade/FadeOut"; import type BattleScene from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; -import * as Utils from "../utils"; +import { FixedInt } from "#app/utils"; type FadeInType = typeof FadeIn; type FadeOutType = typeof FadeOut; @@ -11,9 +11,9 @@ type FadeOutType = typeof FadeOut; export function initGameSpeed() { const thisArg = this as BattleScene; - const transformValue = (value: number | Utils.FixedInt): number => { - if (value instanceof Utils.FixedInt) { - return (value as Utils.FixedInt).value; + const transformValue = (value: number | FixedInt): number => { + if (value instanceof FixedInt) { + return (value as FixedInt).value; } return thisArg.gameSpeed === 1 ? value : Math.ceil((value /= thisArg.gameSpeed)); }; diff --git a/src/system/version_migration/version_converter.ts b/src/system/version_migration/version_converter.ts index 4b712609819..1fdb9e93f88 100644 --- a/src/system/version_migration/version_converter.ts +++ b/src/system/version_migration/version_converter.ts @@ -48,12 +48,15 @@ export const settingsMigrators: Readonly = [settingsMigr // import * as vA_B_C from "./versions/vA_B_C"; // --- v1.0.4 (and below) PATCHES --- // +// biome-ignore lint/style/noNamespaceImport: Convenience (TODO: make this a file-wide ignore when Biome supports those) import * as v1_0_4 from "./versions/v1_0_4"; // --- v1.7.0 PATCHES --- // +// biome-ignore lint/style/noNamespaceImport: Convenience import * as v1_7_0 from "./versions/v1_7_0"; // --- v1.8.3 PATCHES --- // +// biome-ignore lint/style/noNamespaceImport: Convenience import * as v1_8_3 from "./versions/v1_8_3"; /** Current game version */ diff --git a/src/ui/abstact-option-select-ui-handler.ts b/src/ui/abstact-option-select-ui-handler.ts index f605f73e171..b360065f61d 100644 --- a/src/ui/abstact-option-select-ui-handler.ts +++ b/src/ui/abstact-option-select-ui-handler.ts @@ -3,7 +3,7 @@ import { TextStyle, addBBCodeTextObject, getTextColor, getTextStyleOptions } fro import { Mode } from "./ui"; import UiHandler from "./ui-handler"; import { addWindow } from "./ui-theme"; -import * as Utils from "../utils"; +import { rgbHexToRgba, fixedInt } from "#app/utils"; import { argbFromRgba } from "@material/material-color-utilities"; import { Button } from "#enums/buttons"; import BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText"; @@ -178,8 +178,8 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { itemOverlayIcon.setPositionRelative(this.optionSelectText, 36 * this.scale, 7 + i * (114 * this.scale - 3)); if (option.itemArgs) { - itemIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(option.itemArgs[0]))); - itemOverlayIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(option.itemArgs[1]))); + itemIcon.setTint(argbFromRgba(rgbHexToRgba(option.itemArgs[0]))); + itemOverlayIcon.setTint(argbFromRgba(rgbHexToRgba(option.itemArgs[1]))); } } } @@ -207,7 +207,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { this.blockInput = true; this.optionSelectTextContainer.setAlpha(0.5); this.cursorObj?.setAlpha(0.8); - globalScene.time.delayedCall(Utils.fixedInt(this.config.delay), () => this.unblockInput()); + globalScene.time.delayedCall(fixedInt(this.config.delay), () => this.unblockInput()); } if (this.config?.supportHover) { diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index 36a44eb5aa0..1eb18a32f98 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -16,7 +16,7 @@ import type { TurnEndEvent } from "../events/battle-scene"; import { BattleSceneEventType } from "../events/battle-scene"; import { ArenaTagType } from "#enums/arena-tag-type"; import TimeOfDayWidget from "./time-of-day-widget"; -import * as Utils from "../utils"; +import { toCamelCaseString, formatText, fixedInt } from "#app/utils"; import type { ParseKeys } from "i18next"; import i18next from "i18next"; @@ -47,10 +47,10 @@ export function getFieldEffectText(arenaTagType: string): string { if (!arenaTagType || arenaTagType === ArenaTagType.NONE) { return arenaTagType; } - const effectName = Utils.toCamelCaseString(arenaTagType); + const effectName = toCamelCaseString(arenaTagType); const i18nKey = `arenaFlyout:${effectName}` as ParseKeys; const resultName = i18next.t(i18nKey); - return !resultName || resultName === i18nKey ? Utils.formatText(arenaTagType) : resultName; + return !resultName || resultName === i18nKey ? formatText(arenaTagType) : resultName; } export class ArenaFlyout extends Phaser.GameObjects.Container { @@ -411,7 +411,7 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { globalScene.tweens.add({ targets: this.flyoutParent, x: visible ? this.anchorX : this.anchorX - this.translationX, - duration: Utils.fixedInt(125), + duration: fixedInt(125), ease: "Sine.easeInOut", alpha: visible ? 1 : 0, onComplete: () => (this.timeOfDayWidget.parentVisible = visible), diff --git a/src/ui/base-stats-overlay.ts b/src/ui/base-stats-overlay.ts index 5a6c67cae7b..d0b0aff3a9d 100644 --- a/src/ui/base-stats-overlay.ts +++ b/src/ui/base-stats-overlay.ts @@ -1,7 +1,7 @@ import type { InfoToggle } from "../battle-scene"; import { TextStyle, addTextObject } from "./text"; import { addWindow } from "./ui-theme"; -import * as Utils from "../utils"; +import { fixedInt } from "#app/utils"; import i18next from "i18next"; import { globalScene } from "#app/global-scene"; @@ -93,7 +93,7 @@ export class BaseStatsOverlay extends Phaser.GameObjects.Container implements In } globalScene.tweens.add({ targets: this.statsLabels, - duration: Utils.fixedInt(125), + duration: fixedInt(125), ease: "Sine.easeInOut", alpha: visible ? 1 : 0, }); diff --git a/src/ui/battle-flyout.ts b/src/ui/battle-flyout.ts index 206546ad9cb..854f4cc4dd9 100644 --- a/src/ui/battle-flyout.ts +++ b/src/ui/battle-flyout.ts @@ -1,6 +1,6 @@ import type { default as Pokemon } from "../field/pokemon"; import { addTextObject, TextStyle } from "./text"; -import * as Utils from "../utils"; +import { fixedInt } from "#app/utils"; import { globalScene } from "#app/global-scene"; import type Move from "#app/data/moves/move"; import type { BerryUsedEvent, MoveUsedEvent } from "../events/battle-scene"; @@ -201,7 +201,7 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { globalScene.tweens.add({ targets: this.flyoutParent, x: visible ? this.anchorX : this.anchorX - this.translationX, - duration: Utils.fixedInt(125), + duration: fixedInt(125), ease: "Sine.easeInOut", alpha: visible ? 1 : 0, }); diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index ab006269d4e..2b205329ab8 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -1,6 +1,6 @@ import type { EnemyPokemon, default as Pokemon } from "../field/pokemon"; import { getLevelTotalExp, getLevelRelExp } from "../data/exp"; -import * as Utils from "../utils"; +import { getLocalizedSpriteKey, fixedInt } from "#app/utils"; import { addTextObject, TextStyle } from "./text"; import { getGenderSymbol, getGenderColor, Gender } from "../data/gender"; import { StatusEffect } from "#enums/status-effect"; @@ -163,7 +163,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { 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, Utils.getLocalizedSpriteKey("statuses")); + this.statusIndicator = globalScene.add.sprite(0, 0, getLocalizedSpriteKey("statuses")); this.statusIndicator.setName("icon_status"); this.statusIndicator.setVisible(false); this.statusIndicator.setOrigin(0, 0); @@ -536,7 +536,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { toggleStats(visible: boolean): void { globalScene.tweens.add({ targets: this.statsContainer, - duration: Utils.fixedInt(125), + duration: fixedInt(125), ease: "Sine.easeInOut", alpha: visible ? 1 : 0, }); diff --git a/src/ui/bgm-bar.ts b/src/ui/bgm-bar.ts index 45ed766c7fa..d944453ba2c 100644 --- a/src/ui/bgm-bar.ts +++ b/src/ui/bgm-bar.ts @@ -1,6 +1,6 @@ import { addTextObject, TextStyle } from "./text"; import i18next from "i18next"; -import * as Utils from "#app/utils"; +import { formatText } from "#app/utils"; import { globalScene } from "#app/global-scene"; const hiddenX = -150; @@ -100,7 +100,7 @@ export default class BgmBar extends Phaser.GameObjects.Container { getRealBgmName(bgmName: string): string { return i18next.t([`bgmName:${bgmName}`, "bgmName:missing_entries"], { - name: Utils.formatText(bgmName), + name: formatText(bgmName), }); } } diff --git a/src/ui/candy-bar.ts b/src/ui/candy-bar.ts index ba85ed7fef3..0cf3e0c91e9 100644 --- a/src/ui/candy-bar.ts +++ b/src/ui/candy-bar.ts @@ -2,7 +2,7 @@ import { starterColors } from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; import { TextStyle, addTextObject } from "./text"; import { argbFromRgba } from "@material/material-color-utilities"; -import * as Utils from "../utils"; +import { rgbHexToRgba } from "#app/utils"; import type { Species } from "#enums/species"; export default class CandyBar extends Phaser.GameObjects.Container { @@ -60,8 +60,8 @@ export default class CandyBar extends Phaser.GameObjects.Container { const colorScheme = starterColors[starterSpeciesId]; - this.candyIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(colorScheme[0]))); - this.candyOverlayIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(colorScheme[1]))); + this.candyIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[0]))); + this.candyOverlayIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[1]))); this.countText.setText( `${globalScene.gameData.starterData[starterSpeciesId].candyCount + count} (+${count.toString()})`, diff --git a/src/ui/challenges-select-ui-handler.ts b/src/ui/challenges-select-ui-handler.ts index 61989cd594e..caffede2487 100644 --- a/src/ui/challenges-select-ui-handler.ts +++ b/src/ui/challenges-select-ui-handler.ts @@ -5,7 +5,7 @@ import { addWindow } from "./ui-theme"; import { Button } from "#enums/buttons"; import i18next from "i18next"; import type { Challenge } from "#app/data/challenge"; -import * as Utils from "../utils"; +import { getLocalizedSpriteKey } from "#app/utils"; import { Challenges } from "#app/enums/challenges"; import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { Color, ShadowColor } from "#app/enums/color"; @@ -193,7 +193,7 @@ export default class GameChallengesUiHandler extends UiHandler { }; } - this.monoTypeValue = globalScene.add.sprite(8, 98, Utils.getLocalizedSpriteKey("types")); + this.monoTypeValue = globalScene.add.sprite(8, 98, getLocalizedSpriteKey("types")); this.monoTypeValue.setName("challenge-value-monotype-sprite"); this.monoTypeValue.setScale(0.86); this.monoTypeValue.setVisible(false); diff --git a/src/ui/char-sprite.ts b/src/ui/char-sprite.ts index 74c021a65b8..f717927c107 100644 --- a/src/ui/char-sprite.ts +++ b/src/ui/char-sprite.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import * as Utils from "../utils"; +import { MissingTextureKey } from "#app/utils"; export default class CharSprite extends Phaser.GameObjects.Container { private sprite: Phaser.GameObjects.Sprite; @@ -57,7 +57,7 @@ export default class CharSprite extends Phaser.GameObjects.Container { }, }); - this.setVisible(globalScene.textures.get(key).key !== Utils.MissingTextureKey); + this.setVisible(globalScene.textures.get(key).key !== MissingTextureKey); this.shown = true; this.key = key; diff --git a/src/ui/daily-run-scoreboard.ts b/src/ui/daily-run-scoreboard.ts index 53c737898e7..896f2171676 100644 --- a/src/ui/daily-run-scoreboard.ts +++ b/src/ui/daily-run-scoreboard.ts @@ -1,6 +1,6 @@ import i18next from "i18next"; import { globalScene } from "#app/global-scene"; -import * as Utils from "../utils"; +import { getEnumKeys, executeIf } from "#app/utils"; import { TextStyle, addTextObject } from "./text"; import { WindowVariant, addWindow } from "./ui-theme"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; @@ -89,7 +89,7 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container { this.prevCategoryButton.setInteractive(new Phaser.Geom.Rectangle(0, 0, 6, 10), Phaser.Geom.Rectangle.Contains); this.prevCategoryButton.on("pointerup", () => { - this.update(this.category ? this.category - 1 : Utils.getEnumKeys(ScoreboardCategory).length - 1); + this.update(this.category ? this.category - 1 : getEnumKeys(ScoreboardCategory).length - 1); }); this.nextCategoryButton = globalScene.add.sprite(window.displayWidth - 4, 4, "cursor"); @@ -98,7 +98,7 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container { this.nextCategoryButton.setInteractive(new Phaser.Geom.Rectangle(0, 0, 6, 10), Phaser.Geom.Rectangle.Contains); this.nextCategoryButton.on("pointerup", () => { - this.update(this.category < Utils.getEnumKeys(ScoreboardCategory).length - 1 ? this.category + 1 : 0); + this.update(this.category < getEnumKeys(ScoreboardCategory).length - 1 ? this.category + 1 : 0); }); this.prevPageButton = globalScene.add.sprite( @@ -226,7 +226,7 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container { this.page = page = 1; } - Utils.executeIf(category !== this.category || this.pageCount === undefined, () => + executeIf(category !== this.category || this.pageCount === undefined, () => pokerogueApi.daily.getRankingsPageCount({ category }).then(count => (this.pageCount = count)), ) .then(() => { diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index cb6a474f01d..956a308448b 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -1,7 +1,7 @@ import { Mode } from "./ui"; import { TextStyle, addTextObject, getEggTierTextTint, getTextStyleOptions } from "./text"; import MessageUiHandler from "./message-ui-handler"; -import * as Utils from "../utils"; +import { getEnumValues, getEnumKeys, fixedInt, randSeedShuffle } from "#app/utils"; import type { IEggOptions } from "../data/egg"; import { Egg, getLegendaryGachaSpeciesForTimestamp } from "../data/egg"; import { VoucherType, getVoucherTypeIcon } from "../system/voucher"; @@ -83,7 +83,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { }); } - Utils.getEnumValues(GachaType).forEach((gachaType, g) => { + getEnumValues(GachaType).forEach((gachaType, g) => { const gachaTypeKey = GachaType[gachaType].toString().toLowerCase(); const gachaContainer = globalScene.add.container(180 * g, 18); @@ -272,7 +272,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { this.eggGachaContainer.add(this.eggGachaOptionsContainer); - new Array(Utils.getEnumKeys(VoucherType).length).fill(null).map((_, i) => { + new Array(getEnumKeys(VoucherType).length).fill(null).map((_, i) => { const container = globalScene.add.container(globalScene.game.canvas.width / 6 - 56 * i, 0); const bg = addWindow(0, 0, 56, 22); @@ -355,7 +355,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { if (this.transitioning && this.transitionCancelled) { delay = Math.ceil(delay / 5); } - return Utils.fixedInt(delay); + return fixedInt(delay); } pull(pullCount = 0, count = 0, eggs?: Egg[]): void { @@ -476,7 +476,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { eggs.push(egg); } // Shuffle the eggs in case the guaranteed one got added as last egg - eggs = Utils.randSeedShuffle(eggs); + eggs = randSeedShuffle(eggs); (globalScene.currentBattle ? globalScene.gameData.saveAll(true, true, true) @@ -643,7 +643,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { } showError(text: string): void { - this.showText(text, undefined, () => this.showText(this.defaultText), Utils.fixedInt(1500)); + this.showText(text, undefined, () => this.showText(this.defaultText), fixedInt(1500)); } setTransitioning(transitioning: boolean): void { @@ -783,7 +783,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { } break; case Button.RIGHT: - if (this.gachaCursor < Utils.getEnumKeys(GachaType).length - 1) { + if (this.gachaCursor < getEnumKeys(GachaType).length - 1) { success = this.setGachaCursor(this.gachaCursor + 1); } break; diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 9f76e85f228..3775dbc2228 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -6,7 +6,7 @@ import { PokemonType } from "#enums/pokemon-type"; import { Command } from "./command-ui-handler"; import { Mode } from "./ui"; import UiHandler from "./ui-handler"; -import * as Utils from "../utils"; +import { getLocalizedSpriteKey, fixedInt, padInt } from "#app/utils"; import { MoveCategory } from "#enums/MoveCategory"; import i18next from "i18next"; import { Button } from "#enums/buttons"; @@ -54,7 +54,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { this.typeIcon = globalScene.add.sprite( globalScene.scaledCanvas.width - 57, -36, - Utils.getLocalizedSpriteKey("types"), + getLocalizedSpriteKey("types"), "unknown", ); this.typeIcon.setVisible(false); @@ -199,7 +199,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { } globalScene.tweens.add({ targets: [this.movesContainer, this.cursorObj], - duration: Utils.fixedInt(125), + duration: fixedInt(125), ease: "Sine.easeInOut", alpha: visible ? 0 : 1, }); @@ -245,7 +245,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { if (hasMove) { const pokemonMove = moveset[cursor]; const moveType = pokemon.getMoveType(pokemonMove.getMove()); - const textureKey = Utils.getLocalizedSpriteKey("types"); + const textureKey = getLocalizedSpriteKey("types"); this.typeIcon.setTexture(textureKey, PokemonType[moveType].toLowerCase()).setScale(0.8); const moveCategory = pokemonMove.getMove().category; @@ -255,8 +255,8 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { const maxPP = pokemonMove.getMovePp(); const pp = maxPP - pokemonMove.ppUsed; - const ppLeftStr = Utils.padInt(pp, 2, " "); - const ppMaxStr = Utils.padInt(maxPP, 2, " "); + const ppLeftStr = padInt(pp, 2, " "); + const ppMaxStr = padInt(maxPP, 2, " "); this.ppText.setText(`${ppLeftStr}/${ppMaxStr}`); this.powerText.setText(`${power >= 0 ? power : "---"}`); this.accuracyText.setText(`${accuracy >= 0 ? accuracy : "---"}`); diff --git a/src/ui/form-modal-ui-handler.ts b/src/ui/form-modal-ui-handler.ts index 8784145acd6..e27b2e9ed89 100644 --- a/src/ui/form-modal-ui-handler.ts +++ b/src/ui/form-modal-ui-handler.ts @@ -4,7 +4,7 @@ import type { Mode } from "./ui"; import { TextStyle, addTextInputObject, addTextObject } from "./text"; import { WindowVariant, addWindow } from "./ui-theme"; import type InputText from "phaser3-rex-plugins/plugins/inputtext"; -import * as Utils from "../utils"; +import { fixedInt } from "#app/utils"; import { Button } from "#enums/buttons"; import { globalScene } from "#app/global-scene"; @@ -135,7 +135,7 @@ export abstract class FormModalUiHandler extends ModalUiHandler { this.tween = globalScene.tweens.add({ targets: this.modalContainer, - duration: Utils.fixedInt(1000), + duration: fixedInt(1000), ease: "Sine.easeInOut", y: "-=24", alpha: 1, diff --git a/src/ui/game-stats-ui-handler.ts b/src/ui/game-stats-ui-handler.ts index 7d3decf0c4c..2e2112dfda4 100644 --- a/src/ui/game-stats-ui-handler.ts +++ b/src/ui/game-stats-ui-handler.ts @@ -3,7 +3,7 @@ import { TextStyle, addTextObject } from "#app/ui/text"; import type { Mode } from "#app/ui/ui"; import UiHandler from "#app/ui/ui-handler"; import { addWindow } from "#app/ui/ui-theme"; -import * as Utils from "#app/utils"; +import { getPlayTimeString, formatFancyLargeNumber, toReadableString } from "#app/utils"; import type { GameData } from "#app/system/game-data"; import { DexAttr } from "#app/system/game-data"; import { speciesStarterCosts } from "#app/data/balance/starters"; @@ -25,7 +25,7 @@ interface DisplayStats { const displayStats: DisplayStats = { playTime: { label_key: "playTime", - sourceFunc: gameData => Utils.getPlayTimeString(gameData.gameStats.playTime), + sourceFunc: gameData => getPlayTimeString(gameData.gameStats.playTime), }, battles: { label_key: "totalBattles", @@ -91,7 +91,7 @@ const displayStats: DisplayStats = { }, highestMoney: { label_key: "highestMoney", - sourceFunc: gameData => Utils.formatFancyLargeNumber(gameData.gameStats.highestMoney), + sourceFunc: gameData => formatFancyLargeNumber(gameData.gameStats.highestMoney), }, highestDamage: { label_key: "highestDamage", @@ -435,7 +435,7 @@ export function initStatsKeys() { } if (!(displayStats[key] as DisplayStat).label_key) { const splittableKey = key.replace(/([a-z]{2,})([A-Z]{1}(?:[^A-Z]|$))/g, "$1_$2"); - (displayStats[key] as DisplayStat).label_key = Utils.toReadableString( + (displayStats[key] as DisplayStat).label_key = toReadableString( `${splittableKey[0].toUpperCase()}${splittableKey.slice(1)}`, ); } diff --git a/src/ui/login-form-ui-handler.ts b/src/ui/login-form-ui-handler.ts index 1087ffa3fd1..5c009357443 100644 --- a/src/ui/login-form-ui-handler.ts +++ b/src/ui/login-form-ui-handler.ts @@ -1,7 +1,7 @@ import type { InputFieldConfig } from "./form-modal-ui-handler"; import { FormModalUiHandler } from "./form-modal-ui-handler"; import type { ModalConfig } from "./modal-ui-handler"; -import * as Utils from "../utils"; +import { fixedInt } from "#app/utils"; import { Mode } from "./ui"; import i18next from "i18next"; import { addTextObject, TextStyle } from "./text"; @@ -283,7 +283,7 @@ export default class LoginFormUiHandler extends FormModalUiHandler { this.externalPartyContainer.setAlpha(0); globalScene.tweens.add({ targets: this.externalPartyContainer, - duration: Utils.fixedInt(1000), + duration: fixedInt(1000), ease: "Sine.easeInOut", y: "-=24", alpha: 1, @@ -292,7 +292,7 @@ export default class LoginFormUiHandler extends FormModalUiHandler { this.infoContainer.setAlpha(0); globalScene.tweens.add({ targets: this.infoContainer, - duration: Utils.fixedInt(1000), + duration: fixedInt(1000), ease: "Sine.easeInOut", y: "-=24", alpha: 1, diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index b83ae24c9e0..241ddbb91a8 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -2,7 +2,7 @@ import { bypassLogin } from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; import { TextStyle, addTextObject, getTextStyleOptions } from "./text"; import { Mode } from "./ui"; -import * as Utils from "../utils"; +import { getEnumKeys, isLocal, isBeta, fixedInt, getCookie, sessionIdKey } from "#app/utils"; import { addWindow, WindowVariant } from "./ui-theme"; import MessageUiHandler from "./message-ui-handler"; import type { OptionSelectConfig, OptionSelectItem } from "./abstact-option-select-ui-handler"; @@ -75,7 +75,7 @@ export default class MenuUiHandler extends MessageUiHandler { { condition: bypassLogin, options: [MenuOptions.LOG_OUT] }, ]; - this.menuOptions = Utils.getEnumKeys(MenuOptions) + this.menuOptions = getEnumKeys(MenuOptions) .map(m => Number.parseInt(MenuOptions[m]) as MenuOptions) .filter(m => { return !this.excludedMenus().some(exclusion => exclusion.condition && exclusion.options.includes(m)); @@ -130,7 +130,7 @@ export default class MenuUiHandler extends MessageUiHandler { { condition: bypassLogin, options: [MenuOptions.LOG_OUT] }, ]; - this.menuOptions = Utils.getEnumKeys(MenuOptions) + this.menuOptions = getEnumKeys(MenuOptions) .map(m => Number.parseInt(MenuOptions[m]) as MenuOptions) .filter(m => { return !this.excludedMenus().some(exclusion => exclusion.condition && exclusion.options.includes(m)); @@ -238,7 +238,7 @@ export default class MenuUiHandler extends MessageUiHandler { }); }; - if (Utils.isLocal || Utils.isBeta) { + if (isLocal || isBeta) { manageDataOptions.push({ label: i18next.t("menuUiHandler:importSession"), handler: () => { @@ -292,7 +292,7 @@ export default class MenuUiHandler extends MessageUiHandler { }, keepOpen: true, }); - if (Utils.isLocal || Utils.isBeta) { + if (isLocal || isBeta) { manageDataOptions.push({ label: i18next.t("menuUiHandler:importData"), handler: () => { @@ -328,7 +328,7 @@ export default class MenuUiHandler extends MessageUiHandler { keepOpen: true, }, ); - if (Utils.isLocal || Utils.isBeta) { + if (isLocal || isBeta) { // this should make sure we don't have this option in live manageDataOptions.push({ label: "Test Dialogue", @@ -510,7 +510,7 @@ export default class MenuUiHandler extends MessageUiHandler { this.render(); super.show(args); - this.menuOptions = Utils.getEnumKeys(MenuOptions) + this.menuOptions = getEnumKeys(MenuOptions) .map(m => Number.parseInt(MenuOptions[m]) as MenuOptions) .filter(m => { return !this.excludedMenus().some(exclusion => exclusion.condition && exclusion.options.includes(m)); @@ -574,7 +574,7 @@ export default class MenuUiHandler extends MessageUiHandler { ui.setOverlayMode(Mode.EGG_LIST); success = true; } else { - ui.showText(i18next.t("menuUiHandler:noEggs"), null, () => ui.showText(""), Utils.fixedInt(1500)); + ui.showText(i18next.t("menuUiHandler:noEggs"), null, () => ui.showText(""), fixedInt(1500)); error = true; } break; @@ -607,7 +607,7 @@ export default class MenuUiHandler extends MessageUiHandler { : i18next.t("menuUiHandler:unlinkDiscord"), handler: () => { if (loggedInUser?.discordId === "") { - const token = Utils.getCookie(Utils.sessionIdKey); + const token = getCookie(sessionIdKey); const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/discord/callback`); const discordId = import.meta.env.VITE_DISCORD_CLIENT_ID; const discordUrl = `https://discord.com/api/oauth2/authorize?client_id=${discordId}&redirect_uri=${redirectUri}&response_type=code&scope=identify&state=${token}&prompt=none`; @@ -627,7 +627,7 @@ export default class MenuUiHandler extends MessageUiHandler { : i18next.t("menuUiHandler:unlinkGoogle"), handler: () => { if (loggedInUser?.googleId === "") { - const token = Utils.getCookie(Utils.sessionIdKey); + const token = getCookie(sessionIdKey); const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/google/callback`); const googleId = import.meta.env.VITE_GOOGLE_CLIENT_ID; const googleUrl = `https://accounts.google.com/o/oauth2/auth?client_id=${googleId}&response_type=code&redirect_uri=${redirectUri}&scope=openid&state=${token}`; diff --git a/src/ui/message-ui-handler.ts b/src/ui/message-ui-handler.ts index e927793e0ab..b57b236531c 100644 --- a/src/ui/message-ui-handler.ts +++ b/src/ui/message-ui-handler.ts @@ -1,6 +1,6 @@ import AwaitableUiHandler from "./awaitable-ui-handler"; import type { Mode } from "./ui"; -import * as Utils from "../utils"; +import { getFrameMs } from "#app/utils"; import { globalScene } from "#app/global-scene"; export default abstract class MessageUiHandler extends AwaitableUiHandler { @@ -183,7 +183,7 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { if (charDelay) { this.textTimer!.paused = true; // TODO: is the bang correct? globalScene.tweens.addCounter({ - duration: Utils.getFrameMs(charDelay), + duration: getFrameMs(charDelay), onComplete: () => { this.textTimer!.paused = false; // TODO: is the bang correct? advance(); @@ -193,7 +193,7 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { this.textTimer!.paused = true; globalScene.time.delayedCall(150, () => { globalScene.ui.fadeOut(750).then(() => { - const delay = Utils.getFrameMs(charFade); + const delay = getFrameMs(charFade); globalScene.time.delayedCall(delay, () => { globalScene.ui.fadeIn(500).then(() => { this.textTimer!.paused = false; diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index e5d8f858782..26351d4dbf1 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -10,11 +10,10 @@ import { handleTutorial, Tutorial } from "../tutorial"; import { Button } from "#enums/buttons"; import MoveInfoOverlay from "./move-info-overlay"; import { allMoves } from "../data/moves/move"; -import * as Utils from "./../utils"; +import { formatMoney, NumberHolder } from "#app/utils"; import Overrides from "#app/overrides"; import i18next from "i18next"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; -import { NumberHolder } from "./../utils"; import Phaser from "phaser"; import type { PokeballType } from "#enums/pokeball"; @@ -645,7 +644,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.rerollCostText.setVisible(true); const canReroll = globalScene.money >= this.rerollCost; - const formattedMoney = Utils.formatMoney(globalScene.moneyFormat, this.rerollCost); + const formattedMoney = formatMoney(globalScene.moneyFormat, this.rerollCost); this.rerollCostText.setText(i18next.t("modifierSelectUiHandler:rerollCost", { formattedMoney })); this.rerollCostText.setColor(this.getTextColor(canReroll ? TextStyle.MONEY : TextStyle.PARTY_RED)); @@ -933,7 +932,7 @@ class ModifierOption extends Phaser.GameObjects.Container { const cost = Overrides.WAIVE_ROLL_FEE_OVERRIDE ? 0 : this.modifierTypeOption.cost; const textStyle = cost <= globalScene.money ? TextStyle.MONEY : TextStyle.PARTY_RED; - const formattedMoney = Utils.formatMoney(globalScene.moneyFormat, cost); + const formattedMoney = formatMoney(globalScene.moneyFormat, cost); this.itemCostText.setText(i18next.t("modifierSelectUiHandler:itemCost", { formattedMoney })); this.itemCostText.setColor(getTextColor(textStyle, false, globalScene.uiTheme)); diff --git a/src/ui/move-info-overlay.ts b/src/ui/move-info-overlay.ts index 6fc99beb0ae..bd9fdf00c72 100644 --- a/src/ui/move-info-overlay.ts +++ b/src/ui/move-info-overlay.ts @@ -2,7 +2,7 @@ import type { InfoToggle } from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; import { TextStyle, addTextObject } from "./text"; import { addWindow } from "./ui-theme"; -import * as Utils from "../utils"; +import { getLocalizedSpriteKey, fixedInt } from "#app/utils"; import type Move from "../data/moves/move"; import { MoveCategory } from "#enums/MoveCategory"; import { PokemonType } from "#enums/pokemon-type"; @@ -120,7 +120,7 @@ export default class MoveInfoOverlay extends Phaser.GameObjects.Container implem valuesBg.setOrigin(0, 0); this.val.add(valuesBg); - this.typ = globalScene.add.sprite(25, EFF_HEIGHT - 35, Utils.getLocalizedSpriteKey("types"), "unknown"); + this.typ = globalScene.add.sprite(25, EFF_HEIGHT - 35, getLocalizedSpriteKey("types"), "unknown"); this.typ.setScale(0.8); this.val.add(this.typ); @@ -175,7 +175,7 @@ export default class MoveInfoOverlay extends Phaser.GameObjects.Container implem this.pow.setText(move.power >= 0 ? move.power.toString() : "---"); this.acc.setText(move.accuracy >= 0 ? move.accuracy.toString() : "---"); this.pp.setText(move.pp >= 0 ? move.pp.toString() : "---"); - this.typ.setTexture(Utils.getLocalizedSpriteKey("types"), PokemonType[move.type].toLowerCase()); + this.typ.setTexture(getLocalizedSpriteKey("types"), PokemonType[move.type].toLowerCase()); this.cat.setFrame(MoveCategory[move.category].toLowerCase()); this.desc.setText(move?.effect || ""); @@ -193,10 +193,10 @@ export default class MoveInfoOverlay extends Phaser.GameObjects.Container implem // generate scrolling effects this.descScroll = globalScene.tweens.add({ targets: this.desc, - delay: Utils.fixedInt(2000), + delay: fixedInt(2000), loop: -1, - hold: Utils.fixedInt(2000), - duration: Utils.fixedInt((moveDescriptionLineCount - 3) * 2000), + hold: fixedInt(2000), + duration: fixedInt((moveDescriptionLineCount - 3) * 2000), y: `-=${14.83 * (72 / 96) * (moveDescriptionLineCount - 3)}`, }); } @@ -219,7 +219,7 @@ export default class MoveInfoOverlay extends Phaser.GameObjects.Container implem } globalScene.tweens.add({ targets: this.desc, - duration: Utils.fixedInt(125), + duration: fixedInt(125), ease: "Sine.easeInOut", alpha: visible ? 1 : 0, }); diff --git a/src/ui/mystery-encounter-ui-handler.ts b/src/ui/mystery-encounter-ui-handler.ts index 87d2e2ba28c..2bf05302c55 100644 --- a/src/ui/mystery-encounter-ui-handler.ts +++ b/src/ui/mystery-encounter-ui-handler.ts @@ -6,8 +6,7 @@ import { addWindow, WindowVariant } from "./ui-theme"; import type { MysteryEncounterPhase } from "../phases/mystery-encounter-phases"; import { PartyUiMode } from "./party-ui-handler"; import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; -import * as Utils from "../utils"; -import { isNullOrUndefined } from "../utils"; +import { fixedInt, isNullOrUndefined } from "#app/utils"; import { getPokeballAtlasKey } from "../data/pokeball"; import type { OptionSelectSettings } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; @@ -456,10 +455,10 @@ export default class MysteryEncounterUiHandler extends UiHandler { if (optionTextWidth > nonScrollWidth) { this.optionScrollTweens[i] = globalScene.tweens.add({ targets: optionText, - delay: Utils.fixedInt(2000), + delay: fixedInt(2000), loop: -1, - hold: Utils.fixedInt(2000), - duration: Utils.fixedInt(((optionTextWidth - nonScrollWidth) / 15) * 2000), + hold: fixedInt(2000), + duration: fixedInt(((optionTextWidth - nonScrollWidth) / 15) * 2000), x: `-=${optionTextWidth - nonScrollWidth}`, }); } @@ -527,10 +526,10 @@ export default class MysteryEncounterUiHandler extends UiHandler { if (descriptionLineCount > 6) { this.descriptionScrollTween = globalScene.tweens.add({ targets: descriptionTextObject, - delay: Utils.fixedInt(2000), + delay: fixedInt(2000), loop: -1, - hold: Utils.fixedInt(2000), - duration: Utils.fixedInt((descriptionLineCount - 6) * 2000), + hold: fixedInt(2000), + duration: fixedInt((descriptionLineCount - 6) * 2000), y: `-=${10 * (descriptionLineCount - 6)}`, }); } @@ -637,10 +636,10 @@ export default class MysteryEncounterUiHandler extends UiHandler { if (tooltipLineCount > 3) { this.tooltipScrollTween = globalScene.tweens.add({ targets: tooltipTextObject, - delay: Utils.fixedInt(1200), + delay: fixedInt(1200), loop: -1, - hold: Utils.fixedInt(1200), - duration: Utils.fixedInt((tooltipLineCount - 3) * 1200), + hold: fixedInt(1200), + duration: fixedInt((tooltipLineCount - 3) * 1200), y: `-=${11.2 * (tooltipLineCount - 3)}`, }); } diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index ebaccc515c1..61a98d79fbb 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -5,7 +5,7 @@ import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "#ap import { Command } from "#app/ui/command-ui-handler"; import MessageUiHandler from "#app/ui/message-ui-handler"; import { Mode } from "#app/ui/ui"; -import * as Utils from "#app/utils"; +import { BooleanHolder, toReadableString, randInt, getLocalizedSpriteKey } from "#app/utils"; import { PokemonFormChangeItemModifier, PokemonHeldItemModifier, @@ -215,7 +215,7 @@ export default class PartyUiHandler extends MessageUiHandler { * @returns */ private FilterChallengeLegal = (pokemon: PlayerPokemon) => { - const challengeAllowed = new Utils.BooleanHolder(true); + const challengeAllowed = new BooleanHolder(true); applyChallenges(ChallengeType.POKEMON_IN_BATTLE, pokemon, challengeAllowed); if (!challengeAllowed.value) { return i18next.t("partyUiHandler:cantBeUsed", { @@ -1201,7 +1201,7 @@ export default class PartyUiHandler extends MessageUiHandler { if (this.localizedOptions.includes(option)) { optionName = i18next.t(`partyUiHandler:${PartyOption[option]}`); } else { - optionName = Utils.toReadableString(PartyOption[option]); + optionName = toReadableString(PartyOption[option]); } } break; @@ -1309,7 +1309,7 @@ export default class PartyUiHandler extends MessageUiHandler { } getReleaseMessage(pokemonName: string): string { - const rand = Utils.randInt(128); + const rand = randInt(128); if (rand < 20) { return i18next.t("partyUiHandler:goodbye", { pokemonName: pokemonName }); } @@ -1566,7 +1566,7 @@ class PartySlot extends Phaser.GameObjects.Container { } if (this.pokemon.status) { - const statusIndicator = globalScene.add.sprite(0, 0, Utils.getLocalizedSpriteKey("statuses")); + const statusIndicator = globalScene.add.sprite(0, 0, getLocalizedSpriteKey("statuses")); statusIndicator.setFrame(StatusEffect[this.pokemon.status?.effect].toLowerCase()); statusIndicator.setOrigin(0, 0); statusIndicator.setPositionRelative(slotLevelLabel, this.slotIndex >= battlerCount ? 43 : 55, 0); diff --git a/src/ui/pokedex-info-overlay.ts b/src/ui/pokedex-info-overlay.ts index 7dfa3745cb7..43e9bbc1a65 100644 --- a/src/ui/pokedex-info-overlay.ts +++ b/src/ui/pokedex-info-overlay.ts @@ -1,7 +1,7 @@ import type { InfoToggle } from "../battle-scene"; import { TextStyle, addTextObject } from "./text"; import { addWindow } from "./ui-theme"; -import * as Utils from "../utils"; +import { fixedInt } from "#app/utils"; import i18next from "i18next"; import { globalScene } from "#app/global-scene"; @@ -128,10 +128,10 @@ export default class PokedexInfoOverlay extends Phaser.GameObjects.Container imp // generate scrolling effects this.descScroll = globalScene.tweens.add({ targets: this.desc, - delay: Utils.fixedInt(2000), + delay: fixedInt(2000), loop: -1, - hold: Utils.fixedInt(2000), - duration: Utils.fixedInt((lineCount - 3) * 2000), + hold: fixedInt(2000), + duration: fixedInt((lineCount - 3) * 2000), y: `-=${14.83 * (72 / 96) * (lineCount - 3)}`, }); } @@ -154,7 +154,7 @@ export default class PokedexInfoOverlay extends Phaser.GameObjects.Container imp } globalScene.tweens.add({ targets: this.desc, - duration: Utils.fixedInt(125), + duration: fixedInt(125), ease: "Sine.easeInOut", alpha: visible ? 1 : 0, }); diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts index eede346f052..407ebfcd843 100644 --- a/src/ui/pokedex-page-ui-handler.ts +++ b/src/ui/pokedex-page-ui-handler.ts @@ -54,7 +54,7 @@ import { toReadableString, } from "#app/utils"; import type { Nature } from "#enums/nature"; -import * as Utils from "../utils"; +import { getEnumKeys } from "#app/utils"; import { speciesTmMoves } from "#app/data/balance/tms"; import type { BiomeTierTod } from "#app/data/balance/biomes"; import { BiomePoolTier, catchableSpecies } from "#app/data/balance/biomes"; @@ -592,7 +592,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler { this.menuContainer.setVisible(false); - this.menuOptions = Utils.getEnumKeys(MenuOptions).map(m => Number.parseInt(MenuOptions[m]) as MenuOptions); + this.menuOptions = getEnumKeys(MenuOptions).map(m => Number.parseInt(MenuOptions[m]) as MenuOptions); this.optionSelectText = addBBCodeTextObject( 0, @@ -696,7 +696,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler { this.starterAttributes = this.initStarterPrefs(); - this.menuOptions = Utils.getEnumKeys(MenuOptions).map(m => Number.parseInt(MenuOptions[m]) as MenuOptions); + this.menuOptions = getEnumKeys(MenuOptions).map(m => Number.parseInt(MenuOptions[m]) as MenuOptions); this.menuContainer.setVisible(true); diff --git a/src/ui/pokemon-hatch-info-container.ts b/src/ui/pokemon-hatch-info-container.ts index 99940b92351..692f0f1d374 100644 --- a/src/ui/pokemon-hatch-info-container.ts +++ b/src/ui/pokemon-hatch-info-container.ts @@ -1,7 +1,7 @@ import PokemonInfoContainer from "#app/ui/pokemon-info-container"; import { Gender } from "#app/data/gender"; import { PokemonType } from "#enums/pokemon-type"; -import * as Utils from "#app/utils"; +import { rgbHexToRgba, padInt } from "#app/utils"; import { TextStyle, addTextObject } from "#app/ui/text"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { allMoves } from "#app/data/moves/move"; @@ -154,14 +154,14 @@ export default class PokemonHatchInfoContainer extends PokemonInfoContainer { super.show(pokemon, false, 1, hatchInfo.getDex(), hatchInfo.getStarterEntry(), true); const colorScheme = starterColors[species.speciesId]; - this.pokemonCandyIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(colorScheme[0]))); + this.pokemonCandyIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[0]))); this.pokemonCandyIcon.setVisible(true); - this.pokemonCandyOverlayIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(colorScheme[1]))); + this.pokemonCandyOverlayIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[1]))); this.pokemonCandyOverlayIcon.setVisible(true); this.pokemonCandyCountText.setText(`x${globalScene.gameData.starterData[species.speciesId].candyCount}`); this.pokemonCandyCountText.setVisible(true); - this.pokemonNumberText.setText(Utils.padInt(species.speciesId, 4)); + this.pokemonNumberText.setText(padInt(species.speciesId, 4)); this.pokemonNameText.setText(species.name); const hasEggMoves = species && speciesEggMoves.hasOwnProperty(species.speciesId); diff --git a/src/ui/pokemon-icon-anim-handler.ts b/src/ui/pokemon-icon-anim-handler.ts index c84ee2a0f9a..b6944c0fd84 100644 --- a/src/ui/pokemon-icon-anim-handler.ts +++ b/src/ui/pokemon-icon-anim-handler.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import * as Utils from "../utils"; +import { fixedInt } from "#app/utils"; export enum PokemonIconAnimMode { NONE, @@ -27,7 +27,7 @@ export default class PokemonIconAnimHandler { } }; globalScene.tweens.addCounter({ - duration: Utils.fixedInt(200), + duration: fixedInt(200), from: 0, to: 1, yoyo: true, diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 1c880f6aec9..0ccece46ab9 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 * as Utils from "../utils"; +import { fixedInt } from "#app/utils"; import ConfirmUiHandler from "./confirm-ui-handler"; import { StatsContainer } from "./stats-container"; import { TextStyle, addBBCodeTextObject, addTextObject, getTextColor } from "./text"; @@ -393,7 +393,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { if (!eggInfo) { globalScene.tweens.add({ targets: this, - duration: Utils.fixedInt(Math.floor(750 / speedMultiplier)), + duration: fixedInt(Math.floor(750 / speedMultiplier)), ease: "Cubic.easeInOut", x: this.initialX - this.infoWindowWidth, onComplete: () => { @@ -403,9 +403,9 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { if (showMoves) { globalScene.tweens.add({ - delay: Utils.fixedInt(Math.floor(325 / speedMultiplier)), + delay: fixedInt(Math.floor(325 / speedMultiplier)), targets: this.pokemonMovesContainer, - duration: Utils.fixedInt(Math.floor(325 / speedMultiplier)), + duration: fixedInt(Math.floor(325 / speedMultiplier)), ease: "Cubic.easeInOut", x: this.movesContainerInitialX - 57, onComplete: () => resolve(), @@ -463,7 +463,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { return new Promise(resolve => { globalScene.tweens.add({ targets: this, - duration: Utils.fixedInt(Math.floor(150 / speedMultiplier)), + duration: fixedInt(Math.floor(150 / speedMultiplier)), ease: "Cubic.easeInOut", x: xPosition, onComplete: () => { @@ -482,14 +482,14 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { globalScene.tweens.add({ targets: this.pokemonMovesContainer, - duration: Utils.fixedInt(Math.floor(750 / speedMultiplier)), + duration: fixedInt(Math.floor(750 / speedMultiplier)), ease: "Cubic.easeInOut", x: this.movesContainerInitialX, }); globalScene.tweens.add({ targets: this, - duration: Utils.fixedInt(Math.floor(750 / speedMultiplier)), + duration: fixedInt(Math.floor(750 / speedMultiplier)), ease: "Cubic.easeInOut", x: this.initialX, onComplete: () => { diff --git a/src/ui/run-history-ui-handler.ts b/src/ui/run-history-ui-handler.ts index 85ea1e93e8d..ffc9d378d18 100644 --- a/src/ui/run-history-ui-handler.ts +++ b/src/ui/run-history-ui-handler.ts @@ -3,7 +3,7 @@ import { GameModes } from "../game-mode"; import { TextStyle, addTextObject } from "./text"; import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; -import * as Utils from "../utils"; +import { fixedInt, formatLargeNumber } from "#app/utils"; import type PokemonData from "../system/pokemon-data"; import MessageUiHandler from "./message-ui-handler"; import i18next from "i18next"; @@ -218,7 +218,7 @@ export default class RunHistoryUiHandler extends MessageUiHandler { globalScene.tweens.add({ targets: this.runsContainer, y: this.runContainerInitialY - 56 * scrollCursor, - duration: Utils.fixedInt(325), + duration: fixedInt(325), ease: "Sine.easeInOut", }); } @@ -314,7 +314,7 @@ class RunEntryContainer extends Phaser.GameObjects.Container { const enemyLevel = addTextObject( 32, 20, - `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(enemy.level, 1000)}`, + `${i18next.t("saveSlotSelectUiHandler:lv")}${formatLargeNumber(enemy.level, 1000)}`, TextStyle.PARTY, { fontSize: "54px", color: "#f8f8f8" }, ); @@ -408,7 +408,7 @@ class RunEntryContainer extends Phaser.GameObjects.Container { const text = addTextObject( 32, 20, - `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(pokemon.level, 1000)}`, + `${i18next.t("saveSlotSelectUiHandler:lv")}${formatLargeNumber(pokemon.level, 1000)}`, TextStyle.PARTY, { fontSize: "54px", color: "#f8f8f8" }, ); diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index 8719950381a..47de6a1a64d 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -5,7 +5,7 @@ import { TextStyle, addTextObject, addBBCodeTextObject, getTextColor } from "./t import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; import { getPokeballAtlasKey } from "#app/data/pokeball"; -import * as Utils from "../utils"; +import { formatLargeNumber, getPlayTimeString, formatMoney, formatFancyLargeNumber } from "#app/utils"; import type PokemonData from "../system/pokemon-data"; import i18next from "i18next"; import { Button } from "../enums/buttons"; @@ -19,7 +19,8 @@ import { PokemonType } from "#enums/pokemon-type"; import { TypeColor, TypeShadow } from "#app/enums/color"; import { getNatureStatMultiplier, getNatureName } from "../data/nature"; import { getVariantTint } from "#app/sprites/variant"; -import * as Modifier from "../modifier/modifier"; +// biome-ignore lint/style/noNamespaceImport: See `src/system/game-data.ts` +import * as Modifier from "#app/modifier/modifier"; import type { Species } from "#enums/species"; import { PlayerGender } from "#enums/player-gender"; import { SettingKeyboard } from "#app/system/settings/settings-keyboard"; @@ -411,7 +412,7 @@ export default class RunInfoUiHandler extends UiHandler { const enemyLevel = addTextObject( 36, 26, - `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(enemy.level, 1000)}`, + `${i18next.t("saveSlotSelectUiHandler:lv")}${formatLargeNumber(enemy.level, 1000)}`, enemyLevelStyle, { fontSize: "44px", color: "#f8f8f8" }, ); @@ -441,7 +442,7 @@ export default class RunInfoUiHandler extends UiHandler { const enemyLevel = addTextObject( 36, 26, - `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(enemy.level, 1000)}`, + `${i18next.t("saveSlotSelectUiHandler:lv")}${formatLargeNumber(enemy.level, 1000)}`, bossStatus ? TextStyle.PARTY_RED : TextStyle.PARTY, { fontSize: "44px", color: "#f8f8f8" }, ); @@ -527,7 +528,7 @@ export default class RunInfoUiHandler extends UiHandler { const enemyLevel = addTextObject( 43 * (e % 3), 27 * (pokemonRowHeight + 1), - `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(enemy.level, 1000)}`, + `${i18next.t("saveSlotSelectUiHandler:lv")}${formatLargeNumber(enemy.level, 1000)}`, isBoss ? TextStyle.PARTY_RED : TextStyle.PARTY, { fontSize: "54px" }, ); @@ -606,9 +607,9 @@ export default class RunInfoUiHandler extends UiHandler { fontSize: "50px", lineSpacing: lineSpacing, }); - const runTime = Utils.getPlayTimeString(this.runInfo.playTime); + const runTime = getPlayTimeString(this.runInfo.playTime); runInfoText.appendText(`${i18next.t("runHistory:runLength")}: ${runTime}`, false); - const runMoney = Utils.formatMoney(globalScene.moneyFormat, this.runInfo.money); + const runMoney = formatMoney(globalScene.moneyFormat, this.runInfo.money); const moneyTextColor = getTextColor(TextStyle.MONEY_WINDOW, false, globalScene.uiTheme); runInfoText.appendText( `[color=${moneyTextColor}]${i18next.t("battleScene:moneyOwned", { formattedMoney: runMoney })}[/color]`, @@ -770,7 +771,7 @@ export default class RunInfoUiHandler extends UiHandler { lineSpacing: lineSpacing, }); pokeInfoText.appendText( - `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatFancyLargeNumber(pokemon.level, 1)} - ${pNatureName}`, + `${i18next.t("saveSlotSelectUiHandler:lv")}${formatFancyLargeNumber(pokemon.level, 1)} - ${pNatureName}`, ); pokeInfoText.appendText(pAbilityInfo); pokeInfoText.appendText(pPassiveInfo); @@ -780,7 +781,7 @@ export default class RunInfoUiHandler extends UiHandler { // Colored Arrows (Red/Blue) are placed by stats that are boosted from natures const pokeStatTextContainer = globalScene.add.container(-35, 6); const pStats: string[] = []; - pokemon.stats.forEach(element => pStats.push(Utils.formatFancyLargeNumber(element, 1))); + pokemon.stats.forEach(element => pStats.push(formatFancyLargeNumber(element, 1))); for (let i = 0; i < pStats.length; i++) { const isMult = getNatureStatMultiplier(pNature, i); pStats[i] = isMult < 1 ? pStats[i] + "[color=#40c8f8]↓[/color]" : pStats[i]; diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index a1e9e5219b4..0c16e41bbef 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -2,10 +2,11 @@ import i18next from "i18next"; import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; import { GameMode } from "../game-mode"; -import * as Modifier from "../modifier/modifier"; +// biome-ignore lint/style/noNamespaceImport: See `src/system/game-data.ts` +import * as Modifier from "#app/modifier/modifier"; import type { SessionSaveData } from "../system/game-data"; import type PokemonData from "../system/pokemon-data"; -import * as Utils from "../utils"; +import { isNullOrUndefined, fixedInt, getPlayTimeString, formatLargeNumber } from "#app/utils"; import MessageUiHandler from "./message-ui-handler"; import { TextStyle, addTextObject } from "./text"; import { Mode } from "./ui"; @@ -296,7 +297,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { } this.setArrowVisibility(hasData); } - if (!Utils.isNullOrUndefined(prevSlotIndex)) { + if (!isNullOrUndefined(prevSlotIndex)) { this.revertSessionSlot(prevSlotIndex); } @@ -339,7 +340,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { globalScene.tweens.add({ targets: this.sessionSlotsContainer, y: this.sessionSlotsContainerInitialY - 56 * scrollCursor, - duration: Utils.fixedInt(325), + duration: fixedInt(325), ease: "Sine.easeInOut", }); } @@ -407,7 +408,7 @@ class SessionSlot extends Phaser.GameObjects.Container { const timestampLabel = addTextObject(8, 19, new Date(data.timestamp).toLocaleString(), TextStyle.WINDOW); this.add(timestampLabel); - const playTimeLabel = addTextObject(8, 33, Utils.getPlayTimeString(data.playTime), TextStyle.WINDOW); + const playTimeLabel = addTextObject(8, 33, getPlayTimeString(data.playTime), TextStyle.WINDOW); this.add(playTimeLabel); const pokemonIconsContainer = globalScene.add.container(144, 4); @@ -421,7 +422,7 @@ class SessionSlot extends Phaser.GameObjects.Container { const text = addTextObject( 32, 20, - `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(pokemon.level, 1000)}`, + `${i18next.t("saveSlotSelectUiHandler:lv")}${formatLargeNumber(pokemon.level, 1000)}`, TextStyle.PARTY, { fontSize: "54px", color: "#f8f8f8" }, ); diff --git a/src/ui/saving-icon-handler.ts b/src/ui/saving-icon-handler.ts index 4404ea423b1..3db84f128a1 100644 --- a/src/ui/saving-icon-handler.ts +++ b/src/ui/saving-icon-handler.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import * as Utils from "../utils"; +import { fixedInt } from "#app/utils"; export default class SavingIconHandler extends Phaser.GameObjects.Container { private icon: Phaser.GameObjects.Sprite; @@ -36,10 +36,10 @@ export default class SavingIconHandler extends Phaser.GameObjects.Container { globalScene.tweens.add({ targets: this, alpha: 1, - duration: Utils.fixedInt(250), + duration: fixedInt(250), ease: "Sine.easeInOut", onComplete: () => { - globalScene.time.delayedCall(Utils.fixedInt(500), () => { + globalScene.time.delayedCall(fixedInt(500), () => { this.animActive = false; if (!this.shown) { this.hide(); @@ -64,7 +64,7 @@ export default class SavingIconHandler extends Phaser.GameObjects.Container { globalScene.tweens.add({ targets: this, alpha: 0, - duration: Utils.fixedInt(250), + duration: fixedInt(250), ease: "Sine.easeInOut", onComplete: () => { this.animActive = false; diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 3876f2585db..3e2940f45b9 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -43,7 +43,7 @@ import { Egg } from "#app/data/egg"; import Overrides from "#app/overrides"; import { SettingKeyboard } from "#app/system/settings/settings-keyboard"; import { Passive as PassiveAttr } from "#enums/passive"; -import * as Challenge from "#app/data/challenge"; +import { applyChallenges, ChallengeType } from "#app/data/challenge"; import MoveInfoOverlay from "#app/ui/move-info-overlay"; import { getEggTierForSpecies } from "#app/data/egg"; import { Device } from "#enums/devices"; @@ -78,7 +78,6 @@ import { import type { Nature } from "#enums/nature"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; import { achvs } from "#app/system/achv"; -import * as Utils from "../utils"; import type { GameObjects } from "phaser"; import { checkStarterValidForChallenge } from "#app/data/challenge"; @@ -2518,7 +2517,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { case Button.CYCLE_TERA: if (this.canCycleTera) { const speciesForm = getPokemonSpeciesForm(this.lastSpecies.speciesId, starterAttributes.form ?? 0); - if (speciesForm.type1 === this.teraCursor && !Utils.isNullOrUndefined(speciesForm.type2)) { + if (speciesForm.type1 === this.teraCursor && !isNullOrUndefined(speciesForm.type2)) { starterAttributes.tera = speciesForm.type2!; this.setSpeciesDetails(this.lastSpecies, { teraType: speciesForm.type2!, @@ -2960,7 +2959,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { valueLimit.value = 10; } - Challenge.applyChallenges(Challenge.ChallengeType.STARTER_POINTS, valueLimit); + applyChallenges(ChallengeType.STARTER_POINTS, valueLimit); return valueLimit.value; } @@ -3748,7 +3747,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { ); // TODO: is this bang correct? this.abilityCursor = abilityIndex !== undefined ? abilityIndex : (abilityIndex = oldAbilityIndex); this.natureCursor = natureIndex !== undefined ? natureIndex : (natureIndex = oldNatureIndex); - this.teraCursor = !Utils.isNullOrUndefined(teraType) ? teraType : (teraType = species.type1); + this.teraCursor = !isNullOrUndefined(teraType) ? teraType : (teraType = species.type1); const [isInParty, partyIndex]: [boolean, number] = this.isInParty(species); // we use this to firstly check if the pokemon is in the party, and if so, to get the party index in order to update the icon image if (isInParty) { this.updatePartyIcon(species, partyIndex); @@ -3886,7 +3885,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.canCycleTera = !this.statsMode && globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id) && - !Utils.isNullOrUndefined(getPokemonSpeciesForm(species.speciesId, formIndex ?? 0).type2); + !isNullOrUndefined(getPokemonSpeciesForm(species.speciesId, formIndex ?? 0).type2); } if (dexEntry.caughtAttr && species.malePercent !== null) { @@ -4483,7 +4482,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.canCycleTera = !this.statsMode && globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id) && - !Utils.isNullOrUndefined(getPokemonSpeciesForm(this.lastSpecies.speciesId, formIndex ?? 0).type2); + !isNullOrUndefined(getPokemonSpeciesForm(this.lastSpecies.speciesId, formIndex ?? 0).type2); this.updateInstructions(); } } diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index aa3d014bd95..1e0924aa2c5 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -2,7 +2,16 @@ import { starterColors } from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; import { Mode } from "#app/ui/ui"; import UiHandler from "#app/ui/ui-handler"; -import * as Utils from "#app/utils"; +import { + getLocalizedSpriteKey, + rgbHexToRgba, + padInt, + getEnumValues, + fixedInt, + isNullOrUndefined, + toReadableString, + formatStat, +} from "#app/utils"; import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { argbFromRgba } from "@material/material-color-utilities"; @@ -255,7 +264,7 @@ export default class SummaryUiHandler extends UiHandler { this.statusContainer.add(statusLabel); - this.status = globalScene.add.sprite(91, 4, Utils.getLocalizedSpriteKey("statuses")); + this.status = globalScene.add.sprite(91, 4, getLocalizedSpriteKey("statuses")); this.status.setOrigin(0.5, 0); this.statusContainer.add(this.status); @@ -330,10 +339,10 @@ export default class SummaryUiHandler extends UiHandler { this.shinyOverlay.setVisible(this.pokemon.isShiny()); const colorScheme = starterColors[this.pokemon.species.getRootSpeciesId()]; - this.candyIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(colorScheme[0]))); - this.candyOverlay.setTint(argbFromRgba(Utils.rgbHexToRgba(colorScheme[1]))); + this.candyIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[0]))); + this.candyOverlay.setTint(argbFromRgba(rgbHexToRgba(colorScheme[1]))); - this.numberText.setText(Utils.padInt(this.pokemon.species.speciesId, 4)); + this.numberText.setText(padInt(this.pokemon.species.speciesId, 4)); this.numberText.setColor(this.getTextColor(!this.pokemon.isShiny() ? TextStyle.SUMMARY : TextStyle.SUMMARY_GOLD)); this.numberText.setShadowColor( this.getTextColor(!this.pokemon.isShiny() ? TextStyle.SUMMARY : TextStyle.SUMMARY_GOLD, true), @@ -600,7 +609,7 @@ export default class SummaryUiHandler extends UiHandler { } success = true; } else { - const pages = Utils.getEnumValues(Page); + const pages = getEnumValues(Page); switch (button) { case Button.UP: case Button.DOWN: { @@ -675,10 +684,10 @@ export default class SummaryUiHandler extends UiHandler { if (moveDescriptionLineCount > 3) { this.descriptionScrollTween = globalScene.tweens.add({ targets: this.moveDescriptionText, - delay: Utils.fixedInt(2000), + delay: fixedInt(2000), loop: -1, - hold: Utils.fixedInt(2000), - duration: Utils.fixedInt((moveDescriptionLineCount - 3) * 2000), + hold: fixedInt(2000), + duration: fixedInt((moveDescriptionLineCount - 3) * 2000), y: `-=${14.83 * (moveDescriptionLineCount - 3)}`, }); } @@ -697,10 +706,10 @@ export default class SummaryUiHandler extends UiHandler { this.moveCursorObj.setVisible(true); this.moveCursorBlinkTimer = globalScene.time.addEvent({ loop: true, - delay: Utils.fixedInt(600), + delay: fixedInt(600), callback: () => { this.moveCursorObj?.setVisible(false); - globalScene.time.delayedCall(Utils.fixedInt(100), () => { + globalScene.time.delayedCall(fixedInt(100), () => { if (!this.moveCursorObj) { return; } @@ -818,7 +827,7 @@ export default class SummaryUiHandler extends UiHandler { const getTypeIcon = (index: number, type: PokemonType, tera = false) => { const xCoord = typeLabel.width * typeLabel.scale + 9 + 34 * index; const typeIcon = !tera - ? globalScene.add.sprite(xCoord, 42, Utils.getLocalizedSpriteKey("types"), PokemonType[type].toLowerCase()) + ? globalScene.add.sprite(xCoord, 42, getLocalizedSpriteKey("types"), PokemonType[type].toLowerCase()) : globalScene.add.sprite(xCoord, 42, "type_tera"); if (tera) { typeIcon.setScale(0.5); @@ -853,7 +862,7 @@ export default class SummaryUiHandler extends UiHandler { if ( globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id) && - !Utils.isNullOrUndefined(this.pokemon) + !isNullOrUndefined(this.pokemon) ) { const teraIcon = globalScene.add.sprite(123, 26, "button_tera"); teraIcon.setName("terrastallize-icon"); @@ -925,10 +934,10 @@ export default class SummaryUiHandler extends UiHandler { abilityInfo.descriptionText.setY(69); this.descriptionScrollTween = globalScene.tweens.add({ targets: abilityInfo.descriptionText, - delay: Utils.fixedInt(2000), + delay: fixedInt(2000), loop: -1, - hold: Utils.fixedInt(2000), - duration: Utils.fixedInt((abilityDescriptionLineCount - 2) * 2000), + hold: fixedInt(2000), + duration: fixedInt((abilityDescriptionLineCount - 2) * 2000), y: `-=${14.83 * (abilityDescriptionLineCount - 2)}`, }); } @@ -939,8 +948,8 @@ export default class SummaryUiHandler extends UiHandler { this.passiveContainer?.descriptionText?.setVisible(false); const closeFragment = getBBCodeFrag("", TextStyle.WINDOW_ALT); - const rawNature = Utils.toReadableString(Nature[this.pokemon?.getNature()!]); // TODO: is this bang correct? - const nature = `${getBBCodeFrag(Utils.toReadableString(getNatureName(this.pokemon?.getNature()!)), TextStyle.SUMMARY_RED)}${closeFragment}`; // TODO: is this bang correct? + const rawNature = toReadableString(Nature[this.pokemon?.getNature()!]); // TODO: is this bang correct? + const nature = `${getBBCodeFrag(toReadableString(getNatureName(this.pokemon?.getNature()!)), TextStyle.SUMMARY_RED)}${closeFragment}`; // TODO: is this bang correct? const memoString = i18next.t("pokemonSummary:memoString", { metFragment: i18next.t( @@ -999,8 +1008,8 @@ export default class SummaryUiHandler extends UiHandler { const statValueText = stat !== Stat.HP - ? Utils.formatStat(this.pokemon?.getStat(stat)!) // TODO: is this bang correct? - : `${Utils.formatStat(this.pokemon?.hp!, true)}/${Utils.formatStat(this.pokemon?.getMaxHp()!, true)}`; // TODO: are those bangs correct? + ? formatStat(this.pokemon?.getStat(stat)!) // TODO: is this bang correct? + : `${formatStat(this.pokemon?.hp!, true)}/${formatStat(this.pokemon?.getMaxHp()!, true)}`; // TODO: are those bangs correct? const ivText = `${this.pokemon?.ivs[stat]}/31`; const statValue = addTextObject(93 + 88 * colIndex, 16 * rowIndex, statValueText, TextStyle.WINDOW_ALT); @@ -1106,7 +1115,7 @@ export default class SummaryUiHandler extends UiHandler { this.extraMoveRowContainer.setVisible(true); if (this.newMove && this.pokemon) { - const spriteKey = Utils.getLocalizedSpriteKey("types"); + const spriteKey = getLocalizedSpriteKey("types"); const moveType = this.pokemon.getMoveType(this.newMove); const newMoveTypeIcon = globalScene.add.sprite(0, 0, spriteKey, PokemonType[moveType].toLowerCase()); newMoveTypeIcon.setOrigin(0, 1); @@ -1116,7 +1125,7 @@ export default class SummaryUiHandler extends UiHandler { ppOverlay.setOrigin(0, 1); this.extraMoveRowContainer.add(ppOverlay); - const pp = Utils.padInt(this.newMove?.pp!, 2, " "); // TODO: is this bang correct? + const pp = padInt(this.newMove?.pp!, 2, " "); // TODO: is this bang correct? const ppText = addTextObject(173, 1, `${pp}/${pp}`, TextStyle.WINDOW); ppText.setOrigin(0, 1); this.extraMoveRowContainer.add(ppText); @@ -1132,7 +1141,7 @@ export default class SummaryUiHandler extends UiHandler { this.moveRowsContainer.add(moveRowContainer); if (move && this.pokemon) { - const spriteKey = Utils.getLocalizedSpriteKey("types"); + const spriteKey = getLocalizedSpriteKey("types"); const moveType = this.pokemon.getMoveType(move.getMove()); const typeIcon = globalScene.add.sprite(0, 0, spriteKey, PokemonType[moveType].toLowerCase()); typeIcon.setOrigin(0, 1); @@ -1153,7 +1162,7 @@ export default class SummaryUiHandler extends UiHandler { if (move) { const maxPP = move.getMovePp(); const pp = maxPP - move.ppUsed; - ppText.setText(`${Utils.padInt(pp, 2, " ")}/${Utils.padInt(maxPP, 2, " ")}`); + ppText.setText(`${padInt(pp, 2, " ")}/${padInt(maxPP, 2, " ")}`); } moveRowContainer.add(ppText); diff --git a/src/ui/target-select-ui-handler.ts b/src/ui/target-select-ui-handler.ts index d2f72ef4a4c..a9f88b337f3 100644 --- a/src/ui/target-select-ui-handler.ts +++ b/src/ui/target-select-ui-handler.ts @@ -1,7 +1,7 @@ import { BattlerIndex } from "../battle"; import { Mode } from "./ui"; import UiHandler from "./ui-handler"; -import * as Utils from "../utils"; +import { isNullOrUndefined, fixedInt } from "#app/utils"; import { getMoveTargets } from "../data/moves/move"; import { Button } from "#enums/buttons"; import type { Moves } from "#enums/moves"; @@ -70,7 +70,7 @@ export default class TargetSelectUiHandler extends UiHandler { * @param user the Pokemon using the move */ resetCursor(cursorN: number, user: Pokemon): void { - if (!Utils.isNullOrUndefined(cursorN)) { + if (!isNullOrUndefined(cursorN)) { if ([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2].includes(cursorN) || user.battleSummonData.waveTurnCount === 1) { // Reset cursor on the first turn of a fight or if an ally was targeted last turn cursorN = -1; @@ -89,11 +89,11 @@ export default class TargetSelectUiHandler extends UiHandler { this.targetSelectCallback(button === Button.ACTION ? targetIndexes : []); success = true; if (this.fieldIndex === BattlerIndex.PLAYER) { - if (Utils.isNullOrUndefined(this.cursor0) || this.cursor0 !== this.cursor) { + if (isNullOrUndefined(this.cursor0) || this.cursor0 !== this.cursor) { this.cursor0 = this.cursor; } } else if (this.fieldIndex === BattlerIndex.PLAYER_2) { - if (Utils.isNullOrUndefined(this.cursor1) || this.cursor1 !== this.cursor) { + if (isNullOrUndefined(this.cursor1) || this.cursor1 !== this.cursor) { this.cursor1 = this.cursor; } } @@ -152,7 +152,7 @@ export default class TargetSelectUiHandler extends UiHandler { key: { start: 1, to: 0.25 }, loop: -1, loopDelay: 150, - duration: Utils.fixedInt(450), + duration: fixedInt(450), ease: "Sine.easeInOut", yoyo: true, onUpdate: t => { @@ -178,7 +178,7 @@ export default class TargetSelectUiHandler extends UiHandler { targets: [info], y: { start: info.getBaseY(), to: info.getBaseY() + 1 }, loop: -1, - duration: Utils.fixedInt(250), + duration: fixedInt(250), ease: "Linear", yoyo: true, }), diff --git a/src/ui/time-of-day-widget.ts b/src/ui/time-of-day-widget.ts index bda1f750cb1..5e42e6215f8 100644 --- a/src/ui/time-of-day-widget.ts +++ b/src/ui/time-of-day-widget.ts @@ -1,4 +1,4 @@ -import * as Utils from "../utils"; +import { fixedInt } from "#app/utils"; import { globalScene } from "#app/global-scene"; import { BattleSceneEventType } from "../events/battle-scene"; import { EaseType } from "#enums/ease-type"; @@ -75,14 +75,14 @@ export default class TimeOfDayWidget extends Phaser.GameObjects.Container { const rotate = { targets: [this.timeOfDayIconMgs[0], this.timeOfDayIconMgs[1]], angle: "+=90", - duration: Utils.fixedInt(1500), + duration: fixedInt(1500), ease: "Back.easeOut", paused: !this.parentVisible, }; const fade = { targets: [this.timeOfDayIconBgs[1], this.timeOfDayIconMgs[1], this.timeOfDayIconFgs[1]], alpha: 0, - duration: Utils.fixedInt(500), + duration: fixedInt(500), ease: "Linear", paused: !this.parentVisible, }; @@ -98,14 +98,14 @@ export default class TimeOfDayWidget extends Phaser.GameObjects.Container { const bounce = { targets: [this.timeOfDayIconMgs[0], this.timeOfDayIconMgs[1]], angle: "+=90", - duration: Utils.fixedInt(2000), + duration: fixedInt(2000), ease: "Bounce.easeOut", paused: !this.parentVisible, }; const fade = { targets: [this.timeOfDayIconBgs[1], this.timeOfDayIconMgs[1], this.timeOfDayIconFgs[1]], alpha: 0, - duration: Utils.fixedInt(800), + duration: fixedInt(800), ease: "Linear", paused: !this.parentVisible, }; diff --git a/src/ui/title-ui-handler.ts b/src/ui/title-ui-handler.ts index d87d4e5ca79..405e3cc4a27 100644 --- a/src/ui/title-ui-handler.ts +++ b/src/ui/title-ui-handler.ts @@ -1,6 +1,6 @@ import OptionSelectUiHandler from "./settings/option-select-ui-handler"; import { Mode } from "./ui"; -import * as Utils from "../utils"; +import { fixedInt, randInt, randItem } from "#app/utils"; import { TextStyle, addTextObject } from "./text"; import { getSplashMessages } from "../data/splash-messages"; import i18next from "i18next"; @@ -72,7 +72,7 @@ export default class TitleUiHandler extends OptionSelectUiHandler { globalScene.tweens.add({ targets: this.splashMessageText, - duration: Utils.fixedInt(350), + duration: fixedInt(350), scale: originalSplashMessageScale * 1.25, loop: -1, yoyo: true, @@ -104,7 +104,7 @@ export default class TitleUiHandler extends OptionSelectUiHandler { /** Used solely to display a random Pokémon name in a splash message. */ randomPokemon(): void { - const rand = Utils.randInt(1025, 1); + const rand = randInt(1025, 1); const pokemon = getPokemonSpecies(rand as Species); if ( this.splashMessage === "splashMessages:underratedPokemon" || @@ -132,7 +132,7 @@ export default class TitleUiHandler extends OptionSelectUiHandler { // Moving player count to top of the menu this.playerCountLabel.setY(globalScene.game.canvas.height / 6 - 13 - this.getWindowHeight()); - this.splashMessage = Utils.randItem(getSplashMessages()); + this.splashMessage = randItem(getSplashMessages()); this.splashMessageText.setText( i18next.t(this.splashMessage, { count: TitleUiHandler.BATTLES_WON_FALLBACK, @@ -159,7 +159,7 @@ export default class TitleUiHandler extends OptionSelectUiHandler { globalScene.tweens.add({ targets: [this.titleContainer, ui.getMessageHandler().bg], - duration: Utils.fixedInt(325), + duration: fixedInt(325), alpha: (target: any) => (target === this.titleContainer ? 1 : 0), ease: "Sine.easeInOut", }); @@ -180,7 +180,7 @@ export default class TitleUiHandler extends OptionSelectUiHandler { globalScene.tweens.add({ targets: [this.titleContainer, ui.getMessageHandler().bg], - duration: Utils.fixedInt(325), + duration: fixedInt(325), alpha: (target: any) => (target === this.titleContainer ? 0 : 1), ease: "Sine.easeInOut", }); diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 6605e5ef730..c7981cd5fba 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -28,7 +28,7 @@ import { addWindow } from "./ui-theme"; import LoginFormUiHandler from "./login-form-ui-handler"; import RegistrationFormUiHandler from "./registration-form-ui-handler"; import LoadingModalUiHandler from "./loading-modal-ui-handler"; -import * as Utils from "../utils"; +import { executeIf } from "#app/utils"; import GameStatsUiHandler from "./game-stats-ui-handler"; import AwaitableUiHandler from "./awaitable-ui-handler"; import SaveSlotSelectUiHandler from "./save-slot-select-ui-handler"; @@ -674,7 +674,7 @@ export default class UI extends Phaser.GameObjects.Container { if (!this?.modeChain?.length) { return resolve(); } - this.revertMode().then(success => Utils.executeIf(success, this.revertModes).then(() => resolve())); + this.revertMode().then(success => executeIf(success, this.revertModes).then(() => resolve())); }); } diff --git a/src/ui/unavailable-modal-ui-handler.ts b/src/ui/unavailable-modal-ui-handler.ts index 3007f7247f1..01ed850f6d0 100644 --- a/src/ui/unavailable-modal-ui-handler.ts +++ b/src/ui/unavailable-modal-ui-handler.ts @@ -3,7 +3,7 @@ import { ModalUiHandler } from "./modal-ui-handler"; import { addTextObject, TextStyle } from "./text"; import type { Mode } from "./ui"; import { updateUserInfo } from "#app/account"; -import * as Utils from "#app/utils"; +import { removeCookie, sessionIdKey } from "#app/utils"; import i18next from "i18next"; import { globalScene } from "#app/global-scene"; @@ -65,7 +65,7 @@ export default class UnavailableModalUiHandler extends ModalUiHandler { globalScene.playSound("se/pb_bounce_1"); this.reconnectCallback(); } else if (response[1] === 401) { - Utils.removeCookie(Utils.sessionIdKey); + removeCookie(sessionIdKey); globalScene.reset(true, true); } else { this.reconnectDuration = Math.min(this.reconnectDuration * 2, this.maxTime); // Set a max delay so it isn't infinite diff --git a/test/escape-calculations.test.ts b/test/escape-calculations.test.ts index 0cbf11dd230..b4504c7359c 100644 --- a/test/escape-calculations.test.ts +++ b/test/escape-calculations.test.ts @@ -1,7 +1,7 @@ import { AttemptRunPhase } from "#app/phases/attempt-run-phase"; import type { CommandPhase } from "#app/phases/command-phase"; import { Command } from "#app/ui/command-ui-handler"; -import * as Utils from "#app/utils"; +import { NumberHolder } from "#app/utils"; import { Abilities } from "#enums/abilities"; import { Species } from "#enums/species"; import GameManager from "#test/testUtils/gameManager"; @@ -45,7 +45,7 @@ describe("Escape chance calculations", () => { await game.phaseInterceptor.to(AttemptRunPhase, false); const phase = game.scene.getCurrentPhase() as AttemptRunPhase; - const escapePercentage = new Utils.NumberHolder(0); + const escapePercentage = new NumberHolder(0); // this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping const escapeChances: { @@ -118,7 +118,7 @@ describe("Escape chance calculations", () => { await game.phaseInterceptor.to(AttemptRunPhase, false); const phase = game.scene.getCurrentPhase() as AttemptRunPhase; - const escapePercentage = new Utils.NumberHolder(0); + const escapePercentage = new NumberHolder(0); // this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping const escapeChances: { @@ -197,7 +197,7 @@ describe("Escape chance calculations", () => { await game.phaseInterceptor.to(AttemptRunPhase, false); const phase = game.scene.getCurrentPhase() as AttemptRunPhase; - const escapePercentage = new Utils.NumberHolder(0); + const escapePercentage = new NumberHolder(0); // this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping const escapeChances: { @@ -284,7 +284,7 @@ describe("Escape chance calculations", () => { await game.phaseInterceptor.to(AttemptRunPhase, false); const phase = game.scene.getCurrentPhase() as AttemptRunPhase; - const escapePercentage = new Utils.NumberHolder(0); + const escapePercentage = new NumberHolder(0); // this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping const escapeChances: { diff --git a/test/items/exp_booster.test.ts b/test/items/exp_booster.test.ts index e4491b22637..2b1308f1afb 100644 --- a/test/items/exp_booster.test.ts +++ b/test/items/exp_booster.test.ts @@ -1,6 +1,6 @@ import { Abilities } from "#app/enums/abilities"; import { PokemonExpBoosterModifier } from "#app/modifier/modifier"; -import * as Utils from "#app/utils"; +import { NumberHolder } from "#app/utils"; import GameManager from "#test/testUtils/gameManager"; import Phase from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -33,7 +33,7 @@ describe("EXP Modifier Items", () => { const partyMember = game.scene.getPlayerPokemon()!; partyMember.exp = 100; - const expHolder = new Utils.NumberHolder(partyMember.exp); + const expHolder = new NumberHolder(partyMember.exp); game.scene.applyModifiers(PokemonExpBoosterModifier, true, partyMember, expHolder); expect(expHolder.value).toBe(440); }, 20000); diff --git a/test/items/leek.test.ts b/test/items/leek.test.ts index ec4d075fe19..afb31a5f9fa 100644 --- a/test/items/leek.test.ts +++ b/test/items/leek.test.ts @@ -1,5 +1,5 @@ import { TurnEndPhase } from "#app/phases/turn-end-phase"; -import * as Utils from "#app/utils"; +import { randInt } from "#app/utils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/testUtils/gameManager"; @@ -78,7 +78,7 @@ describe("Items - Leek", () => { // Randomly choose from the Farfetch'd line const species = [Species.FARFETCHD, Species.GALAR_FARFETCHD, Species.SIRFETCHD]; - await game.startBattle([species[Utils.randInt(species.length)], Species.PIKACHU]); + await game.startBattle([species[randInt(species.length)], Species.PIKACHU]); const [partyMember, ally] = game.scene.getPlayerParty(); @@ -106,7 +106,7 @@ describe("Items - Leek", () => { // Randomly choose from the Farfetch'd line const species = [Species.FARFETCHD, Species.GALAR_FARFETCHD, Species.SIRFETCHD]; - await game.startBattle([Species.PIKACHU, species[Utils.randInt(species.length)]]); + await game.startBattle([Species.PIKACHU, species[randInt(species.length)]]); const [partyMember, ally] = game.scene.getPlayerParty(); diff --git a/test/items/light_ball.test.ts b/test/items/light_ball.test.ts index e4959002904..1f5227142eb 100644 --- a/test/items/light_ball.test.ts +++ b/test/items/light_ball.test.ts @@ -2,7 +2,7 @@ import { Stat } from "#enums/stat"; import { SpeciesStatBoosterModifier } from "#app/modifier/modifier"; import { modifierTypes } from "#app/modifier/modifier-type"; import i18next from "#app/plugins/i18n"; -import * as Utils from "#app/utils"; +import { NumberHolder } from "#app/utils"; import { Species } from "#enums/species"; import GameManager from "#test/testUtils/gameManager"; import Phase from "phaser"; @@ -90,9 +90,9 @@ describe("Items - Light Ball", () => { const spAtkStat = partyMember.getStat(Stat.SPATK); // Making sure modifier is not applied without holding item - const atkValue = new Utils.NumberHolder(atkStat); + const atkValue = new NumberHolder(atkStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue); - const spAtkValue = new Utils.NumberHolder(spAtkStat); + const spAtkValue = new NumberHolder(spAtkStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue); expect(atkValue.value / atkStat).toBe(1); @@ -129,9 +129,9 @@ describe("Items - Light Ball", () => { const spAtkStat = partyMember.getStat(Stat.SPATK); // Making sure modifier is not applied without holding item - const atkValue = new Utils.NumberHolder(atkStat); + const atkValue = new NumberHolder(atkStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue); - const spAtkValue = new Utils.NumberHolder(spAtkStat); + const spAtkValue = new NumberHolder(spAtkStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue); expect(atkValue.value / atkStat).toBe(1); @@ -168,9 +168,9 @@ describe("Items - Light Ball", () => { const spAtkStat = partyMember.getStat(Stat.SPATK); // Making sure modifier is not applied without holding item - const atkValue = new Utils.NumberHolder(atkStat); + const atkValue = new NumberHolder(atkStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue); - const spAtkValue = new Utils.NumberHolder(spAtkStat); + const spAtkValue = new NumberHolder(spAtkStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue); expect(atkValue.value / atkStat).toBe(1); @@ -197,9 +197,9 @@ describe("Items - Light Ball", () => { const spAtkStat = partyMember.getStat(Stat.SPATK); // Making sure modifier is not applied without holding item - const atkValue = new Utils.NumberHolder(atkStat); + const atkValue = new NumberHolder(atkStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue); - const spAtkValue = new Utils.NumberHolder(spAtkStat); + const spAtkValue = new NumberHolder(spAtkStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue); expect(atkValue.value / atkStat).toBe(1); diff --git a/test/items/metal_powder.test.ts b/test/items/metal_powder.test.ts index 460a95d0f06..ed96d3c498b 100644 --- a/test/items/metal_powder.test.ts +++ b/test/items/metal_powder.test.ts @@ -2,7 +2,7 @@ import { Stat } from "#enums/stat"; import { SpeciesStatBoosterModifier } from "#app/modifier/modifier"; import { modifierTypes } from "#app/modifier/modifier-type"; import i18next from "#app/plugins/i18n"; -import * as Utils from "#app/utils"; +import { NumberHolder } from "#app/utils"; import { Species } from "#enums/species"; import GameManager from "#test/testUtils/gameManager"; import Phase from "phaser"; @@ -89,7 +89,7 @@ describe("Items - Metal Powder", () => { const defStat = partyMember.getStat(Stat.DEF); // Making sure modifier is not applied without holding item - const defValue = new Utils.NumberHolder(defStat); + const defValue = new NumberHolder(defStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(1); @@ -122,7 +122,7 @@ describe("Items - Metal Powder", () => { const defStat = partyMember.getStat(Stat.DEF); // Making sure modifier is not applied without holding item - const defValue = new Utils.NumberHolder(defStat); + const defValue = new NumberHolder(defStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(1); @@ -155,7 +155,7 @@ describe("Items - Metal Powder", () => { const defStat = partyMember.getStat(Stat.DEF); // Making sure modifier is not applied without holding item - const defValue = new Utils.NumberHolder(defStat); + const defValue = new NumberHolder(defStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(1); @@ -178,7 +178,7 @@ describe("Items - Metal Powder", () => { const defStat = partyMember.getStat(Stat.DEF); // Making sure modifier is not applied without holding item - const defValue = new Utils.NumberHolder(defStat); + const defValue = new NumberHolder(defStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(1); diff --git a/test/items/quick_powder.test.ts b/test/items/quick_powder.test.ts index 26faf5a0f4f..7115cad8cd1 100644 --- a/test/items/quick_powder.test.ts +++ b/test/items/quick_powder.test.ts @@ -2,7 +2,7 @@ import { Stat } from "#enums/stat"; import { SpeciesStatBoosterModifier } from "#app/modifier/modifier"; import { modifierTypes } from "#app/modifier/modifier-type"; import i18next from "#app/plugins/i18n"; -import * as Utils from "#app/utils"; +import { NumberHolder } from "#app/utils"; import { Species } from "#enums/species"; import GameManager from "#test/testUtils/gameManager"; import Phase from "phaser"; @@ -89,7 +89,7 @@ describe("Items - Quick Powder", () => { const spdStat = partyMember.getStat(Stat.SPD); // Making sure modifier is not applied without holding item - const spdValue = new Utils.NumberHolder(spdStat); + const spdValue = new NumberHolder(spdStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); expect(spdValue.value / spdStat).toBe(1); @@ -122,7 +122,7 @@ describe("Items - Quick Powder", () => { const spdStat = partyMember.getStat(Stat.SPD); // Making sure modifier is not applied without holding item - const spdValue = new Utils.NumberHolder(spdStat); + const spdValue = new NumberHolder(spdStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); expect(spdValue.value / spdStat).toBe(1); @@ -155,7 +155,7 @@ describe("Items - Quick Powder", () => { const spdStat = partyMember.getStat(Stat.SPD); // Making sure modifier is not applied without holding item - const spdValue = new Utils.NumberHolder(spdStat); + const spdValue = new NumberHolder(spdStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); expect(spdValue.value / spdStat).toBe(1); @@ -178,7 +178,7 @@ describe("Items - Quick Powder", () => { const spdStat = partyMember.getStat(Stat.SPD); // Making sure modifier is not applied without holding item - const spdValue = new Utils.NumberHolder(spdStat); + const spdValue = new NumberHolder(spdStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); expect(spdValue.value / spdStat).toBe(1); diff --git a/test/items/thick_club.test.ts b/test/items/thick_club.test.ts index 9edbbcdc7d9..69ca316d455 100644 --- a/test/items/thick_club.test.ts +++ b/test/items/thick_club.test.ts @@ -2,7 +2,7 @@ import { Stat } from "#enums/stat"; import { SpeciesStatBoosterModifier } from "#app/modifier/modifier"; import { modifierTypes } from "#app/modifier/modifier-type"; import i18next from "#app/plugins/i18n"; -import * as Utils from "#app/utils"; +import { NumberHolder, randInt } from "#app/utils"; import { Species } from "#enums/species"; import GameManager from "#test/testUtils/gameManager"; import Phase from "phaser"; @@ -89,7 +89,7 @@ describe("Items - Thick Club", () => { const atkStat = partyMember.getStat(Stat.ATK); // Making sure modifier is not applied without holding item - const atkValue = new Utils.NumberHolder(atkStat); + const atkValue = new NumberHolder(atkStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(1); @@ -112,7 +112,7 @@ describe("Items - Thick Club", () => { const atkStat = partyMember.getStat(Stat.ATK); // Making sure modifier is not applied without holding item - const atkValue = new Utils.NumberHolder(atkStat); + const atkValue = new NumberHolder(atkStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(1); @@ -135,7 +135,7 @@ describe("Items - Thick Club", () => { const atkStat = partyMember.getStat(Stat.ATK); // Making sure modifier is not applied without holding item - const atkValue = new Utils.NumberHolder(atkStat); + const atkValue = new NumberHolder(atkStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(1); @@ -153,7 +153,7 @@ describe("Items - Thick Club", () => { it("THICK_CLUB held by fused CUBONE line (base)", async () => { // Randomly choose from the Cubone line const species = [Species.CUBONE, Species.MAROWAK, Species.ALOLA_MAROWAK]; - const randSpecies = Utils.randInt(species.length); + const randSpecies = randInt(species.length); await game.classicMode.startBattle([species[randSpecies], Species.PIKACHU]); @@ -172,7 +172,7 @@ describe("Items - Thick Club", () => { const atkStat = partyMember.getStat(Stat.ATK); // Making sure modifier is not applied without holding item - const atkValue = new Utils.NumberHolder(atkStat); + const atkValue = new NumberHolder(atkStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(1); @@ -190,7 +190,7 @@ describe("Items - Thick Club", () => { it("THICK_CLUB held by fused CUBONE line (part)", async () => { // Randomly choose from the Cubone line const species = [Species.CUBONE, Species.MAROWAK, Species.ALOLA_MAROWAK]; - const randSpecies = Utils.randInt(species.length); + const randSpecies = randInt(species.length); await game.classicMode.startBattle([Species.PIKACHU, species[randSpecies]]); @@ -209,7 +209,7 @@ describe("Items - Thick Club", () => { const atkStat = partyMember.getStat(Stat.ATK); // Making sure modifier is not applied without holding item - const atkValue = new Utils.NumberHolder(atkStat); + const atkValue = new NumberHolder(atkStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(1); @@ -232,7 +232,7 @@ describe("Items - Thick Club", () => { const atkStat = partyMember.getStat(Stat.ATK); // Making sure modifier is not applied without holding item - const atkValue = new Utils.NumberHolder(atkStat); + const atkValue = new NumberHolder(atkStat); game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(1); diff --git a/test/moves/multi_target.test.ts b/test/moves/multi_target.test.ts index 2b17929a5df..5d33c7860cb 100644 --- a/test/moves/multi_target.test.ts +++ b/test/moves/multi_target.test.ts @@ -1,7 +1,7 @@ import { BattlerIndex } from "#app/battle"; import { Abilities } from "#app/enums/abilities"; import { Species } from "#app/enums/species"; -import * as Utils from "#app/utils"; +import { toDmgValue } from "#app/utils"; import { Moves } from "#enums/moves"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; @@ -71,8 +71,8 @@ describe("Multi-target damage reduction", () => { // Single target moves don't get reduced expect(tackle1).toBe(tackle2); // Moves that target all enemies get reduced if there's more than one enemy - expect(gleam1).toBeLessThanOrEqual(Utils.toDmgValue(gleam2 * 0.75) + 1); - expect(gleam1).toBeGreaterThanOrEqual(Utils.toDmgValue(gleam2 * 0.75) - 1); + expect(gleam1).toBeLessThanOrEqual(toDmgValue(gleam2 * 0.75) + 1); + expect(gleam1).toBeGreaterThanOrEqual(toDmgValue(gleam2 * 0.75) - 1); }); it("should reduce earthquake when more than one pokemon other than user is not fainted", async () => { @@ -122,7 +122,7 @@ describe("Multi-target damage reduction", () => { const damageEnemy1Turn3 = enemy1.getMaxHp() - enemy1.hp; // Turn 3: 1 target, should be no damage reduction - expect(damageEnemy1Turn1).toBeLessThanOrEqual(Utils.toDmgValue(damageEnemy1Turn3 * 0.75) + 1); - expect(damageEnemy1Turn1).toBeGreaterThanOrEqual(Utils.toDmgValue(damageEnemy1Turn3 * 0.75) - 1); + expect(damageEnemy1Turn1).toBeLessThanOrEqual(toDmgValue(damageEnemy1Turn3 * 0.75) + 1); + expect(damageEnemy1Turn1).toBeGreaterThanOrEqual(toDmgValue(damageEnemy1Turn3 * 0.75) - 1); }); }); diff --git a/test/mystery-encounter/encounter-test-utils.ts b/test/mystery-encounter/encounter-test-utils.ts index 8c54e0dd606..93629778e0a 100644 --- a/test/mystery-encounter/encounter-test-utils.ts +++ b/test/mystery-encounter/encounter-test-utils.ts @@ -1,3 +1,4 @@ +// biome-ignore lint/style/noNamespaceImport: Necessary for mocks import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { Status } from "#app/data/status-effect"; import { CommandPhase } from "#app/phases/command-phase"; diff --git a/test/testUtils/gameWrapper.ts b/test/testUtils/gameWrapper.ts index 388861e01c4..02865701ed0 100644 --- a/test/testUtils/gameWrapper.ts +++ b/test/testUtils/gameWrapper.ts @@ -2,7 +2,7 @@ import BattleScene, * as battleScene from "#app/battle-scene"; import { MoveAnim } from "#app/data/battle-anims"; import Pokemon from "#app/field/pokemon"; -import * as Utils from "#app/utils"; +import { setCookie, sessionIdKey } from "#app/utils"; import { blobToString } from "#test/testUtils/gameManagerUtils"; import { MockClock } from "#test/testUtils/mocks/mockClock"; import { MockFetch } from "#test/testUtils/mocks/mockFetch"; @@ -29,7 +29,7 @@ window.URL.createObjectURL = (blob: Blob) => { }; navigator.getGamepads = () => []; global.fetch = vi.fn(MockFetch); -Utils.setCookie(Utils.sessionIdKey, "fake_token"); +setCookie(sessionIdKey, "fake_token"); window.matchMedia = () => ({ matches: false, From f9ff4abfb0396da1ed74882343e22081c5924599 Mon Sep 17 00:00:00 2001 From: Jimmybald1 <122436263+Jimmybald1@users.noreply.github.com> Date: Sat, 12 Apr 2025 16:56:04 +0200 Subject: [PATCH 27/33] [Bug] Fixed biome map options counting rng twice (#5648) Fixed biome map options counting rng twice Co-authored-by: Jimmybald1 <147992650+IBBCalc@users.noreply.github.com> Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> --- src/phases/select-biome-phase.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/phases/select-biome-phase.ts b/src/phases/select-biome-phase.ts index de705728c50..b27e2d0e7cc 100644 --- a/src/phases/select-biome-phase.ts +++ b/src/phases/select-biome-phase.ts @@ -38,15 +38,7 @@ export class SelectBiomePhase extends BattlePhase { .map(b => (!Array.isArray(b) ? b : b[0])); if (biomes.length > 1 && globalScene.findModifier(m => m instanceof MapModifier)) { - const biomeChoices: Biome[] = ( - !Array.isArray(biomeLinks[currentBiome]) - ? [biomeLinks[currentBiome] as Biome] - : (biomeLinks[currentBiome] as (Biome | [Biome, number])[]) - ) - .filter(b => !Array.isArray(b) || !randSeedInt(b[1])) - .map(b => (Array.isArray(b) ? b[0] : b)); - - const biomeSelectItems = biomeChoices.map(b => { + const biomeSelectItems = biomes.map(b => { const ret: OptionSelectItem = { label: getBiomeName(b), handler: () => { From 15e535a1a0e4328e0c6998f6008b656387fc2dfa Mon Sep 17 00:00:00 2001 From: Lylian BALL <131535108+PyGaVS@users.noreply.github.com> Date: Sun, 13 Apr 2025 03:22:04 +0200 Subject: [PATCH 28/33] [Ability] Implement Illusion (#3273) * implement illusion ability with unit test and localizations * try removing whitespace change on unnecessary files * nit corrections * nit update src/field/pokemon.ts Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> * nit update src/phases.ts Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com> * illusion test correction * unexpected error correction * refactor property pokemon.illusion to pokemon.battleData.illusion * nit * nit * update unit test up-to-date * add docs * merge up to date * bugfix * bugfix * merge up to date * refactor field illusion out of battleData * fix nit * fix nit * Zoroark change illusion after lastPokemon update * Zoroark change illusion after lastPokemon update * refactor bug fix * bugfix * bug fix on tests * Update src/field/pokemon.ts Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> * use GetFailedText * remove useless import * add condition 'no illusion' into transform move * wild Zoroark creates an illusion according to the current biome * wild Zoroark creates an illusion according to the current biome * delete console.log() * add doc * Update src/field/pokemon.ts Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> * fix tests * update locales submodule * update Illusion interface * bug fix * bug fix * bugfix * rename some params for future implementations * Zoroark keep illusion between battles * Zoroark keep illusion between battles * delete draft * merge up-to-date * bugfix * merge * merge * implement canApplyPresummon method * Update test/abilities/illusion.test.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/data/ability.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/data/ability.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update test/abilities/illusion.test.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update test/abilities/illusion.test.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * nit * review corrections * nit * type hints affected by enemy illusion * type hints affected by enemy illusionin fight-ui-handler * nit * rename some parameters back in useIllusion * Update src/field/pokemon.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/field/pokemon.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * refactor battleData.illusion as summonData.illusion and delete oncePerBattleClause * add comments * illusion will break before evolution * Update src/field/pokemon.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/data/ability.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/data/ability.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/data/ability.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/data/ability.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * bug fix * g * get submodule back * get submodule back * bug fix to save illusion status * add pokemon.getPokeball() * Update src/field/pokemon.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/field/pokemon.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/field/pokemon.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/data/ability.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/field/pokemon.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/field/pokemon.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/field/pokemon.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/field/pokemon.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com> Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/battle-scene.ts | 2 +- src/data/ability.ts | 135 +++++++- src/data/moves/move.ts | 10 +- src/field/pokemon.ts | 495 ++++++++++++++++++++++-------- src/messages.ts | 17 +- src/phases/encounter-phase.ts | 7 +- src/phases/level-up-phase.ts | 1 + src/phases/summon-phase.ts | 14 +- src/phases/switch-summon-phase.ts | 11 +- src/system/game-data.ts | 4 +- src/system/pokemon-data.ts | 18 +- src/ui/battle-info.ts | 28 +- src/ui/fight-ui-handler.ts | 5 +- src/ui/party-ui-handler.ts | 38 +-- src/ui/rename-form-ui-handler.ts | 2 +- src/ui/summary-ui-handler.ts | 24 +- test/abilities/illusion.test.ts | 144 +++++++++ 17 files changed, 757 insertions(+), 198 deletions(-) create mode 100644 test/abilities/illusion.test.ts diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 8ae2be5af43..dd983f2b397 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1072,7 +1072,7 @@ export default class BattleScene extends SceneBase { container.add(icon); - if (pokemon.isFusion()) { + if (pokemon.isFusion(true)) { const fusionIcon = this.add.sprite(0, 0, pokemon.getFusionIconAtlasKey(ignoreOverride)); fusionIcon.setName("sprite-fusion-icon"); fusionIcon.setOrigin(0.5, 0); diff --git a/src/data/ability.ts b/src/data/ability.ts index b07f13c18e9..3e32a624f9f 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2370,6 +2370,18 @@ export class PostSummonMessageAbAttr extends PostSummonAbAttr { } } +/** + * Removes illusions when a Pokemon is summoned. + */ +export class PostSummonRemoveIllusionAbAttr extends PostSummonAbAttr { + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + for (const pokemon of globalScene.getField(true)) { + pokemon.breakIllusion(); + } + return true; + } +} + export class PostSummonUnnamedMessageAbAttr extends PostSummonAbAttr { //Attr doesn't force pokemon name on the message private message: string; @@ -2812,7 +2824,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { } private getTarget(targets: Pokemon[]): Pokemon { - let target: Pokemon; + let target: Pokemon = targets[0]; if (targets.length > 1) { globalScene.executeWithSeedOffset(() => { // in a double battle, if one of the opposing pokemon is fused the other one will be chosen @@ -2829,6 +2841,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { } else { target = targets[0]; } + target = target!; return target; @@ -2836,6 +2849,12 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const targets = pokemon.getOpponents(); + const target = this.getTarget(targets); + + if (!!target.summonData?.illusion) { + return false; + } + if (simulated || !targets.length) { return simulated; } @@ -4741,8 +4760,8 @@ export class MaxMultiHitAbAttr extends AbAttr { } export class PostBattleAbAttr extends AbAttr { - constructor() { - super(true); + constructor(showAbility: boolean = true) { + super(showAbility); } canApplyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { @@ -5259,6 +5278,92 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { } } +/** + * Base class for defining {@linkcode Ability} attributes before summon + * (should use {@linkcode PostSummonAbAttr} for most ability) + * @see {@linkcode applyPreSummon()} + */ +export class PreSummonAbAttr extends AbAttr { + applyPreSummon(pokemon: Pokemon, passive: boolean, args: any[]): void {} + + canApplyPreSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + return true; + } +} + +export class IllusionPreSummonAbAttr extends PreSummonAbAttr { + /** + * Apply a new illusion when summoning Zoroark if the illusion is available + * + * @param pokemon - The Pokémon with the Illusion ability. + * @param passive - N/A + * @param args - N/A + * @returns Whether the illusion was applied. + */ + override applyPreSummon(pokemon: Pokemon, passive: boolean, args: any[]): void { + const party: Pokemon[] = (pokemon.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter(p => p.isAllowedInBattle()); + const lastPokemon: Pokemon = party.filter(p => p !==pokemon).at(-1) || pokemon; + pokemon.setIllusion(lastPokemon); + } + + override canApplyPreSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { + pokemon.initSummondata() + if(pokemon.hasTrainer()){ + const party: Pokemon[] = (pokemon.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter(p => p.isAllowedInBattle()); + const lastPokemon: Pokemon = party.filter(p => p !==pokemon).at(-1) || pokemon; + const speciesId = lastPokemon.species.speciesId; + + // If the last conscious Pokémon in the party is a Terastallized Ogerpon or Terapagos, Illusion will not activate. + // Illusion will also not activate if the Pokémon with Illusion is Terastallized and the last Pokémon in the party is Ogerpon or Terapagos. + if ( + lastPokemon === pokemon || + ((speciesId === Species.OGERPON || speciesId === Species.TERAPAGOS) && (lastPokemon.isTerastallized || pokemon.isTerastallized)) + ) { + return false; + } + } + return !pokemon.summonData.illusionBroken; + } +} + +export class IllusionBreakAbAttr extends PostDefendAbAttr { + /** + * Destroy the illusion upon taking damage + * + * @param pokemon - The Pokémon with the Illusion ability. + * @param passive - unused + * @param attacker - The attacking Pokémon. + * @param move - The move being used. + * @param hitResult - The type of hitResult the pokemon got + * @param args - unused + * @returns - Whether the illusion was destroyed. + */ + override applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): void { + pokemon.breakIllusion(); + pokemon.summonData.illusionBroken = true; + } + + override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + const breakIllusion: HitResult[] = [ HitResult.EFFECTIVE, HitResult.SUPER_EFFECTIVE, HitResult.NOT_VERY_EFFECTIVE, HitResult.ONE_HIT_KO ]; + return breakIllusion.includes(hitResult) && !!pokemon.summonData?.illusion + } +} + +export class IllusionPostBattleAbAttr extends PostBattleAbAttr { + /** + * Break the illusion once the battle ends + * + * @param pokemon - The Pokémon with the Illusion ability. + * @param passive - Unused + * @param args - Unused + * @returns - Whether the illusion was applied. + */ + override applyPostBattle(pokemon: Pokemon, passive: boolean, simulated:boolean, args: any[]): void { + pokemon.breakIllusion() + } +} + + /** * If a Pokémon with this Ability selects a damaging move, it has a 30% chance of going first in its priority bracket. If the Ability activates, this is announced at the start of the turn (after move selection). * @@ -6017,6 +6122,20 @@ export function applyPostSummonAbAttrs( ); } +export function applyPreSummonAbAttrs( + attrType: Constructor, + pokemon: Pokemon, + ...args: any[] +): void { + applyAbAttrsInternal( + attrType, + pokemon, + (attr, passive) => attr.applyPreSummon(pokemon, passive, args), + (attr, passive) => attr.canApplyPreSummon(pokemon, passive, args), + args + ); +} + export function applyPreSwitchOutAbAttrs( attrType: Constructor, pokemon: Pokemon, @@ -6811,8 +6930,14 @@ export function initAbilities() { return isNullOrUndefined(movePhase); }, 1.3), new Ability(Abilities.ILLUSION, 5) + //The pokemon generate an illusion if it's available + .attr(IllusionPreSummonAbAttr, false) + //The pokemon loses his illusion when he is damaged by a move + .attr(IllusionBreakAbAttr, true) + //Illusion is available again after a battle + .conditionalAttr((pokemon) => pokemon.isAllowedInBattle(), IllusionPostBattleAbAttr, false) .uncopiable() - .unimplemented(), + .bypassFaint(), new Ability(Abilities.IMPOSTER, 5) .attr(PostSummonTransformAbAttr) .uncopiable(), @@ -7223,6 +7348,8 @@ export function initAbilities() { .attr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr) .uncopiable() .attr(NoTransformAbilityAbAttr) + .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonNeutralizingGas", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) + .attr(PostSummonRemoveIllusionAbAttr) .bypassFaint(), new Ability(Abilities.PASTEL_VEIL, 8) .attr(PostSummonUserFieldRemoveStatusEffectAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index a0f68dcd5cb..962a13bb840 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -7389,11 +7389,13 @@ export class AbilityChangeAttr extends MoveEffectAttr { const moveTarget = this.selfTarget ? user : target; - globalScene.queueMessage(i18next.t("moveTriggers:acquiredAbility", { pokemonName: getPokemonNameWithAffix((this.selfTarget ? user : target)), abilityName: allAbilities[this.ability].name })); - + globalScene.triggerPokemonFormChange(moveTarget, SpeciesFormChangeRevertWeatherFormTrigger); + if (moveTarget.breakIllusion()) { + globalScene.queueMessage(i18next.t("abilityTriggers:illusionBreak", { pokemonName: getPokemonNameWithAffix(moveTarget) })); + } + globalScene.queueMessage(i18next.t("moveTriggers:acquiredAbility", { pokemonName: getPokemonNameWithAffix(moveTarget), abilityName: allAbilities[this.ability].name })); moveTarget.setTempAbility(allAbilities[this.ability]); globalScene.triggerPokemonFormChange(moveTarget, SpeciesFormChangeRevertWeatherFormTrigger); - return true; } @@ -8673,6 +8675,8 @@ export function initMoves() { .makesContact(false), new StatusMove(Moves.TRANSFORM, PokemonType.NORMAL, -1, 10, -1, 0, 1) .attr(TransformAttr) + .condition((user, target, move) => !target.getTag(BattlerTagType.SUBSTITUTE)) + .condition((user, target, move) => !target.summonData?.illusion && !user.summonData?.illusion) // transforming from or into fusion pokemon causes various problems (such as crashes) .condition((user, target, move) => !target.getTag(BattlerTagType.SUBSTITUTE) && !user.fusionSpecies && !target.fusionSpecies) .ignoresProtect(), diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 8fc75ca657d..b59b7ba01fe 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -536,21 +536,33 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - getNameToRender() { + /** + * @param {boolean} 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; try { - if (this.nickname) { - return decodeURIComponent(escape(atob(this.nickname))); + if (nickname) { + return decodeURIComponent(escape(atob(nickname))); } - return this.name; + return name; } catch (err) { - console.error(`Failed to decode nickname for ${this.name}`, err); - return this.name; + console.error(`Failed to decode nickname for ${name}`, err); + return name; + } + } + + getPokeball(useIllusion = false){ + if(useIllusion){ + return this.summonData?.illusion?.pokeball ?? this.pokeball + } else { + return this.pokeball } } init(): void { this.fieldPosition = FieldPosition.CENTER; - this.initBattleInfo(); globalScene.fieldUI.addAt(this.battleInfo, 0); @@ -584,7 +596,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.addAt(sprite, 0); this.addAt(tintSprite, 1); - if (this.isShiny() && !this.shinySparkle) { + if (this.isShiny(true) && !this.shinySparkle) { this.initShinySparkle(); } } @@ -682,6 +694,92 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } + /** + * Generate an illusion of the last pokemon in the party, as other wild pokemon in the area. + */ + setIllusion(pokemon: Pokemon): boolean { + if(!!this.summonData?.illusion){ + this.breakIllusion(); + } + if (this.hasTrainer()) { + const speciesId = pokemon.species.speciesId; + + this.summonData.illusion = { + basePokemon: { + name: this.name, + nickname: this.nickname, + shiny: this.shiny, + variant: this.variant, + fusionShiny: this.fusionShiny, + fusionVariant: this.fusionVariant + }, + species: speciesId, + formIndex: pokemon.formIndex, + gender: pokemon.gender, + pokeball: pokemon.pokeball, + fusionFormIndex: pokemon.fusionFormIndex, + fusionSpecies: pokemon.fusionSpecies || undefined, + fusionGender: pokemon.fusionGender + }; + + this.name = pokemon.name; + this.nickname = pokemon.nickname; + this.shiny = pokemon.shiny; + this.variant = pokemon.variant; + this.fusionVariant = pokemon.fusionVariant; + this.fusionShiny = pokemon.fusionShiny; + if (this.shiny) { + this.initShinySparkle(); + } + this.loadAssets(false, true).then(() => this.playAnim()); + this.updateInfo(); + } else { + const randomIllusion: PokemonSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, this.level); + + this.summonData.illusion = { + basePokemon: { + name: this.name, + nickname: this.nickname, + shiny: this.shiny, + variant: this.variant, + fusionShiny: this.fusionShiny, + fusionVariant: this.fusionVariant + }, + species: randomIllusion.speciesId, + formIndex: randomIllusion.formIndex, + gender: this.gender, + pokeball: this.pokeball + }; + + this.name = randomIllusion.name; + this.loadAssets(false, true).then(() => this.playAnim()); + } + return true; + } + + 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; + } + if (this.isOnField()) { + globalScene.playSound("PRSFX- Transform"); + } + if (this.shiny) { + this.initShinySparkle(); + } + this.loadAssets(false).then(() => this.playAnim()); + this.updateInfo(true); + return true; + } + abstract isPlayer(): boolean; abstract hasTrainer(): boolean; @@ -690,29 +788,41 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { abstract getBattlerIndex(): BattlerIndex; - async loadAssets(ignoreOverride = true): Promise { + /** +   * @param useIllusion - Whether we want the illusion or not. +   */ + async loadAssets(ignoreOverride = true, useIllusion: boolean = false): Promise { /** Promises that are loading assets and can be run concurrently. */ const loadPromises: Promise[] = []; // Assets for moves loadPromises.push(loadMoveAnimations(this.getMoveset().map(m => m.getMove().id))); // Load the assets for the species form + const formIndex = !!this.summonData?.illusion && useIllusion ? this.summonData?.illusion.formIndex : this.formIndex; loadPromises.push( - this.getSpeciesForm().loadAssets(this.getGender() === Gender.FEMALE, this.formIndex, this.shiny, this.variant), + this.getSpeciesForm(false, useIllusion).loadAssets( + this.getGender(useIllusion) === Gender.FEMALE, + formIndex, + this.isShiny(useIllusion), + this.getVariant(useIllusion) + ), ); - if (this.isPlayer() || this.getFusionSpeciesForm()) { + if (this.isPlayer() || this.getFusionSpeciesForm(false, useIllusion)) { globalScene.loadPokemonAtlas( this.getBattleSpriteKey(true, ignoreOverride), this.getBattleSpriteAtlasPath(true, ignoreOverride), ); } if (this.getFusionSpeciesForm()) { - loadPromises.push(this.getFusionSpeciesForm().loadAssets( - this.getFusionGender() === Gender.FEMALE, - this.fusionFormIndex, - this.fusionShiny, - this.fusionVariant, + const fusionFormIndex = !!this.summonData?.illusion && useIllusion ? this.summonData?.illusion.fusionFormIndex : this.fusionFormIndex; + const fusionShiny = !!this.summonData?.illusion && !useIllusion ? this.summonData?.illusion.basePokemon!.fusionShiny : this.fusionShiny; + const fusionVariant = !!this.summonData?.illusion && !useIllusion ? 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), @@ -720,7 +830,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ); } - if (this.shiny) { + if (this.isShiny(true)) { loadPromises.push(populateVariantColors(this, false, ignoreOverride)) if (this.isPlayer()) { loadPromises.push(populateVariantColors(this, true, ignoreOverride)); @@ -870,11 +980,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getSpriteId(ignoreOverride?: boolean): string { - return this.getSpeciesForm(ignoreOverride).getSpriteId( - this.getGender(ignoreOverride) === Gender.FEMALE, - this.formIndex, - this.shiny, - this.variant, + const formIndex: integer = !!this.summonData?.illusion ? this.summonData?.illusion.formIndex! : this.formIndex; + return this.getSpeciesForm(ignoreOverride, true).getSpriteId( + this.getGender(ignoreOverride, true) === Gender.FEMALE, + formIndex, + this.shiny, + this.variant ); } @@ -882,21 +993,24 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (back === undefined) { back = this.isPlayer(); } - return this.getSpeciesForm(ignoreOverride).getSpriteId( - this.getGender(ignoreOverride) === Gender.FEMALE, - this.formIndex, - this.shiny, - this.variant, - back, + + const formIndex: integer = !!this.summonData?.illusion ? this.summonData?.illusion.formIndex! : this.formIndex; + + return this.getSpeciesForm(ignoreOverride, true).getSpriteId( + this.getGender(ignoreOverride, true) === Gender.FEMALE, + formIndex, + this.shiny, + this.variant, + back ); } getSpriteKey(ignoreOverride?: boolean): string { - return this.getSpeciesForm(ignoreOverride).getSpriteKey( + return this.getSpeciesForm(ignoreOverride, false).getSpriteKey( this.getGender(ignoreOverride) === Gender.FEMALE, this.formIndex, - this.shiny, - this.variant, + this.summonData?.illusion?.basePokemon.shiny ?? this.shiny, + this.summonData?.illusion?.basePokemon.variant ?? this.variant ); } @@ -905,11 +1019,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getFusionSpriteId(ignoreOverride?: boolean): string { - return this.getFusionSpeciesForm(ignoreOverride).getSpriteId( - this.getFusionGender(ignoreOverride) === Gender.FEMALE, - this.fusionFormIndex, - this.fusionShiny, - this.fusionVariant, + const fusionFormIndex: integer = !!this.summonData?.illusion ? this.summonData?.illusion.fusionFormIndex! : this.fusionFormIndex; + return this.getFusionSpeciesForm(ignoreOverride, true).getSpriteId( + this.getFusionGender(ignoreOverride, true) === Gender.FEMALE, + fusionFormIndex, + this.fusionShiny, + this.fusionVariant ); } @@ -917,12 +1032,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (back === undefined) { back = this.isPlayer(); } - return this.getFusionSpeciesForm(ignoreOverride).getSpriteId( - this.getFusionGender(ignoreOverride) === Gender.FEMALE, - this.fusionFormIndex, - this.fusionShiny, - this.fusionVariant, - back, + + const fusionFormIndex: integer = !!this.summonData?.illusion ? this.summonData?.illusion.fusionFormIndex! : this.fusionFormIndex; + + return this.getFusionSpeciesForm(ignoreOverride, true).getSpriteId( + this.getFusionGender(ignoreOverride, true) === Gender.FEMALE, + fusionFormIndex, + this.fusionShiny, + this.fusionVariant, + back ); } @@ -941,62 +1059,77 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getIconAtlasKey(ignoreOverride?: boolean): string { - return this.getSpeciesForm(ignoreOverride).getIconAtlasKey( - this.formIndex, - this.shiny, - this.variant, + const formIndex: integer = !!this.summonData?.illusion ? this.summonData?.illusion.formIndex : this.formIndex; + return this.getSpeciesForm(ignoreOverride, true).getIconAtlasKey( + formIndex, + this.shiny, + this.variant ); } getFusionIconAtlasKey(ignoreOverride?: boolean): string { - return this.getFusionSpeciesForm(ignoreOverride).getIconAtlasKey( - this.fusionFormIndex, - this.fusionShiny, - this.fusionVariant, + return this.getFusionSpeciesForm(ignoreOverride, true).getIconAtlasKey( + this.fusionFormIndex, + this.fusionShiny, + this.fusionVariant ); } getIconId(ignoreOverride?: boolean): string { - return this.getSpeciesForm(ignoreOverride).getIconId( - this.getGender(ignoreOverride) === Gender.FEMALE, - this.formIndex, - this.shiny, - this.variant, + const formIndex: integer = !!this.summonData?.illusion ? this.summonData?.illusion.formIndex : this.formIndex; + return this.getSpeciesForm(ignoreOverride, true).getIconId( + this.getGender(ignoreOverride, true) === Gender.FEMALE, + formIndex, + this.shiny, + this.variant ); } getFusionIconId(ignoreOverride?: boolean): string { - return this.getFusionSpeciesForm(ignoreOverride).getIconId( - this.getFusionGender(ignoreOverride) === Gender.FEMALE, - this.fusionFormIndex, - this.fusionShiny, - this.fusionVariant, + const fusionFormIndex: integer = !!this.summonData?.illusion ? this.summonData?.illusion.fusionFormIndex! : this.fusionFormIndex; + return this.getFusionSpeciesForm(ignoreOverride, true).getIconId( + this.getFusionGender(ignoreOverride, true) === Gender.FEMALE, + fusionFormIndex, + this.fusionShiny, + this.fusionVariant ); } - getSpeciesForm(ignoreOverride?: boolean): PokemonSpeciesForm { + /** + * @param {boolean} useIllusion - Whether we want the speciesForm of the illusion or not. + */ + getSpeciesForm(ignoreOverride?: boolean, useIllusion: boolean = false): PokemonSpeciesForm { + const species: PokemonSpecies = useIllusion && !!this.summonData?.illusion ? getPokemonSpecies(this.summonData?.illusion.species) : this.species; + + const formIndex: integer = useIllusion && !!this.summonData?.illusion ? this.summonData?.illusion.formIndex : this.formIndex; + if (!ignoreOverride && this.summonData?.speciesForm) { return this.summonData.speciesForm; } - if (this.species.forms && this.species.forms.length > 0) { - return this.species.forms[this.formIndex]; + if (species.forms && species.forms.length > 0) { + return species.forms[formIndex]; } - return this.species; + return species; } - getFusionSpeciesForm(ignoreOverride?: boolean): PokemonSpeciesForm { + /** + * @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: integer = useIllusion && !!this.summonData?.illusion ? this.summonData?.illusion.fusionFormIndex! : this.fusionFormIndex; + if (!ignoreOverride && this.summonData?.speciesForm) { return this.summonData.fusionSpeciesForm; } if ( - !this.fusionSpecies?.forms?.length || - this.fusionFormIndex >= this.fusionSpecies?.forms.length + !fusionSpecies?.forms?.length || + fusionFormIndex >= fusionSpecies?.forms.length ) { - //@ts-ignore - return this.fusionSpecies; // TODO: I don't even know how to fix this... A complete cluster of classes involved + null + return fusionSpecies; } - return this.fusionSpecies?.forms[this.fusionFormIndex]; + return fusionSpecies?.forms[fusionFormIndex]; } getSprite(): Phaser.GameObjects.Sprite { @@ -1652,36 +1785,98 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - getGender(ignoreOverride?: boolean): Gender { - if (!ignoreOverride && this.summonData?.gender !== undefined) { + /** + * @param {boolean} useIllusion - Whether we want the fake or real gender (illusion ability). + */ + getGender(ignoreOverride?: boolean, useIllusion: boolean = false): Gender { + if (useIllusion && !!this.summonData?.illusion) { + return this.summonData?.illusion.gender!; + } else if (!ignoreOverride && this.summonData?.gender !== undefined) { return this.summonData.gender; } return this.gender; } - getFusionGender(ignoreOverride?: boolean): Gender { - if (!ignoreOverride && this.summonData?.fusionGender !== undefined) { + /** + * @param {boolean} useIllusion - Whether we want the fake or real gender (illusion ability). + */ + getFusionGender(ignoreOverride?: boolean, useIllusion: boolean = false): Gender { + if (useIllusion && !!this.summonData?.illusion) { + return this.summonData?.illusion.fusionGender!; + } else if (!ignoreOverride && this.summonData?.fusionGender !== undefined) { return this.summonData.fusionGender; } return this.fusionGender; } - isShiny(): boolean { - return this.shiny || (this.isFusion() && this.fusionShiny); + /** + * @param {boolean} useIllusion - Whether we want the fake or real shininess (illusion ability). + */ + isShiny(useIllusion: boolean = 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); + } } - getVariant(): Variant { - return !this.isFusion() - ? this.variant - : (Math.max(this.variant, this.fusionVariant) as Variant); + /** + * + * @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 { + if (!useIllusion && !!this.summonData?.illusion) { + return this.isFusion(false) && this.summonData?.illusion.basePokemon.shiny && this.summonData?.illusion.basePokemon.fusionShiny; + } else { + return this.isFusion(useIllusion) && this.shiny && this.fusionShiny; + } + } + + /** + * @param {boolean} useIllusion - Whether we want the fake or real variant (illusion ability). + */ + getVariant(useIllusion: boolean = 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; + } + } + + getBaseVariant(doubleShiny: boolean): Variant { + if (doubleShiny) { + return !!this.summonData?.illusion + ? this.summonData?.illusion.basePokemon!.variant + : this.variant; + } else { + return this.getVariant(); + } } getLuck(): number { return this.luck + (this.isFusion() ? this.fusionLuck : 0); } - isFusion(): boolean { - return !!this.fusionSpecies; + isFusion(useIllusion: boolean = false): boolean { + if (useIllusion && !!this.summonData?.illusion) { + return !!this.summonData?.illusion.fusionSpecies; + } else { + return !!this.fusionSpecies; + } + } + + /** + * @param {boolean} 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 && this.summonData?.illusion.basePokemon) + ? this.summonData?.illusion.basePokemon.name + : this.name; } /** @@ -1796,12 +1991,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param includeTeraType - `true` to include tera-formed type; Default: `false` * @param forDefend - `true` if the pokemon is defending from an attack; Default: `false` * @param ignoreOverride - If `true`, ignore ability changing effects; Default: `false` + * @param useIllusion - `true` to return the types of the illusion instead of the actual types; "AUTO" will depend on forDefend param; Default: "AUTO" * @returns array of {@linkcode PokemonType} */ public getTypes( - includeTeraType = false, - forDefend = false, - ignoreOverride = false, + includeTeraType = false, + forDefend: boolean = false, + ignoreOverride?: boolean, + useIllusion: boolean | "AUTO" = "AUTO" ): PokemonType[] { const types: PokemonType[] = []; @@ -1815,17 +2012,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } } - if (!types.length || !includeTeraType) { + + const doIllusion: boolean = (useIllusion === "AUTO") ? !forDefend : useIllusion; if ( - !ignoreOverride && - this.summonData?.types && - this.summonData.types.length > 0 + !ignoreOverride && + this.summonData?.types && + this.summonData.types.length > 0 && + (!this.summonData?.illusion || !doIllusion) ) { this.summonData.types.forEach(t => types.push(t)); } else { - const speciesForm = this.getSpeciesForm(ignoreOverride); - const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride); + const speciesForm = this.getSpeciesForm(ignoreOverride, doIllusion); + const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride, doIllusion); const customTypes = this.customPokemonData.types?.length > 0; // First type, checking for "permanently changed" types from ME @@ -2378,6 +2577,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param ignoreAbility Whether to ignore abilities that might affect type effectiveness or immunity (defaults to `false`). * @param simulated Whether to apply abilities via simulated calls (defaults to `true`) * @param cancelled {@linkcode BooleanHolder} Stores whether the move was cancelled by a non-type-based immunity. + * @param useIllusion - Whether we want the attack move effectiveness on the illusion or not * Currently only used by {@linkcode Pokemon.apply} to determine whether a "No effect" message should be shown. * @returns The type damage multiplier, indicating the effectiveness of the move */ @@ -2387,6 +2587,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ignoreAbility = false, simulated = true, cancelled?: BooleanHolder, + useIllusion: boolean = false ): TypeDamageMultiplier { if (!isNullOrUndefined(this.turnData?.moveEffectiveness)) { return this.turnData?.moveEffectiveness; @@ -2398,17 +2599,17 @@ 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, - ) - : 1, - ); + move.category !== MoveCategory.STATUS || + move.hasAttr(RespectAttackTypeImmunityAttr) + ? this.getAttackTypeEffectiveness( + moveType, + source, + false, + simulated, + move, + useIllusion + ) + : 1); applyMoveAttrs( VariableMoveTypeMultiplierAttr, @@ -2512,19 +2713,21 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param ignoreStrongWinds whether or not this ignores strong winds (anticipation, forewarn, stealth rocks) * @param simulated tag to only apply the strong winds effect message when the move is used * @param move (optional) the move whose type effectiveness is to be checked. Used for applying {@linkcode VariableMoveTypeChartAttr} + * @param useIllusion - Whether we want the attack type effectiveness on the illusion or not * @returns a multiplier for the type effectiveness */ getAttackTypeEffectiveness( - moveType: PokemonType, - source?: Pokemon, - ignoreStrongWinds = false, - simulated = true, - move?: Move, + moveType: PokemonType, + source?: Pokemon, + ignoreStrongWinds: boolean = false, + simulated: boolean = true, + move?: Move, + useIllusion: boolean = false ): TypeDamageMultiplier { if (moveType === PokemonType.STELLAR) { return this.isTerastallized ? 2 : 1; } - const types = this.getTypes(true, true); + const types = this.getTypes(true, true, undefined, useIllusion); const arena = globalScene.arena; // Handle flying v ground type immunity without removing flying type so effective types are still effective @@ -2623,7 +2826,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ getMatchupScore(opponent: Pokemon): number { const types = this.getTypes(true); - const enemyTypes = opponent.getTypes(true, true); + + const enemyTypes = opponent.getTypes(true, true, false, true); /** Is this Pokemon faster than the opponent? */ const outspeed = (this.isActive(true) @@ -2634,9 +2838,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * 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) * - (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. @@ -2648,12 +2850,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { atkScore *= opponent.getAttackTypeEffectiveness(types[1], this); } if (enemyTypes.length > 1) { - defScore *= - 1 / - Math.max( - this.getAttackTypeEffectiveness(enemyTypes[1], opponent), - 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. @@ -5538,7 +5735,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.summonDataPrimer = summonDataPrimer; } + // For PreSummonAbAttr to get access to summonData + initSummondata(): void { + this.summonData = this.summonData ?? this.summonDataPrimer ?? new PokemonSummonData() + } + resetSummonData(): void { + const illusion: IllusionData | null = this.summonData?.illusion; if (this.summonData?.speciesForm) { this.summonData.speciesForm = null; this.updateFusionPalette(); @@ -5574,6 +5777,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } this.summonDataPrimer = null; } + this.summonData.illusion = illusion this.updateInfo(); } @@ -7146,15 +7350,11 @@ export class EnemyPokemon extends Pokemon { ) { 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.battleData?.abilityRevealed, - ); + /** + * 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.battleData?.abilityRevealed, undefined, undefined, true); if (target.isPlayer() !== this.isPlayer()) { targetScore *= effectiveness; if (this.isOfType(move.type)) { @@ -7543,6 +7743,42 @@ export class EnemyPokemon extends Pokemon { } } +/** + * Illusion property + */ +interface IllusionData { + basePokemon: { + /** The actual name of the Pokemon */ + name: string; + /** The actual nickname of the Pokemon */ + nickname: string; + /** Whether the base pokemon is shiny or not */ + shiny: boolean; + /** The shiny variant of the base pokemon */ + variant: Variant; + /** Whether the fusion species of the base pokemon is shiny or not */ + fusionShiny: boolean; + /** The variant of the fusion species of the base pokemon */ + fusionVariant: Variant; + }; + /** The species of the illusion */ + species: Species; + /** The formIndex of the illusion */ + formIndex: number; + /** The gender of the illusion */ + gender: Gender; + /** The pokeball of the illusion */ + pokeball: PokeballType; + /** The fusion species of the illusion if it's a fusion */ + fusionSpecies?: PokemonSpecies; + /** The fusionFormIndex of the illusion */ + fusionFormIndex?: number; + /** The fusionGender of the illusion if it's a fusion */ + fusionGender?: Gender; + /** The level of the illusion (not used currently) */ + level?: number +} + export interface TurnMove { move: Moves; targets: BattlerIndex[]; @@ -7576,9 +7812,12 @@ export class PokemonSummonData { public fusionGender: Gender; public stats: number[] = [0, 0, 0, 0, 0, 0]; public moveset: PokemonMove[]; + public illusionBroken: boolean = false; + // If not initialized this value will not be populated from save data. public types: PokemonType[] = []; public addedType: PokemonType | null = null; + public illusion: IllusionData | null = null; } export class PokemonBattleData { @@ -7589,7 +7828,7 @@ export class PokemonBattleData { public endured = false; public berriesEaten: BerryType[] = []; public abilitiesApplied: Abilities[] = []; - public abilityRevealed = false; + public abilityRevealed: boolean = false; } export class PokemonBattleSummonData { diff --git a/src/messages.ts b/src/messages.ts index e35b48f7226..c29151a98b3 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -6,9 +6,10 @@ import i18next from "i18next"; /** * Retrieves the Pokemon's name, potentially with an affix indicating its role (wild or foe) in the current battle context, translated * @param pokemon {@linkcode Pokemon} name and battle context will be retrieved from this instance + * @param {boolean} useIllusion - Whether we want the name of the illusion or not. Default value : true * @returns {string} ex: "Wild Gengar", "Ectoplasma sauvage" */ -export function getPokemonNameWithAffix(pokemon: Pokemon | undefined): string { +export function getPokemonNameWithAffix(pokemon: Pokemon | undefined, useIllusion = true): string { if (!pokemon) { return "Missigno"; } @@ -18,19 +19,17 @@ export function getPokemonNameWithAffix(pokemon: Pokemon | undefined): string { return !pokemon.isPlayer() ? pokemon.hasTrainer() ? i18next.t("battle:foePokemonWithAffix", { - pokemonName: pokemon.getNameToRender(), + pokemonName: pokemon.getNameToRender(useIllusion), }) : i18next.t("battle:wildPokemonWithAffix", { - pokemonName: pokemon.getNameToRender(), + pokemonName: pokemon.getNameToRender(useIllusion), }) - : pokemon.getNameToRender(); + : pokemon.getNameToRender(useIllusion); case BattleSpec.FINAL_BOSS: return !pokemon.isPlayer() - ? i18next.t("battle:foePokemonWithAffix", { - pokemonName: pokemon.getNameToRender(), - }) - : pokemon.getNameToRender(); + ? i18next.t("battle:foePokemonWithAffix", { pokemonName: pokemon.getNameToRender(useIllusion) }) + : pokemon.getNameToRender(useIllusion); default: - return pokemon.getNameToRender(); + return pokemon.getNameToRender(useIllusion); } } diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index 9e5edf3e1d9..15f3d102e41 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -1,7 +1,7 @@ import { BattlerIndex, BattleType } from "#app/battle"; import { globalScene } from "#app/global-scene"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; -import { applyAbAttrs, SyncEncounterNatureAbAttr } from "#app/data/ability"; +import { applyAbAttrs, SyncEncounterNatureAbAttr, applyPreSummonAbAttrs, PreSummonAbAttr } from "#app/data/ability"; import { initEncounterAnims, loadEncounterAnimAssets } from "#app/data/battle-anims"; import { getCharVariantFromDialogue } from "#app/data/dialogue"; import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; @@ -259,6 +259,9 @@ export class EncounterPhase extends BattlePhase { } if (e < (battle.double ? 2 : 1)) { if (battle.battleType === BattleType.WILD) { + for (const pokemon of globalScene.getField()) { + applyPreSummonAbAttrs(PreSummonAbAttr, pokemon, []); + } globalScene.field.add(enemyPokemon); battle.seenEnemyPartyMemberIds.add(enemyPokemon.id); const playerPokemon = globalScene.getPlayerPokemon(); @@ -545,7 +548,7 @@ export class EncounterPhase extends BattlePhase { const enemyField = globalScene.getEnemyField(); enemyField.forEach((enemyPokemon, e) => { - if (enemyPokemon.isShiny()) { + if (enemyPokemon.isShiny(true)) { globalScene.unshiftPhase(new ShinySparklePhase(BattlerIndex.ENEMY + e)); } /** This sets Eternatus' held item to be untransferrable, preventing it from being stolen */ diff --git a/src/phases/level-up-phase.ts b/src/phases/level-up-phase.ts index 31c7fabf451..c6ca17d583e 100644 --- a/src/phases/level-up-phase.ts +++ b/src/phases/level-up-phase.ts @@ -71,6 +71,7 @@ export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { if (!this.pokemon.pauseEvolutions) { const evolution = this.pokemon.getEvolution(); if (evolution) { + this.pokemon.breakIllusion() globalScene.unshiftPhase(new EvolutionPhase(this.pokemon, evolution, this.lastLevel)); } } diff --git a/src/phases/summon-phase.ts b/src/phases/summon-phase.ts index 621c8c8c2a9..7379d509e55 100644 --- a/src/phases/summon-phase.ts +++ b/src/phases/summon-phase.ts @@ -13,6 +13,7 @@ import { PostSummonPhase } from "./post-summon-phase"; import { GameOverPhase } from "./game-over-phase"; import { ShinySparklePhase } from "./shiny-sparkle-phase"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; +import { applyPreSummonAbAttrs, PreSummonAbAttr } from "#app/data/ability"; import { globalScene } from "#app/global-scene"; export class SummonPhase extends PartyMemberPokemonPhase { @@ -27,6 +28,7 @@ export class SummonPhase extends PartyMemberPokemonPhase { start() { super.start(); + applyPreSummonAbAttrs(PreSummonAbAttr, this.getPokemon()); this.preSummon(); } @@ -126,7 +128,7 @@ export class SummonPhase extends PartyMemberPokemonPhase { this.player ? 36 : 248, this.player ? 80 : 44, "pb", - getPokeballAtlasKey(pokemon.pokeball), + getPokeballAtlasKey(pokemon.getPokeball(true)), ); pokeball.setVisible(false); pokeball.setOrigin(0.5, 0.625); @@ -175,7 +177,11 @@ export class SummonPhase extends PartyMemberPokemonPhase { } globalScene.currentBattle.seenEnemyPartyMemberIds.add(pokemon.id); } - addPokeballOpenParticles(pokemon.x, pokemon.y - 16, pokemon.pokeball); + addPokeballOpenParticles( + pokemon.x, + pokemon.y - 16, + pokemon.getPokeball(true), + ); globalScene.updateModifiers(this.player); globalScene.updateFieldScale(); pokemon.showInfo(); @@ -183,7 +189,7 @@ export class SummonPhase extends PartyMemberPokemonPhase { pokemon.setVisible(true); pokemon.getSprite().setVisible(true); pokemon.setScale(0.5); - pokemon.tint(getPokeballTintColor(pokemon.pokeball)); + pokemon.tint(getPokeballTintColor(pokemon.getPokeball(true))); pokemon.untint(250, "Sine.easeIn"); globalScene.updateFieldScale(); globalScene.tweens.add({ @@ -270,7 +276,7 @@ export class SummonPhase extends PartyMemberPokemonPhase { onEnd(): void { const pokemon = this.getPokemon(); - if (pokemon.isShiny()) { + if (pokemon.isShiny(true)) { globalScene.unshiftPhase(new ShinySparklePhase(pokemon.getBattlerIndex())); } diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index e0903ada275..d63cdb90f25 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -1,5 +1,11 @@ import { globalScene } from "#app/global-scene"; -import { applyPreSwitchOutAbAttrs, PostDamageForceSwitchAbAttr, PreSwitchOutAbAttr } from "#app/data/ability"; +import { + applyPreSummonAbAttrs, + applyPreSwitchOutAbAttrs, + PostDamageForceSwitchAbAttr, + PreSummonAbAttr, + PreSwitchOutAbAttr, +} from "#app/data/ability"; import { allMoves, ForceSwitchOutAttr } from "#app/data/moves/move"; import { getPokeballTintColor } from "#app/data/pokeball"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; @@ -99,7 +105,7 @@ export class SwitchSummonPhase extends SummonPhase { ); globalScene.playSound("se/pb_rel"); pokemon.hideInfo(); - pokemon.tint(getPokeballTintColor(pokemon.pokeball), 1, 250, "Sine.easeIn"); + pokemon.tint(getPokeballTintColor(pokemon.getPokeball(true)), 1, 250, "Sine.easeIn"); globalScene.tweens.add({ targets: pokemon, duration: 250, @@ -116,6 +122,7 @@ export class SwitchSummonPhase extends SummonPhase { const party = this.player ? this.getParty() : globalScene.getEnemyParty(); const switchedInPokemon = party[this.slotIndex]; this.lastPokemon = this.getPokemon(); + applyPreSummonAbAttrs(PreSummonAbAttr, switchedInPokemon); applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon); if (this.switchType === SwitchType.BATON_PASS && switchedInPokemon) { (this.player ? globalScene.getEnemyField() : globalScene.getPlayerField()).forEach(enemyPokemon => diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 63955b02de8..53146301666 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -1426,12 +1426,11 @@ export class GameData { ), ) // TODO: is this bang correct? : this.getSessionSaveData(); - const maxIntAttrValue = 0x80000000; const systemData = useCachedSystem ? this.parseSystemData(decrypt(localStorage.getItem(`data_${loggedInUser?.username}`)!, bypassLogin)) : this.getSystemSaveData(); // TODO: is this bang correct? - + const request = { system: systemData, session: sessionData, @@ -1448,7 +1447,6 @@ export class GameData { bypassLogin, ), ); - localStorage.setItem( `sessionData${globalScene.sessionSlotId ? globalScene.sessionSlotId : ""}_${loggedInUser?.username}`, encrypt(JSON.stringify(sessionData), bypassLogin), diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 7cdcb0c72c3..97ce494a43a 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -79,12 +79,14 @@ export default class PokemonData { this.id = source.id; this.player = sourcePokemon ? sourcePokemon.isPlayer() : source.player; this.species = sourcePokemon ? sourcePokemon.species.speciesId : source.species; - this.nickname = sourcePokemon ? sourcePokemon.nickname : source.nickname; + this.nickname = sourcePokemon + ? (!!sourcePokemon.summonData?.illusion ? sourcePokemon.summonData.illusion.basePokemon.nickname : sourcePokemon.nickname) + : source.nickname; this.formIndex = Math.max(Math.min(source.formIndex, getPokemonSpecies(this.species).forms.length - 1), 0); this.abilityIndex = source.abilityIndex; this.passive = source.passive; - this.shiny = source.shiny; - this.variant = source.variant; + this.shiny = sourcePokemon ? sourcePokemon.isShiny() : source.shiny; + this.variant = sourcePokemon ? sourcePokemon.getVariant() : source.variant; this.pokeball = source.pokeball; this.level = source.level; this.exp = source.exp; @@ -117,8 +119,12 @@ export default class PokemonData { this.fusionSpecies = sourcePokemon ? sourcePokemon.fusionSpecies?.speciesId : source.fusionSpecies; this.fusionFormIndex = source.fusionFormIndex; this.fusionAbilityIndex = source.fusionAbilityIndex; - this.fusionShiny = source.fusionShiny; - this.fusionVariant = source.fusionVariant; + this.fusionShiny = sourcePokemon + ? (!!sourcePokemon.summonData?.illusion ? sourcePokemon.summonData.illusion.basePokemon.fusionShiny : sourcePokemon.fusionShiny) + : source.fusionShiny; + this.fusionVariant = sourcePokemon + ? (!!sourcePokemon.summonData?.illusion ? sourcePokemon.summonData.illusion.basePokemon.fusionVariant : sourcePokemon.fusionVariant) + : source.fusionVariant; this.fusionGender = source.fusionGender; this.fusionLuck = source.fusionLuck !== undefined ? source.fusionLuck : source.fusionShiny ? source.fusionVariant + 1 : 0; @@ -174,6 +180,7 @@ export default class PokemonData { this.summonData.types = source.summonData.types; this.summonData.speciesForm = source.summonData.speciesForm; this.summonDataSpeciesFormIndex = source.summonDataSpeciesFormIndex; + this.summonData.illusionBroken = source.summonData.illusionBroken; if (source.summonData.tags) { this.summonData.tags = source.summonData.tags?.map(t => loadBattlerTag(t)); @@ -219,6 +226,7 @@ export default class PokemonData { if (this.summonData) { // when loading from saved session, recover summonData.speciesFrom and form index species object // used to stay transformed on reload session + if (this.summonData.speciesForm) { this.summonData.speciesForm = getPokemonSpeciesForm( this.summonData.speciesForm.speciesId, diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index 2b205329ab8..06c5f7fb3f1 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -356,7 +356,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { }); this.teraIcon.on("pointerout", () => globalScene.ui.hideTooltip()); - const isFusion = pokemon.isFusion(); + const isFusion = pokemon.isFusion(true); this.splicedIcon.setPositionRelative( this.nameText, @@ -375,7 +375,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { } const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny; - const baseVariant = !doubleShiny ? pokemon.getVariant() : pokemon.variant; + const baseVariant = !doubleShiny ? pokemon.getVariant(true) : pokemon.variant; this.shinyIcon.setPositionRelative( this.nameText, @@ -617,6 +617,11 @@ export default class BattleInfo extends Phaser.GameObjects.Container { return resolve(); } + const gender: Gender = !!pokemon.summonData?.illusion ? pokemon.summonData?.illusion.gender : pokemon.gender; + + this.genderText.setText(getGenderSymbol(gender)); + this.genderText.setColor(getGenderColor(gender)); + const nameUpdated = this.lastName !== pokemon.getNameToRender(); if (nameUpdated) { @@ -638,8 +643,10 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.lastTeraType = teraType; } + const isFusion = pokemon.isFusion(true); + if (nameUpdated || teraTypeUpdated) { - this.splicedIcon.setVisible(!!pokemon.fusionSpecies); + this.splicedIcon.setVisible(isFusion); this.teraIcon.setPositionRelative( this.nameText, @@ -764,7 +771,17 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.lastStats = statsStr; } - this.shinyIcon.setVisible(pokemon.isShiny()); + 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(); }); @@ -777,10 +794,11 @@ export default class BattleInfo extends Phaser.GameObjects.Container { const nameSizeTest = addTextObject(0, 0, displayName, TextStyle.BATTLE_INFO); nameTextWidth = nameSizeTest.displayWidth; + const gender: Gender = !!pokemon.summonData?.illusion ? pokemon.summonData?.illusion.gender : pokemon.gender; while ( nameTextWidth > (this.player || !this.boss ? 60 : 98) - - ((pokemon.gender !== Gender.GENDERLESS ? 6 : 0) + + ((gender !== Gender.GENDERLESS ? 6 : 0) + (pokemon.fusionSpecies ? 8 : 0) + (pokemon.isShiny() ? 8 : 0) + (Math.min(pokemon.level.toString().length, 3) - 3) * 8) diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 3775dbc2228..27985629e3d 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -306,6 +306,9 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { pokemon, pokemonMove.getMove(), !opponent.battleData?.abilityRevealed, + undefined, + undefined, + true ); if (effectiveness === undefined) { return undefined; @@ -350,7 +353,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { const moveColors = opponents .map(opponent => - opponent.getMoveEffectiveness(pokemon, pokemonMove.getMove(), !opponent.battleData.abilityRevealed), + opponent.getMoveEffectiveness(pokemon, pokemonMove.getMove(), !opponent.battleData.abilityRevealed, undefined, undefined, true), ) .sort((a, b) => b - a) .map(effectiveness => getTypeDamageMultiplierColor(effectiveness ?? 0, "offense")); diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 61a98d79fbb..ba90108c274 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -193,18 +193,14 @@ export default class PartyUiHandler extends MessageUiHandler { public static FilterNonFainted = (pokemon: PlayerPokemon) => { if (pokemon.isFainted()) { - return i18next.t("partyUiHandler:noEnergy", { - pokemonName: getPokemonNameWithAffix(pokemon), - }); + return i18next.t("partyUiHandler:noEnergy", { pokemonName: getPokemonNameWithAffix(pokemon, false) }); } return null; }; public static FilterFainted = (pokemon: PlayerPokemon) => { if (!pokemon.isFainted()) { - return i18next.t("partyUiHandler:hasEnergy", { - pokemonName: getPokemonNameWithAffix(pokemon), - }); + return i18next.t("partyUiHandler:hasEnergy", { pokemonName: getPokemonNameWithAffix(pokemon, false) }); } return null; }; @@ -218,9 +214,7 @@ export default class PartyUiHandler extends MessageUiHandler { const challengeAllowed = new BooleanHolder(true); applyChallenges(ChallengeType.POKEMON_IN_BATTLE, pokemon, challengeAllowed); if (!challengeAllowed.value) { - return i18next.t("partyUiHandler:cantBeUsed", { - pokemonName: getPokemonNameWithAffix(pokemon), - }); + return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: getPokemonNameWithAffix(pokemon, false) }); } return null; }; @@ -232,9 +226,7 @@ export default class PartyUiHandler extends MessageUiHandler { m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id && m.matchType(modifier), ) as PokemonHeldItemModifier; if (matchingModifier && matchingModifier.stackCount === matchingModifier.getMaxStackCount()) { - return i18next.t("partyUiHandler:tooManyItems", { - pokemonName: getPokemonNameWithAffix(pokemon), - }); + return i18next.t("partyUiHandler:tooManyItems", { pokemonName: getPokemonNameWithAffix(pokemon, false) }); } return null; }; @@ -583,7 +575,7 @@ export default class PartyUiHandler extends MessageUiHandler { this.showText( i18next.t( pokemon.pauseEvolutions ? "partyUiHandler:pausedEvolutions" : "partyUiHandler:unpausedEvolutions", - { pokemonName: getPokemonNameWithAffix(pokemon) }, + { pokemonName: getPokemonNameWithAffix(pokemon, false) }, ), undefined, () => this.showText("", 0), @@ -596,14 +588,14 @@ export default class PartyUiHandler extends MessageUiHandler { this.showText( i18next.t("partyUiHandler:unspliceConfirmation", { fusionName: pokemon.fusionSpecies?.name, - pokemonName: pokemon.name, + pokemonName: pokemon.getName(), }), null, () => { ui.setModeWithoutClear( Mode.CONFIRM, () => { - const fusionName = pokemon.name; + const fusionName = pokemon.getName(); pokemon.unfuse().then(() => { this.clearPartySlots(); this.populatePartySlots(); @@ -611,7 +603,7 @@ export default class PartyUiHandler extends MessageUiHandler { this.showText( i18next.t("partyUiHandler:wasReverted", { fusionName: fusionName, - pokemonName: pokemon.name, + pokemonName: pokemon.getName(false), }), undefined, () => { @@ -637,7 +629,7 @@ export default class PartyUiHandler extends MessageUiHandler { this.blockInput = true; this.showText( i18next.t("partyUiHandler:releaseConfirmation", { - pokemonName: getPokemonNameWithAffix(pokemon), + pokemonName: getPokemonNameWithAffix(pokemon, false), }), null, () => { @@ -1285,7 +1277,7 @@ export default class PartyUiHandler extends MessageUiHandler { doRelease(slotIndex: number): void { this.showText( - this.getReleaseMessage(getPokemonNameWithAffix(globalScene.getPlayerParty()[slotIndex])), + this.getReleaseMessage(getPokemonNameWithAffix(globalScene.getPlayerParty()[slotIndex], false)), null, () => { this.clearPartySlots(); @@ -1495,7 +1487,7 @@ class PartySlot extends Phaser.GameObjects.Container { const slotInfoContainer = globalScene.add.container(0, 0); this.add(slotInfoContainer); - let displayName = this.pokemon.getNameToRender(); + let displayName = this.pokemon.getNameToRender(false); let nameTextWidth: number; const nameSizeTest = addTextObject(0, 0, displayName, TextStyle.PARTY); @@ -1575,12 +1567,12 @@ class PartySlot extends Phaser.GameObjects.Container { } if (this.pokemon.isShiny()) { - const doubleShiny = this.pokemon.isFusion() && this.pokemon.shiny && this.pokemon.fusionShiny; + const doubleShiny = this.pokemon.isDoubleShiny(false); const shinyStar = globalScene.add.image(0, 0, `shiny_star_small${doubleShiny ? "_1" : ""}`); shinyStar.setOrigin(0, 0); shinyStar.setPositionRelative(this.slotName, -9, 3); - shinyStar.setTint(getVariantTint(!doubleShiny ? this.pokemon.getVariant() : this.pokemon.variant)); + shinyStar.setTint(getVariantTint(this.pokemon.getBaseVariant(doubleShiny))); slotInfoContainer.add(shinyStar); @@ -1588,7 +1580,9 @@ class PartySlot extends Phaser.GameObjects.Container { const fusionShinyStar = globalScene.add.image(0, 0, "shiny_star_small_2"); fusionShinyStar.setOrigin(0, 0); fusionShinyStar.setPosition(shinyStar.x, shinyStar.y); - fusionShinyStar.setTint(getVariantTint(this.pokemon.fusionVariant)); + fusionShinyStar.setTint( + getVariantTint(this.pokemon.summonData?.illusion?.basePokemon.fusionVariant ?? this.pokemon.fusionVariant), + ); slotInfoContainer.add(fusionShinyStar); } diff --git a/src/ui/rename-form-ui-handler.ts b/src/ui/rename-form-ui-handler.ts index 91c0025d283..7083f83865b 100644 --- a/src/ui/rename-form-ui-handler.ts +++ b/src/ui/rename-form-ui-handler.ts @@ -38,7 +38,7 @@ export default class RenameFormUiHandler extends FormModalUiHandler { if (super.show(args)) { const config = args[0] as ModalConfig; if (args[1] && typeof (args[1] as PlayerPokemon).getNameToRender === "function") { - this.inputs[0].text = (args[1] as PlayerPokemon).getNameToRender(); + this.inputs[0].text = (args[1] as PlayerPokemon).getNameToRender(false); } else { this.inputs[0].text = args[1]; } diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 1e0924aa2c5..04bcf71d7ae 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -357,8 +357,14 @@ export default class SummaryUiHandler extends UiHandler { this.pokemonSprite.setPipelineData("isTerastallized", this.pokemon.isTerastallized); this.pokemonSprite.setPipelineData("ignoreTimeTint", true); this.pokemonSprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey()); - this.pokemonSprite.setPipelineData("shiny", this.pokemon.shiny); - this.pokemonSprite.setPipelineData("variant", this.pokemon.variant); + this.pokemonSprite.setPipelineData( + "shiny", + this.pokemon.summonData?.illusion?.basePokemon.shiny ?? this.pokemon.shiny, + ); + this.pokemonSprite.setPipelineData( + "variant", + this.pokemon.summonData?.illusion?.basePokemon.variant ?? this.pokemon.variant, + ); ["spriteColors", "fusionSpriteColors"].map(k => { delete this.pokemonSprite.pipelineData[`${k}Base`]; if (this.pokemon?.summonData?.speciesForm) { @@ -368,7 +374,7 @@ export default class SummaryUiHandler extends UiHandler { }); this.pokemon.cry(); - this.nameText.setText(this.pokemon.getNameToRender()); + this.nameText.setText(this.pokemon.getNameToRender(false)); const isFusion = this.pokemon.isFusion(); @@ -426,8 +432,8 @@ export default class SummaryUiHandler extends UiHandler { this.friendshipShadow.setCrop(0, 0, 16, 16 - 16 * ((this.pokemon?.friendship || 0) / 255)); - const doubleShiny = isFusion && this.pokemon.shiny && this.pokemon.fusionShiny; - const baseVariant = !doubleShiny ? this.pokemon.getVariant() : this.pokemon.variant; + const doubleShiny = this.pokemon.isDoubleShiny(false); + const baseVariant = this.pokemon.getBaseVariant(doubleShiny); this.shinyIcon.setPositionRelative( this.nameText, @@ -435,7 +441,7 @@ export default class SummaryUiHandler extends UiHandler { 3, ); this.shinyIcon.setTexture(`shiny_star${doubleShiny ? "_1" : ""}`); - this.shinyIcon.setVisible(this.pokemon.isShiny()); + this.shinyIcon.setVisible(this.pokemon.isShiny(false)); this.shinyIcon.setTint(getVariantTint(baseVariant)); if (this.shinyIcon.visible) { const shinyDescriptor = @@ -455,7 +461,9 @@ export default class SummaryUiHandler extends UiHandler { this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y); this.fusionShinyIcon.setVisible(doubleShiny); if (isFusion) { - this.fusionShinyIcon.setTint(getVariantTint(this.pokemon.fusionVariant)); + this.fusionShinyIcon.setTint( + getVariantTint(this.pokemon.summonData?.illusion?.basePokemon.fusionVariant ?? this.pokemon.fusionVariant), + ); } this.pokeball.setFrame(getPokeballAtlasKey(this.pokemon.pokeball)); @@ -838,7 +846,7 @@ export default class SummaryUiHandler extends UiHandler { return typeIcon; }; - const types = this.pokemon?.getTypes(false, false, true)!; // TODO: is this bang correct? + const types = this.pokemon?.getTypes(false, false, true, false)!; // TODO: is this bang correct? profileContainer.add(getTypeIcon(0, types[0])); if (types.length > 1) { profileContainer.add(getTypeIcon(1, types[1])); diff --git a/test/abilities/illusion.test.ts b/test/abilities/illusion.test.ts new file mode 100644 index 00000000000..aa77aa701b2 --- /dev/null +++ b/test/abilities/illusion.test.ts @@ -0,0 +1,144 @@ +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import Phaser from "phaser"; +import GameManager from "#test/testUtils/gameManager"; +import { Species } from "#enums/species"; +import { TurnEndPhase } from "#app/phases/turn-end-phase"; +import { Moves } from "#enums/moves"; +import { Abilities } from "#enums/abilities"; +import { PokeballType } from "#app/enums/pokeball"; +import { Gender } from "#app/data/gender"; + +describe("Abilities - Illusion", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override.battleType("single"); + game.override.enemySpecies(Species.ZORUA); + game.override.enemyAbility(Abilities.ILLUSION); + game.override.enemyMoveset(Moves.TACKLE); + game.override.enemyHeldItems([{ name: "WIDE_LENS", count: 3 }]); + + game.override.moveset([Moves.WORRY_SEED, Moves.SOAK, Moves.TACKLE]); + game.override.startingHeldItems([{ name: "WIDE_LENS", count: 3 }]); + }); + + it("creates illusion at the start", async () => { + await game.classicMode.startBattle([Species.ZOROARK, Species.AXEW]); + const zoroark = game.scene.getPlayerPokemon()!; + const zorua = game.scene.getEnemyPokemon()!; + + expect(!!zoroark.summonData?.illusion).equals(true); + expect(!!zorua.summonData?.illusion).equals(true); + }); + + it("break after receiving damaging move", async () => { + await game.classicMode.startBattle([Species.AXEW]); + game.move.select(Moves.TACKLE); + + await game.phaseInterceptor.to(TurnEndPhase); + + const zorua = game.scene.getEnemyPokemon()!; + + expect(!!zorua.summonData?.illusion).equals(false); + expect(zorua.name).equals("Zorua"); + }); + + it("break after getting ability changed", async () => { + await game.classicMode.startBattle([Species.AXEW]); + game.move.select(Moves.WORRY_SEED); + + await game.phaseInterceptor.to(TurnEndPhase); + + const zorua = game.scene.getEnemyPokemon()!; + + expect(!!zorua.summonData?.illusion).equals(false); + }); + + it("break if the ability is suppressed", async () => { + game.override.enemyAbility(Abilities.NEUTRALIZING_GAS); + await game.classicMode.startBattle([Species.KOFFING]); + + const zorua = game.scene.getEnemyPokemon()!; + + expect(!!zorua.summonData?.illusion).equals(false); + }); + + it("causes enemy AI to consider the illusion's type instead of the actual type when considering move effectiveness", async () => { + game.override.enemyMoveset([Moves.FLAMETHROWER, Moves.PSYCHIC, Moves.TACKLE]); + await game.classicMode.startBattle([Species.ZOROARK, Species.AXEW]); + + const enemy = game.scene.getEnemyPokemon()!; + const zoroark = game.scene.getPlayerPokemon()!; + + const flameThrower = enemy.getMoveset()[0]!.getMove(); + const psychic = enemy.getMoveset()[1]!.getMove(); + const flameThrowerEffectiveness = zoroark.getAttackTypeEffectiveness( + flameThrower.type, + enemy, + undefined, + undefined, + flameThrower, + true, + ); + const psychicEffectiveness = zoroark.getAttackTypeEffectiveness( + psychic.type, + enemy, + undefined, + undefined, + psychic, + true, + ); + expect(psychicEffectiveness).above(flameThrowerEffectiveness); + }); + + it("does not break from indirect damage", async () => { + game.override.enemySpecies(Species.GIGALITH); + game.override.enemyAbility(Abilities.SAND_STREAM); + game.override.enemyMoveset(Moves.WILL_O_WISP); + game.override.moveset([Moves.FLARE_BLITZ]); + + await game.classicMode.startBattle([Species.ZOROARK, Species.AZUMARILL]); + + game.move.select(Moves.FLARE_BLITZ); + + await game.phaseInterceptor.to(TurnEndPhase); + + const zoroark = game.scene.getPlayerPokemon()!; + + expect(!!zoroark.summonData?.illusion).equals(true); + }); + + it("copies the the name, nickname, gender, shininess, and pokeball from the illusion source", async () => { + game.override.enemyMoveset(Moves.SPLASH); + await game.classicMode.startBattle([Species.ABRA, Species.ZOROARK, Species.AXEW]); + const axew = game.scene.getPlayerParty().at(2)!; + axew.shiny = true; + axew.nickname = btoa(unescape(encodeURIComponent("axew nickname"))); + axew.gender = Gender.FEMALE; + axew.pokeball = PokeballType.GREAT_BALL; + + game.doSwitchPokemon(1); + + await game.phaseInterceptor.to(TurnEndPhase); + + const zoroark = game.scene.getPlayerPokemon()!; + + expect(zoroark.name).equals("Axew"); + expect(zoroark.getNameToRender()).equals("axew nickname"); + expect(zoroark.getGender(false, true)).equals(Gender.FEMALE); + expect(zoroark.isShiny(true)).equals(true); + expect(zoroark.getPokeball(true)).equals(PokeballType.GREAT_BALL); + }); +}); From c82e01eed377cacc5d3ffd2ef9caa27270e8a2dc Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Mon, 14 Apr 2025 10:31:26 -0500 Subject: [PATCH 29/33] [Refactor] Move many interfaces and enums to their own file (#5646) * Move LearnMoveSituation to its own file * Remove unused selfStatLowerMoves array * Move all-moves to its own file * Move TurnMove interface to its own file * move AiType to its own file * Move PokemonMove to its own file * Move DamageCalculationResult interface to its own file * Move fieldPosition to its own file * Move hit-result to its own file * Move DamageResult to its own file * Move SpeciesWildEvolutionDelay to its own file * move EvolutionItem to its own file --- src/@types/damage-result.ts | 10 + src/battle-scene.ts | 2 +- src/battle.ts | 3 +- src/data/ability.ts | 9 +- src/data/arena-tag.ts | 5 +- src/data/balance/egg-moves.ts | 2 +- src/data/balance/pokemon-evolutions.ts | 60 +---- src/data/battle-anims.ts | 3 +- src/data/battler-tags.ts | 5 +- src/data/berry.ts | 2 +- src/data/challenge.ts | 2 +- src/data/moves/all-moves.ts | 3 + src/data/moves/move.ts | 21 +- src/data/moves/pokemon-move.ts | 93 ++++++++ .../encounters/absolute-avarice-encounter.ts | 3 +- .../encounters/bug-type-superfan-encounter.ts | 4 +- .../encounters/clowning-around-encounter.ts | 2 +- .../encounters/dancing-lessons-encounter.ts | 3 +- .../encounters/field-trip-encounter.ts | 3 +- .../encounters/fiery-fallout-encounter.ts | 2 +- .../encounters/fun-and-games-encounter.ts | 2 +- .../global-trade-system-encounter.ts | 3 +- .../encounters/lost-at-sea-encounter.ts | 2 +- .../slumbering-snorlax-encounter.ts | 3 +- .../encounters/the-strong-stuff-encounter.ts | 2 +- .../encounters/trash-to-treasure-encounter.ts | 2 +- .../encounters/uncommon-breed-encounter.ts | 2 +- .../encounters/weird-dream-encounter.ts | 2 +- .../mystery-encounter-requirements.ts | 3 +- .../mystery-encounters/mystery-encounter.ts | 3 +- .../can-learn-move-requirement.ts | 2 +- .../utils/encounter-phase-utils.ts | 7 +- src/data/pokemon-forms.ts | 2 +- src/data/pokemon-species.ts | 7 +- src/data/trainers/trainer-config.ts | 2 +- src/enums/ai-type.ts | 5 + src/enums/evolution-item.ts | 48 ++++ src/enums/field-position.ts | 5 + src/enums/hit-result.ts | 15 ++ src/enums/learn-move-context.ts | 8 + src/enums/species-wild-evolution-delay.ts | 8 + src/field/damage-number-handler.ts | 4 +- src/field/pokemon.ts | 206 ++---------------- src/interfaces/attack-move-result.ts | 12 + src/interfaces/damage-calculation-result.ts | 11 + src/interfaces/turn-move.ts | 12 + src/modifier/modifier-type.ts | 9 +- src/modifier/modifier.ts | 2 +- src/overrides.ts | 2 +- src/phases/command-phase.ts | 5 +- src/phases/damage-anim-phase.ts | 3 +- src/phases/encounter-phase.ts | 2 +- src/phases/evolution-phase.ts | 10 +- src/phases/faint-phase.ts | 7 +- src/phases/learn-move-phase.ts | 2 +- src/phases/move-charge-phase.ts | 2 +- src/phases/move-effect-phase.ts | 5 +- src/phases/move-header-phase.ts | 2 +- src/phases/move-phase.ts | 4 +- src/phases/pokemon-heal-phase.ts | 2 +- src/phases/pokemon-transform-phase.ts | 2 +- src/phases/select-target-phase.ts | 2 +- src/phases/summon-phase.ts | 2 +- src/phases/switch-summon-phase.ts | 3 +- src/phases/toggle-double-position-phase.ts | 2 +- src/phases/turn-start-phase.ts | 5 +- src/phases/weather-effect-phase.ts | 2 +- src/system/game-data.ts | 2 +- src/system/pokemon-data.ts | 3 +- src/ui/fight-ui-handler.ts | 2 +- src/ui/modifier-select-ui-handler.ts | 2 +- src/ui/party-ui-handler.ts | 6 +- src/ui/pokedex-page-ui-handler.ts | 2 +- src/ui/pokedex-scan-ui-handler.ts | 2 +- src/ui/pokedex-ui-handler.ts | 2 +- src/ui/pokemon-hatch-info-container.ts | 2 +- src/ui/starter-select-ui-handler.ts | 2 +- src/ui/summary-ui-handler.ts | 3 +- test/abilities/aura_break.test.ts | 2 +- test/abilities/battery.test.ts | 2 +- test/abilities/battle_bond.test.ts | 3 +- test/abilities/flower_veil.test.ts | 2 +- test/abilities/friend_guard.test.ts | 2 +- test/abilities/galvanize.test.ts | 4 +- test/abilities/hustle.test.ts | 2 +- test/abilities/infiltrator.test.ts | 2 +- test/abilities/libero.test.ts | 2 +- test/abilities/magic_bounce.test.ts | 2 +- test/abilities/power_spot.test.ts | 2 +- test/abilities/protean.test.ts | 2 +- test/abilities/sap_sipper.test.ts | 3 +- test/abilities/serene_grace.test.ts | 2 +- test/abilities/sheer_force.test.ts | 3 +- test/abilities/steely_spirit.test.ts | 2 +- test/abilities/supreme_overlord.test.ts | 2 +- test/abilities/tera_shell.test.ts | 2 +- test/abilities/unburden.test.ts | 3 +- test/abilities/wimp_out.test.ts | 9 +- test/abilities/wonder_skin.test.ts | 2 +- test/arena/arena_gravity.test.ts | 2 +- test/arena/grassy_terrain.test.ts | 2 +- test/arena/weather_fog.test.ts | 2 +- test/arena/weather_strong_winds.test.ts | 2 +- test/battle/damage_calculation.test.ts | 2 +- test/battlerTags/substitute.test.ts | 6 +- test/enemy_command.test.ts | 4 +- test/evolution.test.ts | 7 +- test/imports.test.ts | 2 +- test/items/reviver_seed.test.ts | 2 +- test/moves/astonish.test.ts | 2 +- test/moves/aurora_veil.test.ts | 3 +- test/moves/burning_jealousy.test.ts | 2 +- test/moves/ceaseless_edge.test.ts | 2 +- test/moves/copycat.test.ts | 3 +- test/moves/destiny_bond.test.ts | 2 +- test/moves/diamond_storm.test.ts | 2 +- test/moves/dig.test.ts | 2 +- test/moves/dragon_tail.test.ts | 2 +- test/moves/dynamax_cannon.test.ts | 2 +- test/moves/effectiveness.test.ts | 2 +- test/moves/fell_stinger.test.ts | 2 +- test/moves/fly.test.ts | 2 +- test/moves/freezy_frost.test.ts | 2 +- test/moves/fusion_flare_bolt.test.ts | 2 +- test/moves/glaive_rush.test.ts | 2 +- test/moves/hard_press.test.ts | 2 +- test/moves/hyper_beam.test.ts | 2 +- test/moves/lash_out.test.ts | 2 +- test/moves/last_respects.test.ts | 2 +- test/moves/light_screen.test.ts | 3 +- test/moves/magic_coat.test.ts | 2 +- test/moves/metronome.test.ts | 3 +- test/moves/moongeist_beam.test.ts | 3 +- test/moves/pledge_moves.test.ts | 3 +- test/moves/powder.test.ts | 3 +- test/moves/protect.test.ts | 2 +- test/moves/rage_fist.test.ts | 2 +- test/moves/reflect.test.ts | 3 +- test/moves/retaliate.test.ts | 2 +- test/moves/rollout.test.ts | 2 +- test/moves/round.test.ts | 2 +- test/moves/scale_shot.test.ts | 2 +- test/moves/secret_power.test.ts | 2 +- test/moves/shell_side_arm.test.ts | 3 +- test/moves/shell_trap.test.ts | 2 +- test/moves/sketch.test.ts | 6 +- test/moves/solar_beam.test.ts | 2 +- test/moves/sparkly_swirl.test.ts | 2 +- test/moves/spectral_thief.test.ts | 2 +- test/moves/spit_up.test.ts | 4 +- test/moves/steamroller.test.ts | 4 +- test/moves/stockpile.test.ts | 2 +- test/moves/substitute.test.ts | 3 +- test/moves/swallow.test.ts | 2 +- test/moves/telekinesis.test.ts | 2 +- test/moves/tera_blast.test.ts | 5 +- test/moves/toxic.test.ts | 2 +- test/moves/triple_arrows.test.ts | 3 +- ...an-offer-you-cant-refuse-encounter.test.ts | 3 +- .../bug-type-superfan-encounter.test.ts | 2 +- .../clowning-around-encounter.test.ts | 2 +- .../dancing-lessons-encounter.test.ts | 2 +- .../fight-or-flight-encounter.test.ts | 2 +- .../encounters/part-timer-encounter.test.ts | 2 +- .../the-strong-stuff-encounter.test.ts | 2 +- .../trash-to-treasure-encounter.test.ts | 2 +- .../uncommon-breed-encounter.test.ts | 2 +- test/testUtils/helpers/moveHelper.ts | 2 +- 168 files changed, 490 insertions(+), 455 deletions(-) create mode 100644 src/@types/damage-result.ts create mode 100644 src/data/moves/all-moves.ts create mode 100644 src/data/moves/pokemon-move.ts create mode 100644 src/enums/ai-type.ts create mode 100644 src/enums/evolution-item.ts create mode 100644 src/enums/field-position.ts create mode 100644 src/enums/hit-result.ts create mode 100644 src/enums/learn-move-context.ts create mode 100644 src/enums/species-wild-evolution-delay.ts create mode 100644 src/interfaces/attack-move-result.ts create mode 100644 src/interfaces/damage-calculation-result.ts create mode 100644 src/interfaces/turn-move.ts diff --git a/src/@types/damage-result.ts b/src/@types/damage-result.ts new file mode 100644 index 00000000000..7086d843cf4 --- /dev/null +++ b/src/@types/damage-result.ts @@ -0,0 +1,10 @@ +import type { HitResult } from "#enums/hit-result"; + +export type DamageResult = + | HitResult.EFFECTIVE + | HitResult.SUPER_EFFECTIVE + | HitResult.NOT_VERY_EFFECTIVE + | HitResult.ONE_HIT_KO + | HitResult.CONFUSION + | HitResult.INDIRECT_KO + | HitResult.INDIRECT; diff --git a/src/battle-scene.ts b/src/battle-scene.ts index dd983f2b397..12dbfca68e8 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -51,7 +51,7 @@ import { initGameSpeed } from "#app/system/game-speed"; import { Arena, ArenaBase } from "#app/field/arena"; import { GameData } from "#app/system/game-data"; import { addTextObject, getTextColor, TextStyle } from "#app/ui/text"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "./data/moves/all-moves"; import { MusicPreference } from "#app/system/settings/settings"; import { getDefaultModifierTypeForTier, diff --git a/src/battle.ts b/src/battle.ts index fb5af223b8f..1122db2679a 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -15,7 +15,8 @@ import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/mod import type { PokeballType } from "#enums/pokeball"; import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { SpeciesFormKey } from "#enums/species-form-key"; -import type { EnemyPokemon, PlayerPokemon, TurnMove } from "#app/field/pokemon"; +import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import type { TurnMove } from "./interfaces/turn-move"; import type Pokemon from "#app/field/pokemon"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattleSpec } from "#enums/battle-spec"; diff --git a/src/data/ability.ts b/src/data/ability.ts index 3e32a624f9f..02cc12dd0f4 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -1,6 +1,8 @@ -import type { EnemyPokemon, PokemonMove } from "../field/pokemon"; +import type { EnemyPokemon } from "../field/pokemon"; +import type { PokemonMove } from "./moves/pokemon-move"; import type Pokemon from "../field/pokemon"; -import { HitResult, MoveResult, PlayerPokemon } from "../field/pokemon"; +import { MoveResult, PlayerPokemon } from "../field/pokemon"; +import { HitResult } from "#enums/hit-result"; import { PokemonType } from "#enums/pokemon-type"; import { BooleanHolder, NumberHolder, toDmgValue, isNullOrUndefined, randSeedItem, randSeedInt, type Constructor } from "#app/utils"; import { getPokemonNameWithAffix } from "../messages"; @@ -10,7 +12,8 @@ import { BattlerTagLapseType, GroundedTag } from "./battler-tags"; import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "#app/data/status-effect"; import { Gender } from "./gender"; import type Move from "./moves/move"; -import { AttackMove, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./moves/move"; +import { AttackMove, FlinchAttr, OneHitKOAttr, HitHealAttr, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./moves/move"; +import { allMoves } from "./moves/all-moves"; import { MoveFlags } from "#enums/MoveFlags"; import { MoveTarget } from "#enums/MoveTarget"; import { MoveCategory } from "#enums/MoveCategory"; diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 871f622f70a..c6a1515685f 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -2,12 +2,13 @@ import { globalScene } from "#app/global-scene"; import type { Arena } from "#app/field/arena"; import { PokemonType } from "#enums/pokemon-type"; import { BooleanHolder, NumberHolder, toDmgValue } from "#app/utils"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "./moves/all-moves"; import { MoveTarget } from "#enums/MoveTarget"; import { MoveCategory } from "#enums/MoveCategory"; import { getPokemonNameWithAffix } from "#app/messages"; import type Pokemon from "#app/field/pokemon"; -import { HitResult, PokemonMove } from "#app/field/pokemon"; +import { HitResult } from "#enums/hit-result"; +import { PokemonMove } from "./moves/pokemon-move"; import { StatusEffect } from "#enums/status-effect"; import type { BattlerIndex } from "#app/battle"; import { diff --git a/src/data/balance/egg-moves.ts b/src/data/balance/egg-moves.ts index 74f6a2c1afb..98f3347764c 100644 --- a/src/data/balance/egg-moves.ts +++ b/src/data/balance/egg-moves.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { getEnumKeys, getEnumValues } from "#app/utils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index 17f71f3c3c9..70b616be8e5 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -14,66 +14,10 @@ import { DamageMoneyRewardModifier, ExtraModifierModifier, MoneyMultiplierModifi import { SpeciesFormKey } from "#enums/species-form-key"; import { speciesStarterCosts } from "./starters"; import i18next from "i18next"; +import { SpeciesWildEvolutionDelay } from "#enums/species-wild-evolution-delay"; +import { EvolutionItem } from "#enums/evolution-item"; -export enum SpeciesWildEvolutionDelay { - NONE, - SHORT, - MEDIUM, - LONG, - VERY_LONG, - NEVER -} - -export enum EvolutionItem { - NONE, - - LINKING_CORD, - SUN_STONE, - MOON_STONE, - LEAF_STONE, - FIRE_STONE, - WATER_STONE, - THUNDER_STONE, - ICE_STONE, - DUSK_STONE, - DAWN_STONE, - SHINY_STONE, - CRACKED_POT, - SWEET_APPLE, - TART_APPLE, - STRAWBERRY_SWEET, - UNREMARKABLE_TEACUP, - UPGRADE, - DUBIOUS_DISC, - DRAGON_SCALE, - PRISM_SCALE, - RAZOR_CLAW, - RAZOR_FANG, - REAPER_CLOTH, - ELECTIRIZER, - MAGMARIZER, - PROTECTOR, - SACHET, - WHIPPED_DREAM, - SYRUPY_APPLE, - CHIPPED_POT, - GALARICA_CUFF, - GALARICA_WREATH, - AUSPICIOUS_ARMOR, - MALICIOUS_ARMOR, - MASTERPIECE_TEACUP, - SUN_FLUTE, - MOON_FLUTE, - - BLACK_AUGURITE = 51, - PEAT_BLOCK, - METAL_ALLOY, - SCROLL_OF_DARKNESS, - SCROLL_OF_WATERS, - LEADERS_CREST -} - /** * Pokemon Evolution tuple type consisting of: * @property 0 {@linkcode Species} The species of the Pokemon. diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index 511c80bee72..396cf71d984 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -1,5 +1,6 @@ import { globalScene } from "#app/global-scene"; -import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, SelfStatusMove, allMoves } from "./moves/move"; +import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, SelfStatusMove } from "./moves/move"; +import { allMoves } from "./moves/all-moves"; import { MoveFlags } from "#enums/MoveFlags"; import type Pokemon from "../field/pokemon"; import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils"; diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 76e91485460..c3dcfc49ef6 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -11,12 +11,12 @@ import { import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims"; import type Move from "#app/data/moves/move"; import { - allMoves, applyMoveAttrs, ConsecutiveUseDoublePowerAttr, HealOnAllyAttr, StatusCategoryOnAllyAttr, } from "#app/data/moves/move"; +import { allMoves } from "./moves/all-moves"; import { MoveFlags } from "#enums/MoveFlags"; import { MoveCategory } from "#enums/MoveCategory"; import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms"; @@ -24,7 +24,8 @@ import { getStatusEffectHealText } from "#app/data/status-effect"; import { TerrainType } from "#app/data/terrain"; import { PokemonType } from "#enums/pokemon-type"; import type Pokemon from "#app/field/pokemon"; -import { HitResult, MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#app/field/pokemon"; +import { HitResult } from "#enums/hit-result"; import { getPokemonNameWithAffix } from "#app/messages"; import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; diff --git a/src/data/berry.ts b/src/data/berry.ts index 8a58d337aa4..aaa0dda6e7f 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -1,6 +1,6 @@ import { getPokemonNameWithAffix } from "../messages"; import type Pokemon from "../field/pokemon"; -import { HitResult } from "../field/pokemon"; +import { HitResult } from "#enums/hit-result"; import { getStatusEffectHealText } from "./status-effect"; import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils"; import { diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 51616c3f00f..9a3c329a70b 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -6,7 +6,7 @@ import type PokemonSpecies from "#app/data/pokemon-species"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "./moves/pokemon-move"; import type { FixedBattleConfig } from "#app/battle"; import { ClassicFixedBossWaves, BattleType, getRandomTrainerFunc } from "#app/battle"; import Trainer, { TrainerVariant } from "#app/field/trainer"; diff --git a/src/data/moves/all-moves.ts b/src/data/moves/all-moves.ts new file mode 100644 index 00000000000..c7b6d11a08d --- /dev/null +++ b/src/data/moves/all-moves.ts @@ -0,0 +1,3 @@ +import type Move from "./move"; + +export const allMoves: Move[] = []; diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 962a13bb840..591894f5f1e 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -12,16 +12,17 @@ import { TypeBoostTag, } from "../battler-tags"; import { getPokemonNameWithAffix } from "../../messages"; -import type { AttackMoveResult, TurnMove } from "../../field/pokemon"; +import type { AttackMoveResult } from "#app/interfaces/attack-move-result"; +import type { TurnMove } from "#app/interfaces/turn-move"; import type Pokemon from "../../field/pokemon"; import { EnemyPokemon, - FieldPosition, - HitResult, MoveResult, PlayerPokemon, - PokemonMove, } from "../../field/pokemon"; +import { HitResult } from "#enums/hit-result"; +import { FieldPosition } from "#enums/field-position"; +import { PokemonMove } from "./pokemon-move"; import { getNonVolatileStatusEffects, getStatusEffectHealText, @@ -121,6 +122,7 @@ import { MoveFlags } from "#enums/MoveFlags"; import { MoveEffectTrigger } from "#enums/MoveEffectTrigger"; import { MultiHitType } from "#enums/MultiHitType"; import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves } from "./invalid-moves"; +import { allMoves } from "./all-moves"; type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean; @@ -8257,11 +8259,7 @@ export function getMoveTargets(user: Pokemon, move: Moves, replaceTarget?: MoveT return { targets: set.filter(p => p?.isActive(true)).map(p => p.getBattlerIndex()).filter(t => t !== undefined), multiple }; } -export const allMoves: Move[] = [ - new SelfStatusMove(Moves.NONE, PokemonType.NORMAL, MoveCategory.STATUS, -1, -1, 0, 1), -]; - -export const selfStatLowerMoves: Moves[] = []; +allMoves.push(new SelfStatusMove(Moves.NONE, PokemonType.NORMAL, MoveCategory.STATUS, -1, -1, 0, 1)); export function initMoves() { allMoves.push( @@ -11250,9 +11248,4 @@ export function initMoves() { new AttackMove(Moves.MALIGNANT_CHAIN, PokemonType.POISON, MoveCategory.SPECIAL, 100, 100, 5, 50, 0, 9) .attr(StatusEffectAttr, StatusEffect.TOXIC) ); - allMoves.map(m => { - if (m.getAttrs(StatStageChangeAttr).some(a => a.selfTarget && a.stages < 0)) { - selfStatLowerMoves.push(m.id); - } - }); } diff --git a/src/data/moves/pokemon-move.ts b/src/data/moves/pokemon-move.ts new file mode 100644 index 00000000000..49ccaba698b --- /dev/null +++ b/src/data/moves/pokemon-move.ts @@ -0,0 +1,93 @@ +import * as Utils from "#app/utils"; +import { allMoves } from "./all-moves"; +import type { Moves } from "#enums/moves"; +import type Pokemon from "#app/field/pokemon"; +import type Move from "./move"; + +/** + * Wrapper class for the {@linkcode Move} class for Pokemon to interact with. + * These are the moves assigned to a {@linkcode Pokemon} object. + * It links to {@linkcode Move} class via the move ID. + * Compared to {@linkcode Move}, this class also tracks if a move has received. + * PP Ups, amount of PP used, and things like that. + * @see {@linkcode isUsable} - checks if move is restricted, out of PP, or not implemented. + * @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID. + * @see {@linkcode usePp} - removes a point of PP from the move. + * @see {@linkcode getMovePp} - returns amount of PP a move currently has. + * @see {@linkcode getPpRatio} - returns the current PP amount / max PP amount. + * @see {@linkcode getName} - returns name of {@linkcode Move}. + **/ +export class PokemonMove { + public moveId: Moves; + public ppUsed: number; + public ppUp: number; + public virtual: boolean; + + /** + * If defined and nonzero, overrides the maximum PP of the move (e.g., due to move being copied by Transform). + * This also nullifies all effects of `ppUp`. + */ + public maxPpOverride?: number; + + constructor(moveId: Moves, ppUsed = 0, ppUp = 0, virtual = false, maxPpOverride?: number) { + this.moveId = moveId; + this.ppUsed = ppUsed; + this.ppUp = ppUp; + this.virtual = virtual; + this.maxPpOverride = maxPpOverride; + } + + /** + * Checks whether the move can be selected or performed by a Pokemon, without consideration for the move's targets. + * The move is unusable if it is out of PP, restricted by an effect, or unimplemented. + * + * @param pokemon - {@linkcode Pokemon} that would be using this move + * @param ignorePp - If `true`, skips the PP check + * @param ignoreRestrictionTags - If `true`, skips the check for move restriction tags (see {@link MoveRestrictionBattlerTag}) + * @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`. + */ + isUsable(pokemon: Pokemon, ignorePp = false, ignoreRestrictionTags = false): boolean { + if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon)) { + return false; + } + + if (this.getMove().name.endsWith(" (N)")) { + return false; + } + + return ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1; + } + + getMove(): Move { + return allMoves[this.moveId]; + } + + /** + * Sets {@link ppUsed} for this move and ensures the value does not exceed {@link getMovePp} + * @param {number} count Amount of PP to use + */ + usePp(count = 1) { + this.ppUsed = Math.min(this.ppUsed + count, this.getMovePp()); + } + + getMovePp(): number { + return this.maxPpOverride || this.getMove().pp + this.ppUp * Utils.toDmgValue(this.getMove().pp / 5); + } + + getPpRatio(): number { + return 1 - this.ppUsed / this.getMovePp(); + } + + getName(): string { + return this.getMove().name; + } + + /** + * Copies an existing move or creates a valid PokemonMove object from json representing one + * @param source - The data for the move to copy + * @return A valid pokemonmove object + */ + static loadMove(source: PokemonMove | any): PokemonMove { + return new PokemonMove(source.moveId, source.ppUsed, source.ppUp, source.virtual, source.maxPpOverride); + } +} diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index 85f40a41e51..b781f14fad1 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -7,7 +7,8 @@ import { transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type Pokemon from "#app/field/pokemon"; -import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; +import { EnemyPokemon } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index 1e4c9a3b957..c6971c42364 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -24,7 +24,7 @@ import { TrainerType } from "#enums/trainer-type"; import { Species } from "#enums/species"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { getEncounterText, showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { Moves } from "#enums/moves"; @@ -50,7 +50,7 @@ import { } from "#app/modifier/modifier"; import i18next from "i18next"; import MoveInfoOverlay from "#app/ui/move-info-overlay"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { ModifierTier } from "#app/modifier/modifier-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index eca99fc0c13..15fad6dacbf 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -37,7 +37,7 @@ import { Mode } from "#app/ui/ui"; import i18next from "i18next"; import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler"; import type { PlayerPokemon } from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { Ability } from "#app/data/ability"; import { BerryModifier } from "#app/modifier/modifier"; import { BerryType } from "#enums/berry-type"; diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index 75527e1f8c1..90ea6a69c0d 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -23,7 +23,8 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { TrainerSlot } from "#enums/trainer-slot"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; +import { EnemyPokemon } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { modifierTypes } from "#app/modifier/modifier-type"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; diff --git a/src/data/mystery-encounters/encounters/field-trip-encounter.ts b/src/data/mystery-encounters/encounters/field-trip-encounter.ts index a1964aa5ab4..4e330fab3d9 100644 --- a/src/data/mystery-encounters/encounters/field-trip-encounter.ts +++ b/src/data/mystery-encounters/encounters/field-trip-encounter.ts @@ -7,7 +7,8 @@ import { setEncounterExp, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; import { modifierTypes } from "#app/modifier/modifier-type"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 6118fe3d0de..d868184a7fa 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -26,7 +26,7 @@ import { Gender } from "#app/data/gender"; import { PokemonType } from "#enums/pokemon-type"; import { BattlerIndex } from "#app/battle"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { Moves } from "#enums/moves"; import { EncounterBattleAnim } from "#app/data/battle-anims"; import { WeatherType } from "#enums/weather-type"; diff --git a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts index 282c6c149ff..a9fc24c70b7 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -13,7 +13,7 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst import { TrainerSlot } from "#enums/trainer-slot"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { FieldPosition } from "#app/field/pokemon"; +import { FieldPosition } from "#enums/field-position"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index f80620647b0..fce496e5e17 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -26,7 +26,8 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { NumberHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle, randSeedItem } from "#app/utils"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; +import { EnemyPokemon } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { HiddenAbilityRateBoosterModifier, diff --git a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts index 97fd5783ebb..030678b77b1 100644 --- a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts +++ b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts @@ -11,7 +11,7 @@ import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encount import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; const OPTION_1_REQUIRED_MOVE = Moves.SURF; const OPTION_2_REQUIRED_MOVE = Moves.FLY; diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index bfa1204a8ba..97a17af43d0 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -21,7 +21,8 @@ import { import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { Moves } from "#enums/moves"; import { BattlerIndex } from "#app/battle"; -import { AiType, PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { AiType } from "#enums/ai-type"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index c994c6e993f..af0363f37e3 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -17,7 +17,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Species } from "#enums/species"; import { Nature } from "#enums/nature"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { Moves } from "#enums/moves"; diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index e60fe0ddc18..2203ac041f8 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -25,7 +25,7 @@ import { ModifierTier } from "#app/modifier/modifier-tier"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Moves } from "#enums/moves"; import { BattlerIndex } from "#app/battle"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { randSeedInt } from "#app/utils"; diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index ed1866c7a1b..4e3c238aeba 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -10,7 +10,7 @@ import { import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import type Pokemon from "#app/field/pokemon"; import type { EnemyPokemon } from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 22ec52e976c..be0c0bdff54 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -16,7 +16,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { NumberHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils"; import type PokemonSpecies from "#app/data/pokemon-species"; import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index f9aedf2c1a7..0c146fe485d 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -1,6 +1,7 @@ import { globalScene } from "#app/global-scene"; import { allAbilities } from "#app/data/ability"; -import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; +import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; +import { EvolutionItem } from "#enums/evolution-item"; import { Nature } from "#enums/nature"; import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; import { StatusEffect } from "#enums/status-effect"; diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index ff098d4d7dd..8010983f9f3 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -1,5 +1,6 @@ import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type { PokemonMove } from "../moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; diff --git a/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts b/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts index a7ffe3e26ca..598b0ffae70 100644 --- a/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts +++ b/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts @@ -1,6 +1,6 @@ import type { Moves } from "#app/enums/moves"; import type { PlayerPokemon } from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { isNullOrUndefined } from "#app/utils"; import { EncounterPokemonRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { globalScene } from "#app/global-scene"; diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index a9f6b787878..6ab650d5f9b 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -7,9 +7,12 @@ import { WEIGHT_INCREMENT_ON_SPAWN_MISS, } from "#app/data/mystery-encounters/mystery-encounters"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import type { AiType, PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type { AiType } from "#enums/ai-type"; import type Pokemon from "#app/field/pokemon"; -import { EnemyPokemon, FieldPosition, PokemonMove, PokemonSummonData } from "#app/field/pokemon"; +import { EnemyPokemon, PokemonSummonData } from "#app/field/pokemon"; +import { FieldPosition } from "#enums/field-position"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type"; import { getPartyLuckValue, diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index 63e166c7fc4..6f36bfde74f 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -1,7 +1,7 @@ import { PokemonFormChangeItemModifier } from "../modifier/modifier"; import type Pokemon from "../field/pokemon"; import { StatusEffect } from "#enums/status-effect"; -import { allMoves } from "./moves/move"; +import { allMoves } from "./moves/all-moves"; import { MoveCategory } from "#enums/MoveCategory"; import type { Constructor, nil } from "#app/utils"; import { Abilities } from "#enums/abilities"; diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index a27c00121dc..ced828fbc6b 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -13,11 +13,8 @@ import { uncatchableSpecies } from "#app/data/balance/biomes"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { GrowthRate } from "#app/data/exp"; import type { EvolutionLevel } from "#app/data/balance/pokemon-evolutions"; -import { - SpeciesWildEvolutionDelay, - pokemonEvolutions, - pokemonPrevolutions, -} from "#app/data/balance/pokemon-evolutions"; +import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; +import { SpeciesWildEvolutionDelay } from "#enums/species-wild-evolution-delay"; import { PokemonType } from "#enums/pokemon-type"; import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; import { diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 0ab7119dab9..c87f72bd912 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; import { modifierTypes } from "#app/modifier/modifier-type"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { toReadableString, isNullOrUndefined, randSeedItem, randSeedInt } from "#app/utils"; import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { getPokemonSpecies } from "#app/data/pokemon-species"; diff --git a/src/enums/ai-type.ts b/src/enums/ai-type.ts new file mode 100644 index 00000000000..13931172a4a --- /dev/null +++ b/src/enums/ai-type.ts @@ -0,0 +1,5 @@ +export enum AiType { + RANDOM, + SMART_RANDOM, + SMART +} diff --git a/src/enums/evolution-item.ts b/src/enums/evolution-item.ts new file mode 100644 index 00000000000..3b5e493b378 --- /dev/null +++ b/src/enums/evolution-item.ts @@ -0,0 +1,48 @@ +export enum EvolutionItem { + NONE, + + LINKING_CORD, + SUN_STONE, + MOON_STONE, + LEAF_STONE, + FIRE_STONE, + WATER_STONE, + THUNDER_STONE, + ICE_STONE, + DUSK_STONE, + DAWN_STONE, + SHINY_STONE, + CRACKED_POT, + SWEET_APPLE, + TART_APPLE, + STRAWBERRY_SWEET, + UNREMARKABLE_TEACUP, + UPGRADE, + DUBIOUS_DISC, + DRAGON_SCALE, + PRISM_SCALE, + RAZOR_CLAW, + RAZOR_FANG, + REAPER_CLOTH, + ELECTIRIZER, + MAGMARIZER, + PROTECTOR, + SACHET, + WHIPPED_DREAM, + SYRUPY_APPLE, + CHIPPED_POT, + GALARICA_CUFF, + GALARICA_WREATH, + AUSPICIOUS_ARMOR, + MALICIOUS_ARMOR, + MASTERPIECE_TEACUP, + SUN_FLUTE, + MOON_FLUTE, + + BLACK_AUGURITE = 51, + PEAT_BLOCK, + METAL_ALLOY, + SCROLL_OF_DARKNESS, + SCROLL_OF_WATERS, + LEADERS_CREST +} diff --git a/src/enums/field-position.ts b/src/enums/field-position.ts new file mode 100644 index 00000000000..5b7f9c6c570 --- /dev/null +++ b/src/enums/field-position.ts @@ -0,0 +1,5 @@ +export enum FieldPosition { + CENTER, + LEFT, + RIGHT +} diff --git a/src/enums/hit-result.ts b/src/enums/hit-result.ts new file mode 100644 index 00000000000..3e62587dd6c --- /dev/null +++ b/src/enums/hit-result.ts @@ -0,0 +1,15 @@ +export enum HitResult { + EFFECTIVE = 1, + SUPER_EFFECTIVE, + NOT_VERY_EFFECTIVE, + ONE_HIT_KO, + NO_EFFECT, + STATUS, + HEAL, + FAIL, + MISS, + INDIRECT, + IMMUNE, + CONFUSION, + INDIRECT_KO +} diff --git a/src/enums/learn-move-context.ts b/src/enums/learn-move-context.ts new file mode 100644 index 00000000000..26001cbcce8 --- /dev/null +++ b/src/enums/learn-move-context.ts @@ -0,0 +1,8 @@ +export enum LearnMoveContext { + MISC, + LEVEL_UP, + RELEARN, + EVOLUTION, + EVOLUTION_FUSED, // If fusionSpecies has Evolved + EVOLUTION_FUSED_BASE, // If fusion's base species has Evolved +} diff --git a/src/enums/species-wild-evolution-delay.ts b/src/enums/species-wild-evolution-delay.ts new file mode 100644 index 00000000000..7555dc0e8f6 --- /dev/null +++ b/src/enums/species-wild-evolution-delay.ts @@ -0,0 +1,8 @@ +export enum SpeciesWildEvolutionDelay { + NONE, + SHORT, + MEDIUM, + LONG, + VERY_LONG, + NEVER +} diff --git a/src/field/damage-number-handler.ts b/src/field/damage-number-handler.ts index a527b148fff..3bb001bf005 100644 --- a/src/field/damage-number-handler.ts +++ b/src/field/damage-number-handler.ts @@ -1,7 +1,7 @@ import { TextStyle, addTextObject } from "../ui/text"; -import type { DamageResult } from "./pokemon"; +import type { DamageResult } from "#app/@types/damage-result"; import type Pokemon from "./pokemon"; -import { HitResult } from "./pokemon"; +import { HitResult } from "#enums/hit-result"; import { formatStat, fixedInt } from "#app/utils"; import type { BattlerIndex } from "../battle"; import { globalScene } from "#app/global-scene"; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index b59b7ba01fe..162a5118f65 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -17,7 +17,6 @@ import { applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, - allMoves, TypelessAttr, CritOnlyAttr, getMoveTargets, @@ -42,6 +41,7 @@ import { VariableMoveTypeChartAttr, HpSplitAttr, } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { MoveTarget } from "#enums/MoveTarget"; import { MoveCategory } from "#enums/MoveCategory"; import type { PokemonSpeciesForm } from "#app/data/pokemon-species"; @@ -214,7 +214,7 @@ import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeMoveLearnedTrigger, - SpeciesFormChangePostMoveTrigger + SpeciesFormChangePostMoveTrigger, } from "#app/data/pokemon-forms"; import { TerrainType } from "#app/data/terrain"; import type { TrainerSlot } from "#enums/trainer-slot"; @@ -259,21 +259,15 @@ import { MoveFlags } from "#enums/MoveFlags"; import { timedEventManager } from "#app/global-event-manager"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; import { ResetStatusPhase } from "#app/phases/reset-status-phase"; - -export enum LearnMoveSituation { - MISC, - LEVEL_UP, - RELEARN, - EVOLUTION, - EVOLUTION_FUSED, // If fusionSpecies has Evolved - EVOLUTION_FUSED_BASE, // If fusion's base species has Evolved -} - -export enum FieldPosition { - CENTER, - LEFT, - RIGHT, -} +import { LearnMoveContext } from "#enums/learn-move-context"; +import { TurnMove } from "#app/interfaces/turn-move"; +import { AiType } from "#enums/ai-type"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { DamageCalculationResult } from "#app/interfaces/damage-calculation-result"; +import { FieldPosition } from "#enums/field-position"; +import { AttackMoveResult } from "#app/interfaces/attack-move-result"; +import { HitResult } from "#enums/hit-result"; +import { DamageResult } from "#app/@types/damage-result"; export default abstract class Pokemon extends Phaser.GameObjects.Container { public id: number; @@ -2925,7 +2919,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { includeEvolutionMoves = false, simulateEvolutionChain = false, includeRelearnerMoves = false, - learnSituation: LearnMoveSituation = LearnMoveSituation.MISC, + learnSituation: LearnMoveContext = LearnMoveContext.MISC, ): LevelMoves { const ret: LevelMoves = []; let levelMoves: LevelMoves = []; @@ -2933,7 +2927,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { startingLevel = this.level; } if ( - learnSituation === LearnMoveSituation.EVOLUTION_FUSED && + learnSituation === LearnMoveContext.EVOLUTION_FUSED && this.fusionSpecies ) { // For fusion evolutions, get ONLY the moves of the component mon that evolved @@ -2985,7 +2979,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if ( this.fusionSpecies && - learnSituation !== LearnMoveSituation.EVOLUTION_FUSED_BASE + learnSituation !== LearnMoveContext.EVOLUTION_FUSED_BASE ) { // For fusion evolutions, get ONLY the moves of the component mon that evolved if (simulateEvolutionChain) { @@ -7779,24 +7773,6 @@ interface IllusionData { level?: number } -export interface TurnMove { - move: Moves; - targets: BattlerIndex[]; - result?: MoveResult; - virtual?: boolean; - turn?: number; - ignorePP?: boolean; -} - -export interface AttackMoveResult { - move: Moves; - result: DamageResult; - damage: number; - critical: boolean; - sourceId: number; - sourceBattlerIndex: BattlerIndex; -} - export class PokemonSummonData { /** [Atk, Def, SpAtk, SpDef, Spd, Acc, Eva] */ public statStages: number[] = [0, 0, 0, 0, 0, 0, 0]; @@ -7869,12 +7845,6 @@ export class PokemonTurnData { public extraTurns = 0; } -export enum AiType { - RANDOM, - SMART_RANDOM, - SMART, -} - export enum MoveResult { PENDING, SUCCESS, @@ -7882,151 +7852,3 @@ export enum MoveResult { MISS, OTHER, } - -export enum HitResult { - EFFECTIVE = 1, - SUPER_EFFECTIVE, - NOT_VERY_EFFECTIVE, - ONE_HIT_KO, - NO_EFFECT, - STATUS, - HEAL, - FAIL, - MISS, - INDIRECT, - IMMUNE, - CONFUSION, - INDIRECT_KO, -} - -export type DamageResult = - | HitResult.EFFECTIVE - | HitResult.SUPER_EFFECTIVE - | HitResult.NOT_VERY_EFFECTIVE - | HitResult.ONE_HIT_KO - | HitResult.CONFUSION - | HitResult.INDIRECT_KO - | HitResult.INDIRECT; - -/** Interface containing the results of a damage calculation for a given move */ -export interface DamageCalculationResult { - /** `true` if the move was cancelled (thus suppressing "No Effect" messages) */ - cancelled: boolean; - /** The effectiveness of the move */ - result: HitResult; - /** The damage dealt by the move */ - damage: number; -} - -/** - * Wrapper class for the {@linkcode Move} class for Pokemon to interact with. - * These are the moves assigned to a {@linkcode Pokemon} object. - * It links to {@linkcode Move} class via the move ID. - * Compared to {@linkcode Move}, this class also tracks if a move has received. - * PP Ups, amount of PP used, and things like that. - * @see {@linkcode isUsable} - checks if move is restricted, out of PP, or not implemented. - * @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID. - * @see {@linkcode usePp} - removes a point of PP from the move. - * @see {@linkcode getMovePp} - returns amount of PP a move currently has. - * @see {@linkcode getPpRatio} - returns the current PP amount / max PP amount. - * @see {@linkcode getName} - returns name of {@linkcode Move}. - **/ -export class PokemonMove { - public moveId: Moves; - public ppUsed: number; - public ppUp: number; - public virtual: boolean; - - /** - * If defined and nonzero, overrides the maximum PP of the move (e.g., due to move being copied by Transform). - * This also nullifies all effects of `ppUp`. - */ - public maxPpOverride?: number; - - constructor( - moveId: Moves, - ppUsed = 0, - ppUp = 0, - virtual = false, - maxPpOverride?: number, - ) { - this.moveId = moveId; - this.ppUsed = ppUsed; - this.ppUp = ppUp; - this.virtual = virtual; - this.maxPpOverride = maxPpOverride; - } - - /** - * Checks whether the move can be selected or performed by a Pokemon, without consideration for the move's targets. - * The move is unusable if it is out of PP, restricted by an effect, or unimplemented. - * - * @param {Pokemon} 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}) - * @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`. - */ - isUsable( - pokemon: Pokemon, - ignorePp = false, - ignoreRestrictionTags = false, - ): boolean { - if ( - this.moveId && - !ignoreRestrictionTags && - pokemon.isMoveRestricted(this.moveId, pokemon) - ) { - return false; - } - - if (this.getMove().name.endsWith(" (N)")) { - return false; - } - - return ( - ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1 - ); - } - - getMove(): Move { - return allMoves[this.moveId]; - } - - /** - * Sets {@link ppUsed} for this move and ensures the value does not exceed {@link getMovePp} - * @param {number} count Amount of PP to use - */ - usePp(count = 1) { - this.ppUsed = Math.min(this.ppUsed + count, this.getMovePp()); - } - - getMovePp(): number { - return ( - this.maxPpOverride || - this.getMove().pp + this.ppUp * toDmgValue(this.getMove().pp / 5) - ); - } - - getPpRatio(): number { - return 1 - this.ppUsed / this.getMovePp(); - } - - getName(): string { - return this.getMove().name; - } - - /** - * Copies an existing move or creates a valid PokemonMove object from json representing one - * @param {PokemonMove | any} source The data for the move to copy - * @return {PokemonMove} A valid pokemonmove object - */ - static loadMove(source: PokemonMove | any): PokemonMove { - return new PokemonMove( - source.moveId, - source.ppUsed, - source.ppUp, - source.virtual, - source.maxPpOverride, - ); - } -} diff --git a/src/interfaces/attack-move-result.ts b/src/interfaces/attack-move-result.ts new file mode 100644 index 00000000000..f91d31a69ee --- /dev/null +++ b/src/interfaces/attack-move-result.ts @@ -0,0 +1,12 @@ +import type { BattlerIndex } from "#app/battle"; +import type { DamageResult } from "#app/@types/damage-result"; +import type { Moves } from "#enums/moves"; + +export interface AttackMoveResult { + move: Moves; + result: DamageResult; + damage: number; + critical: boolean; + sourceId: number; + sourceBattlerIndex: BattlerIndex; +} diff --git a/src/interfaces/damage-calculation-result.ts b/src/interfaces/damage-calculation-result.ts new file mode 100644 index 00000000000..1220ff7b57d --- /dev/null +++ b/src/interfaces/damage-calculation-result.ts @@ -0,0 +1,11 @@ +import type { HitResult } from "#enums/hit-result"; + +/** Interface containing the results of a damage calculation for a given move */ +export interface DamageCalculationResult { + /** `true` if the move was cancelled (thus suppressing "No Effect" messages) */ + cancelled: boolean; + /** The effectiveness of the move */ + result: HitResult; + /** The damage dealt by the move */ + damage: number; +} diff --git a/src/interfaces/turn-move.ts b/src/interfaces/turn-move.ts new file mode 100644 index 00000000000..639d309256e --- /dev/null +++ b/src/interfaces/turn-move.ts @@ -0,0 +1,12 @@ +import type { BattlerIndex } from "#app/battle"; +import type { MoveResult } from "#app/field/pokemon"; +import type { Moves } from "#enums/moves"; + +export interface TurnMove { + move: Moves; + targets: BattlerIndex[]; + result?: MoveResult; + virtual?: boolean; + turn?: number; + ignorePP?: boolean; +} diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 8feb60c7778..852593d922c 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1,8 +1,10 @@ import { globalScene } from "#app/global-scene"; -import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; +import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; +import { EvolutionItem } from "#enums/evolution-item"; import { tmPoolTiers, tmSpecies } from "#app/data/balance/tms"; import { getBerryEffectDescription, getBerryName } from "#app/data/berry"; -import { allMoves, AttackMove } from "#app/data/moves/move"; +import { AttackMove } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { getNatureName, getNatureStatMultiplier } from "#app/data/nature"; import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; import { @@ -13,7 +15,8 @@ import { } from "#app/data/pokemon-forms"; import { getStatusEffectDescriptor } from "#app/data/status-effect"; import { PokemonType } from "#enums/pokemon-type"; -import type { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 80f14ba22ce..7860d0f9296 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1,7 +1,7 @@ import { FusionSpeciesFormEvolution, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry"; import { getLevelTotalExp } from "#app/data/exp"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; import { type FormChangeItem, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; import { getStatusEffectHealText } from "#app/data/status-effect"; diff --git a/src/overrides.ts b/src/overrides.ts index 21c72cd7b98..49efb5eed33 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -1,5 +1,5 @@ import { type PokeballCounts } from "#app/battle-scene"; -import { EvolutionItem } from "#app/data/balance/pokemon-evolutions"; +import { EvolutionItem } from "#enums/evolution-item"; import { Gender } from "#app/data/gender"; import { FormChangeItem } from "#app/data/pokemon-forms"; import { Variant } from "#app/sprites/variant"; diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index 8691ac453ca..c65f121d20e 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -11,8 +11,9 @@ import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Biome } from "#app/enums/biome"; import { Moves } from "#app/enums/moves"; import { PokeballType } from "#enums/pokeball"; -import type { PlayerPokemon, TurnMove } from "#app/field/pokemon"; -import { FieldPosition } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type { TurnMove } from "#app/interfaces/turn-move"; +import { FieldPosition } from "#enums/field-position"; import { getPokemonNameWithAffix } from "#app/messages"; import { Command } from "#app/ui/command-ui-handler"; import { Mode } from "#app/ui/ui"; diff --git a/src/phases/damage-anim-phase.ts b/src/phases/damage-anim-phase.ts index 696a2e55b6f..91b21376515 100644 --- a/src/phases/damage-anim-phase.ts +++ b/src/phases/damage-anim-phase.ts @@ -1,7 +1,8 @@ import { globalScene } from "#app/global-scene"; import type { BattlerIndex } from "#app/battle"; import { BattleSpec } from "#enums/battle-spec"; -import { type DamageResult, HitResult } from "#app/field/pokemon"; +import type { DamageResult } from "#app/@types/damage-result"; +import { HitResult } from "#enums/hit-result"; import { fixedInt } from "#app/utils"; import { PokemonPhase } from "#app/phases/pokemon-phase"; diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index 15f3d102e41..9e28de32c4a 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -11,7 +11,7 @@ import { TrainerSlot } from "#enums/trainer-slot"; import { getRandomWeatherType } from "#app/data/weather"; import { EncounterPhaseEvent } from "#app/events/battle-scene"; import type Pokemon from "#app/field/pokemon"; -import { FieldPosition } from "#app/field/pokemon"; +import { FieldPosition } from "#enums/field-position"; import { getPokemonNameWithAffix } from "#app/messages"; import { BoostBugSpawnModifier, IvScannerModifier, TurnHeldItemTransferModifier } from "#app/modifier/modifier"; import { ModifierPoolType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index 203c7542eff..076b7dec80d 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -10,7 +10,7 @@ import { Mode } from "#app/ui/ui"; import { cos, sin } from "#app/field/anims"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { LearnMoveSituation } from "#app/field/pokemon"; +import { LearnMoveContext } from "#enums/learn-move-context"; import { getTypeRgb } from "#app/data/type"; import i18next from "i18next"; import { getPokemonNameWithAffix } from "#app/messages"; @@ -343,11 +343,11 @@ export class EvolutionPhase extends Phase { this.evolutionHandler.canCancel = false; this.pokemon.evolve(this.evolution, this.pokemon.species).then(() => { - const learnSituation: LearnMoveSituation = this.fusionSpeciesEvolved - ? LearnMoveSituation.EVOLUTION_FUSED + const learnSituation: LearnMoveContext = this.fusionSpeciesEvolved + ? LearnMoveContext.EVOLUTION_FUSED : this.pokemon.fusionSpecies - ? LearnMoveSituation.EVOLUTION_FUSED_BASE - : LearnMoveSituation.EVOLUTION; + ? LearnMoveContext.EVOLUTION_FUSED_BASE + : LearnMoveContext.EVOLUTION; const levelMoves = this.pokemon .getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation) .filter(lm => lm[0] === EVOLVE_MOVE); diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 7e1ae4ec07b..4c418679047 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -12,13 +12,16 @@ import { import type { DestinyBondTag, GrudgeTag } from "#app/data/battler-tags"; import { BattlerTagLapseType } from "#app/data/battler-tags"; import { battleSpecDialogue } from "#app/data/dialogue"; -import { allMoves, PostVictoryStatStageChangeAttr } from "#app/data/moves/move"; +import { PostVictoryStatStageChangeAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { BattleSpec } from "#app/enums/battle-spec"; import { StatusEffect } from "#app/enums/status-effect"; import type { EnemyPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { HitResult, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import { PlayerPokemon } from "#app/field/pokemon"; +import { HitResult } from "#enums/hit-result"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { SwitchType } from "#enums/switch-type"; diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts index 4107a9cf087..a939298f620 100644 --- a/src/phases/learn-move-phase.ts +++ b/src/phases/learn-move-phase.ts @@ -1,7 +1,7 @@ import { globalScene } from "#app/global-scene"; import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; import type Move from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms"; import { Moves } from "#enums/moves"; import { getPokemonNameWithAffix } from "#app/messages"; diff --git a/src/phases/move-charge-phase.ts b/src/phases/move-charge-phase.ts index 26ad85bbe03..ccaf6d054b9 100644 --- a/src/phases/move-charge-phase.ts +++ b/src/phases/move-charge-phase.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import type { BattlerIndex } from "#app/battle"; import { MoveChargeAnim } from "#app/data/battle-anims"; import { applyMoveChargeAttrs, MoveEffectAttr, InstantChargeAttr } from "#app/data/moves/move"; -import type { PokemonMove } from "#app/field/pokemon"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; import { MoveResult } from "#app/field/pokemon"; import { BooleanHolder } from "#app/utils"; diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index acc7ac0f63a..c13c411be68 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -49,9 +49,10 @@ import { MoveTarget } from "#enums/MoveTarget"; import { MoveCategory } from "#enums/MoveCategory"; import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms"; import { PokemonType } from "#enums/pokemon-type"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; -import { HitResult, MoveResult } from "#app/field/pokemon"; +import { MoveResult } from "#app/field/pokemon"; +import { HitResult } from "#enums/hit-result"; import { getPokemonNameWithAffix } from "#app/messages"; import { ContactHeldItemTransferChanceModifier, diff --git a/src/phases/move-header-phase.ts b/src/phases/move-header-phase.ts index c320df462d1..c255b45190b 100644 --- a/src/phases/move-header-phase.ts +++ b/src/phases/move-header-phase.ts @@ -1,5 +1,5 @@ import { applyMoveAttrs, MoveHeaderAttr } from "#app/data/moves/move"; -import type { PokemonMove } from "#app/field/pokemon"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; import { BattlePhase } from "./battle-phase"; diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 478229dcae8..032ac6d06ab 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -16,7 +16,6 @@ import { CommonAnim } from "#app/data/battle-anims"; import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags"; import { AddArenaTrapTagAttr, - allMoves, applyMoveAttrs, BypassRedirectAttr, BypassSleepAttr, @@ -27,13 +26,14 @@ import { PreMoveMessageAttr, PreUseInterruptAttr, } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { MoveFlags } from "#enums/MoveFlags"; import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms"; import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect"; import { PokemonType } from "#enums/pokemon-type"; import { getTerrainBlockMessage, getWeatherBlockMessage } from "#app/data/weather"; import { MoveUsedEvent } from "#app/events/battle-scene"; -import type { PokemonMove } from "#app/field/pokemon"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; import { MoveResult } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; diff --git a/src/phases/pokemon-heal-phase.ts b/src/phases/pokemon-heal-phase.ts index 651c625b23a..84dc8a5e116 100644 --- a/src/phases/pokemon-heal-phase.ts +++ b/src/phases/pokemon-heal-phase.ts @@ -3,7 +3,7 @@ import type { BattlerIndex } from "#app/battle"; import { CommonAnim } from "#app/data/battle-anims"; import { getStatusEffectHealText } from "#app/data/status-effect"; import { StatusEffect } from "#app/enums/status-effect"; -import { HitResult } from "#app/field/pokemon"; +import { HitResult } from "#enums/hit-result"; import { getPokemonNameWithAffix } from "#app/messages"; import { HealingBoosterModifier } from "#app/modifier/modifier"; import { HealAchv } from "#app/system/achv"; diff --git a/src/phases/pokemon-transform-phase.ts b/src/phases/pokemon-transform-phase.ts index b33689321b5..fb9a28a5a26 100644 --- a/src/phases/pokemon-transform-phase.ts +++ b/src/phases/pokemon-transform-phase.ts @@ -2,7 +2,7 @@ import type { BattlerIndex } from "#app/battle"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; import { EFFECTIVE_STATS, BATTLE_STATS } from "#enums/stat"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { globalScene } from "#app/global-scene"; import { PokemonPhase } from "./pokemon-phase"; import { getPokemonNameWithAffix } from "#app/messages"; diff --git a/src/phases/select-target-phase.ts b/src/phases/select-target-phase.ts index 035eaff41fa..edd56ba60ed 100644 --- a/src/phases/select-target-phase.ts +++ b/src/phases/select-target-phase.ts @@ -5,7 +5,7 @@ import { Mode } from "#app/ui/ui"; import { CommandPhase } from "./command-phase"; import { PokemonPhase } from "./pokemon-phase"; import i18next from "#app/plugins/i18n"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; export class SelectTargetPhase extends PokemonPhase { // biome-ignore lint/complexity/noUselessConstructor: This makes `fieldIndex` required diff --git a/src/phases/summon-phase.ts b/src/phases/summon-phase.ts index 7379d509e55..e053b18e4d7 100644 --- a/src/phases/summon-phase.ts +++ b/src/phases/summon-phase.ts @@ -5,7 +5,7 @@ import { TrainerSlot } from "#enums/trainer-slot"; import { PlayerGender } from "#app/enums/player-gender"; import { addPokeballOpenParticles } from "#app/field/anims"; import type Pokemon from "#app/field/pokemon"; -import { FieldPosition } from "#app/field/pokemon"; +import { FieldPosition } from "#enums/field-position"; import { getPokemonNameWithAffix } from "#app/messages"; import i18next from "i18next"; import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase"; diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index d63cdb90f25..f39a3e62bb6 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -6,7 +6,8 @@ import { PreSummonAbAttr, PreSwitchOutAbAttr, } from "#app/data/ability"; -import { allMoves, ForceSwitchOutAttr } from "#app/data/moves/move"; +import { ForceSwitchOutAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { getPokeballTintColor } from "#app/data/pokeball"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { TrainerSlot } from "#enums/trainer-slot"; diff --git a/src/phases/toggle-double-position-phase.ts b/src/phases/toggle-double-position-phase.ts index 37f47d5cf95..c4766f888aa 100644 --- a/src/phases/toggle-double-position-phase.ts +++ b/src/phases/toggle-double-position-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { FieldPosition } from "#app/field/pokemon"; +import { FieldPosition } from "#enums/field-position"; import { BattlePhase } from "./battle-phase"; export class ToggleDoublePositionPhase extends BattlePhase { diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index d5b4160fe1b..5941e0af163 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -1,9 +1,10 @@ import { applyAbAttrs, BypassSpeedChanceAbAttr, PreventBypassSpeedChanceAbAttr } from "#app/data/ability"; -import { allMoves, MoveHeaderAttr } from "#app/data/moves/move"; +import { MoveHeaderAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#app/enums/abilities"; import { Stat } from "#app/enums/stat"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { BypassSpeedChanceModifier } from "#app/modifier/modifier"; import { Command } from "#app/ui/command-ui-handler"; import { randSeedShuffle, BooleanHolder } from "#app/utils"; diff --git a/src/phases/weather-effect-phase.ts b/src/phases/weather-effect-phase.ts index 5284c9fba85..256894457fc 100644 --- a/src/phases/weather-effect-phase.ts +++ b/src/phases/weather-effect-phase.ts @@ -14,7 +14,7 @@ import { getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weath import { BattlerTagType } from "#app/enums/battler-tag-type"; import { WeatherType } from "#app/enums/weather-type"; import type Pokemon from "#app/field/pokemon"; -import { HitResult } from "#app/field/pokemon"; +import { HitResult } from "#enums/hit-result"; import { BooleanHolder, toDmgValue } from "#app/utils"; import { CommonAnimPhase } from "./common-anim-phase"; diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 53146301666..e87c735f459 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -30,7 +30,7 @@ import { Nature } from "#enums/nature"; import { GameStats } from "#app/system/game-stats"; import { Tutorial } from "#app/tutorial"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { TrainerVariant } from "#app/field/trainer"; import type { Variant } from "#app/sprites/variant"; import { setSettingGamepad, SettingGamepad, settingGamepadDefaults } from "#app/system/settings/settings-gamepad"; diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 97ce494a43a..7579fc3b78d 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -5,7 +5,8 @@ import type { Nature } from "#enums/nature"; import type { PokeballType } from "#enums/pokeball"; import { getPokemonSpecies, getPokemonSpeciesForm } from "../data/pokemon-species"; import { Status } from "../data/status-effect"; -import Pokemon, { EnemyPokemon, PokemonMove, PokemonSummonData } from "../field/pokemon"; +import Pokemon, { EnemyPokemon, PokemonSummonData } from "../field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { TrainerSlot } from "#enums/trainer-slot"; import type { Variant } from "#app/sprites/variant"; import { loadBattlerTag } from "../data/battler-tags"; diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 27985629e3d..63c0703fa18 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"; import { MoveCategory } from "#enums/MoveCategory"; import i18next from "i18next"; import { Button } from "#enums/buttons"; -import type { PokemonMove } from "#app/field/pokemon"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; import type { CommandPhase } from "#app/phases/command-phase"; import MoveInfoOverlay from "./move-info-overlay"; diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 26351d4dbf1..f0ff351bb8a 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -9,7 +9,7 @@ import { LockModifierTiersModifier, PokemonHeldItemModifier, HealShopCostModifie import { handleTutorial, Tutorial } from "../tutorial"; import { Button } from "#enums/buttons"; import MoveInfoOverlay from "./move-info-overlay"; -import { allMoves } from "../data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { formatMoney, NumberHolder } from "#app/utils"; import Overrides from "#app/overrides"; import i18next from "i18next"; diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index ba90108c274..a42e0caadae 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -1,4 +1,5 @@ -import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; import { MoveResult } from "#app/field/pokemon"; import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "#app/ui/text"; @@ -11,7 +12,8 @@ import { PokemonHeldItemModifier, SwitchEffectTransferModifier, } from "#app/modifier/modifier"; -import { allMoves, ForceSwitchOutAttr } from "#app/data/moves/move"; +import { ForceSwitchOutAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender"; import { StatusEffect } from "#enums/status-effect"; import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler"; diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts index 407ebfcd843..1011fc89ae0 100644 --- a/src/ui/pokedex-page-ui-handler.ts +++ b/src/ui/pokedex-page-ui-handler.ts @@ -9,7 +9,7 @@ import { allAbilities } from "#app/data/ability"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { GrowthRate, getGrowthRateColor } from "#app/data/exp"; import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { getNatureName } from "#app/data/nature"; import type { SpeciesFormChange } from "#app/data/pokemon-forms"; import { pokemonFormChanges } from "#app/data/pokemon-forms"; diff --git a/src/ui/pokedex-scan-ui-handler.ts b/src/ui/pokedex-scan-ui-handler.ts index b34246b97d1..54c32fb34a1 100644 --- a/src/ui/pokedex-scan-ui-handler.ts +++ b/src/ui/pokedex-scan-ui-handler.ts @@ -7,7 +7,7 @@ import { isNullOrUndefined } from "#app/utils"; import { Mode } from "./ui"; import { FilterTextRow } from "./filter-text"; import { allAbilities } from "#app/data/ability"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { allSpecies } from "#app/data/pokemon-species"; import i18next from "i18next"; diff --git a/src/ui/pokedex-ui-handler.ts b/src/ui/pokedex-ui-handler.ts index 59b06d476a2..22ce5b833af 100644 --- a/src/ui/pokedex-ui-handler.ts +++ b/src/ui/pokedex-ui-handler.ts @@ -38,7 +38,7 @@ import type { OptionSelectConfig } from "./abstact-option-select-ui-handler"; import { FilterText, FilterTextRow } from "./filter-text"; import { allAbilities } from "#app/data/ability"; import { starterPassiveAbilities } from "#app/data/balance/passives"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { speciesTmMoves } from "#app/data/balance/tms"; import { pokemonPrevolutions, pokemonStarters } from "#app/data/balance/pokemon-evolutions"; import { Biome } from "#enums/biome"; diff --git a/src/ui/pokemon-hatch-info-container.ts b/src/ui/pokemon-hatch-info-container.ts index 692f0f1d374..77f9f5090a0 100644 --- a/src/ui/pokemon-hatch-info-container.ts +++ b/src/ui/pokemon-hatch-info-container.ts @@ -4,7 +4,7 @@ import { PokemonType } from "#enums/pokemon-type"; import { rgbHexToRgba, padInt } from "#app/utils"; import { TextStyle, addTextObject } from "#app/ui/text"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Species } from "#enums/species"; import { getEggTierForSpecies } from "#app/data/egg"; import { starterColors } from "#app/battle-scene"; diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 3e2940f45b9..680f752096b 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -13,7 +13,7 @@ import { allAbilities } from "#app/data/ability"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { GrowthRate, getGrowthRateColor } from "#app/data/exp"; import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { getNatureName } from "#app/data/nature"; import { pokemonFormChanges } from "#app/data/pokemon-forms"; import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 04bcf71d7ae..d82082f0872 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -12,7 +12,8 @@ import { toReadableString, formatStat, } from "#app/utils"; -import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { argbFromRgba } from "@material/material-color-utilities"; import { getTypeRgb } from "#app/data/type"; diff --git a/test/abilities/aura_break.test.ts b/test/abilities/aura_break.test.ts index 86b6c69ec8b..30841fdbe0c 100644 --- a/test/abilities/aura_break.test.ts +++ b/test/abilities/aura_break.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/test/abilities/battery.test.ts b/test/abilities/battery.test.ts index cc7570c3d31..78db19e67ff 100644 --- a/test/abilities/battery.test.ts +++ b/test/abilities/battery.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#app/enums/abilities"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; diff --git a/test/abilities/battle_bond.test.ts b/test/abilities/battle_bond.test.ts index 6305d7dedc5..e615b5746c0 100644 --- a/test/abilities/battle_bond.test.ts +++ b/test/abilities/battle_bond.test.ts @@ -1,4 +1,5 @@ -import { allMoves, MultiHitAttr } from "#app/data/moves/move"; +import { MultiHitAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { MultiHitType } from "#enums/MultiHitType"; import { Status } from "#app/data/status-effect"; import { Abilities } from "#enums/abilities"; diff --git a/test/abilities/flower_veil.test.ts b/test/abilities/flower_veil.test.ts index c26a952acff..d91c92e8c9f 100644 --- a/test/abilities/flower_veil.test.ts +++ b/test/abilities/flower_veil.test.ts @@ -7,7 +7,7 @@ import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { BattlerTagType } from "#enums/battler-tag-type"; import { allAbilities } from "#app/data/ability"; diff --git a/test/abilities/friend_guard.test.ts b/test/abilities/friend_guard.test.ts index 30175fe37e0..cee82ca2c69 100644 --- a/test/abilities/friend_guard.test.ts +++ b/test/abilities/friend_guard.test.ts @@ -6,7 +6,7 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { BattlerIndex } from "#app/battle"; import { allAbilities } from "#app/data/ability"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { MoveCategory } from "#enums/MoveCategory"; describe("Moves - Friend Guard", () => { diff --git a/test/abilities/galvanize.test.ts b/test/abilities/galvanize.test.ts index c1e02c6c8d8..4efb6bb068f 100644 --- a/test/abilities/galvanize.test.ts +++ b/test/abilities/galvanize.test.ts @@ -1,10 +1,10 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { PokemonType } from "#enums/pokemon-type"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; -import { HitResult } from "#app/field/pokemon"; +import { HitResult } from "#enums/hit-result"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/abilities/hustle.test.ts b/test/abilities/hustle.test.ts index 40197cf9e97..fbfa23e90d6 100644 --- a/test/abilities/hustle.test.ts +++ b/test/abilities/hustle.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#app/enums/abilities"; import { Stat } from "#app/enums/stat"; import { Moves } from "#enums/moves"; diff --git a/test/abilities/infiltrator.test.ts b/test/abilities/infiltrator.test.ts index 6278439651c..e9ecf366a37 100644 --- a/test/abilities/infiltrator.test.ts +++ b/test/abilities/infiltrator.test.ts @@ -1,5 +1,5 @@ import { ArenaTagSide } from "#app/data/arena-tag"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Stat } from "#enums/stat"; diff --git a/test/abilities/libero.test.ts b/test/abilities/libero.test.ts index 22abf1c248f..96a6b3c5d93 100644 --- a/test/abilities/libero.test.ts +++ b/test/abilities/libero.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { PokemonType } from "#enums/pokemon-type"; import { Weather } from "#app/data/weather"; import type { PlayerPokemon } from "#app/field/pokemon"; diff --git a/test/abilities/magic_bounce.test.ts b/test/abilities/magic_bounce.test.ts index f9a076776aa..c785827c910 100644 --- a/test/abilities/magic_bounce.test.ts +++ b/test/abilities/magic_bounce.test.ts @@ -1,7 +1,7 @@ import { BattlerIndex } from "#app/battle"; import { allAbilities } from "#app/data/ability"; import { ArenaTagSide } from "#app/data/arena-tag"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Stat } from "#app/enums/stat"; diff --git a/test/abilities/power_spot.test.ts b/test/abilities/power_spot.test.ts index e29b5ecf775..68ace696d4a 100644 --- a/test/abilities/power_spot.test.ts +++ b/test/abilities/power_spot.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#app/enums/abilities"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; diff --git a/test/abilities/protean.test.ts b/test/abilities/protean.test.ts index 574033bb13f..ca5e67139e1 100644 --- a/test/abilities/protean.test.ts +++ b/test/abilities/protean.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { PokemonType } from "#enums/pokemon-type"; import { Weather } from "#app/data/weather"; import type { PlayerPokemon } from "#app/field/pokemon"; diff --git a/test/abilities/sap_sipper.test.ts b/test/abilities/sap_sipper.test.ts index f4f02844cbc..b27f97099b9 100644 --- a/test/abilities/sap_sipper.test.ts +++ b/test/abilities/sap_sipper.test.ts @@ -9,7 +9,8 @@ import { Species } from "#enums/species"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { allMoves, RandomMoveAttr } from "#app/data/moves/move"; +import { RandomMoveAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; // See also: TypeImmunityAbAttr describe("Abilities - Sap Sipper", () => { diff --git a/test/abilities/serene_grace.test.ts b/test/abilities/serene_grace.test.ts index 65ca96acbbc..30073f30b24 100644 --- a/test/abilities/serene_grace.test.ts +++ b/test/abilities/serene_grace.test.ts @@ -4,7 +4,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { FlinchAttr } from "#app/data/moves/move"; diff --git a/test/abilities/sheer_force.test.ts b/test/abilities/sheer_force.test.ts index 4a1c20cde5c..74c7b30a846 100644 --- a/test/abilities/sheer_force.test.ts +++ b/test/abilities/sheer_force.test.ts @@ -7,7 +7,8 @@ import { Stat } from "#enums/stat"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { allMoves, FlinchAttr } from "#app/data/moves/move"; +import { FlinchAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; describe("Abilities - Sheer Force", () => { let phaserGame: Phaser.Game; diff --git a/test/abilities/steely_spirit.test.ts b/test/abilities/steely_spirit.test.ts index b180ff8919e..6e8331ea51a 100644 --- a/test/abilities/steely_spirit.test.ts +++ b/test/abilities/steely_spirit.test.ts @@ -1,5 +1,5 @@ import { allAbilities } from "#app/data/ability"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/test/abilities/supreme_overlord.test.ts b/test/abilities/supreme_overlord.test.ts index a71bf0a9354..69ff4f393b6 100644 --- a/test/abilities/supreme_overlord.test.ts +++ b/test/abilities/supreme_overlord.test.ts @@ -7,7 +7,7 @@ import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; describe("Abilities - Supreme Overlord", () => { let phaserGame: Phaser.Game; diff --git a/test/abilities/tera_shell.test.ts b/test/abilities/tera_shell.test.ts index a99ecfd4ce1..bd88c21f52d 100644 --- a/test/abilities/tera_shell.test.ts +++ b/test/abilities/tera_shell.test.ts @@ -2,7 +2,7 @@ import { BattlerIndex } from "#app/battle"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; -import { HitResult } from "#app/field/pokemon"; +import { HitResult } from "#enums/hit-result"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/abilities/unburden.test.ts b/test/abilities/unburden.test.ts index 8f18604011c..7012c4cf065 100644 --- a/test/abilities/unburden.test.ts +++ b/test/abilities/unburden.test.ts @@ -1,6 +1,7 @@ import { BattlerIndex } from "#app/battle"; import { PostItemLostAbAttr } from "#app/data/ability"; -import { allMoves, StealHeldItemChanceAttr } from "#app/data/moves/move"; +import { StealHeldItemChanceAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import type Pokemon from "#app/field/pokemon"; import type { ContactHeldItemTransferChanceModifier } from "#app/modifier/modifier"; import { Abilities } from "#enums/abilities"; diff --git a/test/abilities/wimp_out.test.ts b/test/abilities/wimp_out.test.ts index 294025a10e7..c81fa2071c5 100644 --- a/test/abilities/wimp_out.test.ts +++ b/test/abilities/wimp_out.test.ts @@ -1,6 +1,6 @@ import { BattlerIndex } from "#app/battle"; import { ArenaTagSide } from "#app/data/arena-tag"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import GameManager from "#test/testUtils/gameManager"; import { toDmgValue } from "#app/utils"; import { Abilities } from "#enums/abilities"; @@ -534,12 +534,12 @@ describe("Abilities - Wimp Out", () => { .enemyAbility(Abilities.WIMP_OUT) .startingLevel(50) .enemyLevel(1) - .enemyMoveset([ Moves.SPLASH, Moves.ENDURE ]) + .enemyMoveset([Moves.SPLASH, Moves.ENDURE]) .battleType("double") - .moveset([ Moves.DRAGON_ENERGY, Moves.SPLASH ]) + .moveset([Moves.DRAGON_ENERGY, Moves.SPLASH]) .startingWave(wave); - await game.classicMode.startBattle([ Species.REGIDRAGO, Species.MAGIKARP ]); + await game.classicMode.startBattle([Species.REGIDRAGO, Species.MAGIKARP]); // turn 1 game.move.select(Moves.DRAGON_ENERGY, 0); @@ -549,6 +549,5 @@ describe("Abilities - Wimp Out", () => { await game.phaseInterceptor.to("SelectModifierPhase"); expect(game.scene.currentBattle.waveIndex).toBe(wave + 1); - }); }); diff --git a/test/abilities/wonder_skin.test.ts b/test/abilities/wonder_skin.test.ts index 18d5be36aef..fe24cdad5ec 100644 --- a/test/abilities/wonder_skin.test.ts +++ b/test/abilities/wonder_skin.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; diff --git a/test/arena/arena_gravity.test.ts b/test/arena/arena_gravity.test.ts index a5ce84667f0..7e72d14460a 100644 --- a/test/arena/arena_gravity.test.ts +++ b/test/arena/arena_gravity.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; diff --git a/test/arena/grassy_terrain.test.ts b/test/arena/grassy_terrain.test.ts index d92fb24be5a..9ee9d2ef434 100644 --- a/test/arena/grassy_terrain.test.ts +++ b/test/arena/grassy_terrain.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/test/arena/weather_fog.test.ts b/test/arena/weather_fog.test.ts index 784c4886648..b240bfa7386 100644 --- a/test/arena/weather_fog.test.ts +++ b/test/arena/weather_fog.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#app/enums/abilities"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Moves } from "#enums/moves"; diff --git a/test/arena/weather_strong_winds.test.ts b/test/arena/weather_strong_winds.test.ts index 3a9235d9eb9..50d25947612 100644 --- a/test/arena/weather_strong_winds.test.ts +++ b/test/arena/weather_strong_winds.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { StatusEffect } from "#app/enums/status-effect"; import { TurnStartPhase } from "#app/phases/turn-start-phase"; import { Abilities } from "#enums/abilities"; diff --git a/test/battle/damage_calculation.test.ts b/test/battle/damage_calculation.test.ts index dab1fc81caa..11bb8246ca1 100644 --- a/test/battle/damage_calculation.test.ts +++ b/test/battle/damage_calculation.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import type { EnemyPersistentModifier } from "#app/modifier/modifier"; import { modifierTypes } from "#app/modifier/modifier-type"; import { Abilities } from "#enums/abilities"; diff --git a/test/battlerTags/substitute.test.ts b/test/battlerTags/substitute.test.ts index fca3dc5ef7e..f2ee741bca2 100644 --- a/test/battlerTags/substitute.test.ts +++ b/test/battlerTags/substitute.test.ts @@ -1,5 +1,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import type { PokemonTurnData, TurnMove, PokemonMove } from "#app/field/pokemon"; +import type { PokemonTurnData } from "#app/field/pokemon"; +import type { PokemonMove } from "#app/data/moves/pokemon-move"; +import type { TurnMove } from "#app/interfaces/turn-move"; import type Pokemon from "#app/field/pokemon"; import { MoveResult } from "#app/field/pokemon"; import type BattleScene from "#app/battle-scene"; @@ -7,7 +9,7 @@ import { BattlerTagLapseType, BindTag, SubstituteTag } from "#app/data/battler-t import { Moves } from "#app/enums/moves"; import { PokemonAnimType } from "#app/enums/pokemon-anim-type"; import * as messages from "#app/messages"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import type { MoveEffectPhase } from "#app/phases/move-effect-phase"; import GameManager from "#test/testUtils/gameManager"; diff --git a/test/enemy_command.test.ts b/test/enemy_command.test.ts index 6d5cc2698a3..cfa141cf89e 100644 --- a/test/enemy_command.test.ts +++ b/test/enemy_command.test.ts @@ -1,11 +1,11 @@ import type BattleScene from "#app/battle-scene"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { MoveCategory } from "#enums/MoveCategory"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; import type { EnemyPokemon } from "#app/field/pokemon"; -import { AiType } from "#app/field/pokemon"; +import { AiType } from "#enums/ai-type"; import { randSeedInt } from "#app/utils"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; diff --git a/test/evolution.test.ts b/test/evolution.test.ts index dd6795bf161..62a06f868e8 100644 --- a/test/evolution.test.ts +++ b/test/evolution.test.ts @@ -1,8 +1,5 @@ -import { - pokemonEvolutions, - SpeciesFormEvolution, - SpeciesWildEvolutionDelay, -} from "#app/data/balance/pokemon-evolutions"; +import { pokemonEvolutions, SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; +import { SpeciesWildEvolutionDelay } from "#enums/species-wild-evolution-delay"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; diff --git a/test/imports.test.ts b/test/imports.test.ts index 128308dbd14..ada7eff0109 100644 --- a/test/imports.test.ts +++ b/test/imports.test.ts @@ -4,7 +4,7 @@ import { describe, expect, it } from "vitest"; async function importModule() { try { initStatsKeys(); - const { PokemonMove } = await import("#app/field/pokemon"); + const { PokemonMove } = await import("#app/data/moves/pokemon-move"); const { Species } = await import("#enums/species"); return { PokemonMove, diff --git a/test/items/reviver_seed.test.ts b/test/items/reviver_seed.test.ts index c06f354a94a..e1e7e0d554e 100644 --- a/test/items/reviver_seed.test.ts +++ b/test/items/reviver_seed.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import type { PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { Abilities } from "#enums/abilities"; diff --git a/test/moves/astonish.test.ts b/test/moves/astonish.test.ts index 53922060ae6..69a312d4517 100644 --- a/test/moves/astonish.test.ts +++ b/test/moves/astonish.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { BerryPhase } from "#app/phases/berry-phase"; import { CommandPhase } from "#app/phases/command-phase"; diff --git a/test/moves/aurora_veil.test.ts b/test/moves/aurora_veil.test.ts index 31f6497bae5..06637d0764e 100644 --- a/test/moves/aurora_veil.test.ts +++ b/test/moves/aurora_veil.test.ts @@ -1,7 +1,8 @@ import type BattleScene from "#app/battle-scene"; import { ArenaTagSide } from "#app/data/arena-tag"; import type Move from "#app/data/moves/move"; -import { allMoves, CritOnlyAttr } from "#app/data/moves/move"; +import { CritOnlyAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import type Pokemon from "#app/field/pokemon"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; diff --git a/test/moves/burning_jealousy.test.ts b/test/moves/burning_jealousy.test.ts index 60387df4226..c618b46e842 100644 --- a/test/moves/burning_jealousy.test.ts +++ b/test/moves/burning_jealousy.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#app/enums/abilities"; import { StatusEffect } from "#app/enums/status-effect"; import { Moves } from "#enums/moves"; diff --git a/test/moves/ceaseless_edge.test.ts b/test/moves/ceaseless_edge.test.ts index d54f1bd9f21..227645df360 100644 --- a/test/moves/ceaseless_edge.test.ts +++ b/test/moves/ceaseless_edge.test.ts @@ -1,5 +1,5 @@ import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#app/enums/abilities"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; diff --git a/test/moves/copycat.test.ts b/test/moves/copycat.test.ts index 0d9b0951f89..615206275d4 100644 --- a/test/moves/copycat.test.ts +++ b/test/moves/copycat.test.ts @@ -1,5 +1,6 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves, RandomMoveAttr } from "#app/data/moves/move"; +import { RandomMoveAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Stat } from "#app/enums/stat"; import { MoveResult } from "#app/field/pokemon"; import { Abilities } from "#enums/abilities"; diff --git a/test/moves/destiny_bond.test.ts b/test/moves/destiny_bond.test.ts index c39d40427ad..9873d678b8c 100644 --- a/test/moves/destiny_bond.test.ts +++ b/test/moves/destiny_bond.test.ts @@ -1,6 +1,6 @@ import type { ArenaTrapTag } from "#app/data/arena-tag"; import { ArenaTagSide } from "#app/data/arena-tag"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; import { Moves } from "#enums/moves"; diff --git a/test/moves/diamond_storm.test.ts b/test/moves/diamond_storm.test.ts index 2363122f0d7..73a1aee3fd2 100644 --- a/test/moves/diamond_storm.test.ts +++ b/test/moves/diamond_storm.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/test/moves/dig.test.ts b/test/moves/dig.test.ts index 81339111656..14e7efee19b 100644 --- a/test/moves/dig.test.ts +++ b/test/moves/dig.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#enums/abilities"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; diff --git a/test/moves/dragon_tail.test.ts b/test/moves/dragon_tail.test.ts index 37e8aa2fe1b..a571312473d 100644 --- a/test/moves/dragon_tail.test.ts +++ b/test/moves/dragon_tail.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Status } from "#app/data/status-effect"; import { Challenges } from "#enums/challenges"; import { StatusEffect } from "#enums/status-effect"; diff --git a/test/moves/dynamax_cannon.test.ts b/test/moves/dynamax_cannon.test.ts index 9cf3106b9c1..b2590449e4e 100644 --- a/test/moves/dynamax_cannon.test.ts +++ b/test/moves/dynamax_cannon.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Moves } from "#enums/moves"; diff --git a/test/moves/effectiveness.test.ts b/test/moves/effectiveness.test.ts index fb03f1c10a0..efcbc9c3293 100644 --- a/test/moves/effectiveness.test.ts +++ b/test/moves/effectiveness.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { TrainerSlot } from "#enums/trainer-slot"; import { PokemonType } from "#enums/pokemon-type"; diff --git a/test/moves/fell_stinger.test.ts b/test/moves/fell_stinger.test.ts index 2ffa44c5a3a..766fedf68dc 100644 --- a/test/moves/fell_stinger.test.ts +++ b/test/moves/fell_stinger.test.ts @@ -7,7 +7,7 @@ import { Moves } from "#enums/moves"; import { Stat } from "#enums/stat"; import { StatusEffect } from "#app/enums/status-effect"; import { WeatherType } from "#app/enums/weather-type"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; describe("Moves - Fell Stinger", () => { let phaserGame: Phaser.Game; diff --git a/test/moves/fly.test.ts b/test/moves/fly.test.ts index 0bd7d22b2a7..37fa42b608d 100644 --- a/test/moves/fly.test.ts +++ b/test/moves/fly.test.ts @@ -8,7 +8,7 @@ import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; describe("Moves - Fly", () => { let phaserGame: Phaser.Game; diff --git a/test/moves/freezy_frost.test.ts b/test/moves/freezy_frost.test.ts index c1ac4054e70..d764600bc78 100644 --- a/test/moves/freezy_frost.test.ts +++ b/test/moves/freezy_frost.test.ts @@ -5,7 +5,7 @@ import { Species } from "#enums/species"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { CommandPhase } from "#app/phases/command-phase"; describe("Moves - Freezy Frost", () => { diff --git a/test/moves/fusion_flare_bolt.test.ts b/test/moves/fusion_flare_bolt.test.ts index c340aeea63f..32df10b4c7c 100644 --- a/test/moves/fusion_flare_bolt.test.ts +++ b/test/moves/fusion_flare_bolt.test.ts @@ -1,6 +1,6 @@ import { Stat } from "#enums/stat"; import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import type Move from "#app/data/moves/move"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; diff --git a/test/moves/glaive_rush.test.ts b/test/moves/glaive_rush.test.ts index d3531b172e2..28d6328c095 100644 --- a/test/moves/glaive_rush.test.ts +++ b/test/moves/glaive_rush.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; diff --git a/test/moves/hard_press.test.ts b/test/moves/hard_press.test.ts index 8891f0bf0e2..425993fb1a9 100644 --- a/test/moves/hard_press.test.ts +++ b/test/moves/hard_press.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; diff --git a/test/moves/hyper_beam.test.ts b/test/moves/hyper_beam.test.ts index 5cd54e9b46a..b1a244f2ea4 100644 --- a/test/moves/hyper_beam.test.ts +++ b/test/moves/hyper_beam.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#app/enums/abilities"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Moves } from "#app/enums/moves"; diff --git a/test/moves/lash_out.test.ts b/test/moves/lash_out.test.ts index 8395633f5c0..16632ec0065 100644 --- a/test/moves/lash_out.test.ts +++ b/test/moves/lash_out.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/test/moves/last_respects.test.ts b/test/moves/last_respects.test.ts index ccab8a43415..891b287dece 100644 --- a/test/moves/last_respects.test.ts +++ b/test/moves/last_respects.test.ts @@ -3,7 +3,7 @@ import { BattlerIndex } from "#app/battle"; import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import GameManager from "#test/testUtils/gameManager"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import type Move from "#app/data/moves/move"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import Phaser from "phaser"; diff --git a/test/moves/light_screen.test.ts b/test/moves/light_screen.test.ts index 9cc6944ed3e..b77bb1c790b 100644 --- a/test/moves/light_screen.test.ts +++ b/test/moves/light_screen.test.ts @@ -1,7 +1,8 @@ import type BattleScene from "#app/battle-scene"; import { ArenaTagSide } from "#app/data/arena-tag"; import type Move from "#app/data/moves/move"; -import { allMoves, CritOnlyAttr } from "#app/data/moves/move"; +import { CritOnlyAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#app/enums/abilities"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import type Pokemon from "#app/field/pokemon"; diff --git a/test/moves/magic_coat.test.ts b/test/moves/magic_coat.test.ts index 2cc8dea8938..e96125a23ac 100644 --- a/test/moves/magic_coat.test.ts +++ b/test/moves/magic_coat.test.ts @@ -1,6 +1,6 @@ import { BattlerIndex } from "#app/battle"; import { ArenaTagSide } from "#app/data/arena-tag"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Stat } from "#app/enums/stat"; diff --git a/test/moves/metronome.test.ts b/test/moves/metronome.test.ts index 80f32a3a6fb..bf045f5e9f9 100644 --- a/test/moves/metronome.test.ts +++ b/test/moves/metronome.test.ts @@ -1,5 +1,6 @@ import { RechargingTag, SemiInvulnerableTag } from "#app/data/battler-tags"; -import { allMoves, RandomMoveAttr } from "#app/data/moves/move"; +import { RandomMoveAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#app/enums/abilities"; import { Stat } from "#app/enums/stat"; import { CommandPhase } from "#app/phases/command-phase"; diff --git a/test/moves/moongeist_beam.test.ts b/test/moves/moongeist_beam.test.ts index 117fe513e17..94197683ea4 100644 --- a/test/moves/moongeist_beam.test.ts +++ b/test/moves/moongeist_beam.test.ts @@ -1,4 +1,5 @@ -import { allMoves, RandomMoveAttr } from "#app/data/moves/move"; +import { RandomMoveAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/test/moves/pledge_moves.test.ts b/test/moves/pledge_moves.test.ts index c866d15357c..d3b8e60ac62 100644 --- a/test/moves/pledge_moves.test.ts +++ b/test/moves/pledge_moves.test.ts @@ -1,7 +1,8 @@ import { BattlerIndex } from "#app/battle"; import { allAbilities } from "#app/data/ability"; import { ArenaTagSide } from "#app/data/arena-tag"; -import { allMoves, FlinchAttr } from "#app/data/moves/move"; +import { FlinchAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { PokemonType } from "#enums/pokemon-type"; import { ArenaTagType } from "#enums/arena-tag-type"; import { Stat } from "#enums/stat"; diff --git a/test/moves/powder.test.ts b/test/moves/powder.test.ts index 522b0b74ca7..510564e0f53 100644 --- a/test/moves/powder.test.ts +++ b/test/moves/powder.test.ts @@ -5,7 +5,8 @@ import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { BerryPhase } from "#app/phases/berry-phase"; -import { MoveResult, PokemonMove } from "#app/field/pokemon"; +import { MoveResult } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { PokemonType } from "#enums/pokemon-type"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { StatusEffect } from "#enums/status-effect"; diff --git a/test/moves/protect.test.ts b/test/moves/protect.test.ts index d50c490f7d3..65de079982f 100644 --- a/test/moves/protect.test.ts +++ b/test/moves/protect.test.ts @@ -5,7 +5,7 @@ import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Stat } from "#enums/stat"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; import { BattlerIndex } from "#app/battle"; import { MoveResult } from "#app/field/pokemon"; diff --git a/test/moves/rage_fist.test.ts b/test/moves/rage_fist.test.ts index f44901c5aba..73d83f4929c 100644 --- a/test/moves/rage_fist.test.ts +++ b/test/moves/rage_fist.test.ts @@ -2,7 +2,7 @@ import { BattlerIndex } from "#app/battle"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import type Move from "#app/data/moves/move"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; diff --git a/test/moves/reflect.test.ts b/test/moves/reflect.test.ts index ac879a7cc2b..272e5c2972c 100644 --- a/test/moves/reflect.test.ts +++ b/test/moves/reflect.test.ts @@ -1,7 +1,8 @@ import type BattleScene from "#app/battle-scene"; import { ArenaTagSide } from "#app/data/arena-tag"; import type Move from "#app/data/moves/move"; -import { allMoves, CritOnlyAttr } from "#app/data/moves/move"; +import { CritOnlyAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#app/enums/abilities"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import type Pokemon from "#app/field/pokemon"; diff --git a/test/moves/retaliate.test.ts b/test/moves/retaliate.test.ts index e916c9ffeaa..57d29b4fdfc 100644 --- a/test/moves/retaliate.test.ts +++ b/test/moves/retaliate.test.ts @@ -3,7 +3,7 @@ import Phaser from "phaser"; import GameManager from "#test/testUtils/gameManager"; import { Species } from "#enums/species"; import { Moves } from "#enums/moves"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import type Move from "#app/data/moves/move"; describe("Moves - Retaliate", () => { diff --git a/test/moves/rollout.test.ts b/test/moves/rollout.test.ts index 89270c2dfc7..456f029cda1 100644 --- a/test/moves/rollout.test.ts +++ b/test/moves/rollout.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { CommandPhase } from "#app/phases/command-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; diff --git a/test/moves/round.test.ts b/test/moves/round.test.ts index 82f080a25ea..ec9f3f69a5e 100644 --- a/test/moves/round.test.ts +++ b/test/moves/round.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import type { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; diff --git a/test/moves/scale_shot.test.ts b/test/moves/scale_shot.test.ts index 2be632adb54..ee759b8404a 100644 --- a/test/moves/scale_shot.test.ts +++ b/test/moves/scale_shot.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; diff --git a/test/moves/secret_power.test.ts b/test/moves/secret_power.test.ts index 37f1664251b..40802dcc51f 100644 --- a/test/moves/secret_power.test.ts +++ b/test/moves/secret_power.test.ts @@ -2,7 +2,7 @@ import { Abilities } from "#enums/abilities"; import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Stat } from "#enums/stat"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Species } from "#enums/species"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; diff --git a/test/moves/shell_side_arm.test.ts b/test/moves/shell_side_arm.test.ts index a5b065b76cb..232182ffef0 100644 --- a/test/moves/shell_side_arm.test.ts +++ b/test/moves/shell_side_arm.test.ts @@ -1,5 +1,6 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves, ShellSideArmCategoryAttr } from "#app/data/moves/move"; +import { ShellSideArmCategoryAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import type Move from "#app/data/moves/move"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; diff --git a/test/moves/shell_trap.test.ts b/test/moves/shell_trap.test.ts index 2df94cdb828..d3ba67843ac 100644 --- a/test/moves/shell_trap.test.ts +++ b/test/moves/shell_trap.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; import { MoveResult } from "#app/field/pokemon"; diff --git a/test/moves/sketch.test.ts b/test/moves/sketch.test.ts index dfbf2eca713..94f37757a6a 100644 --- a/test/moves/sketch.test.ts +++ b/test/moves/sketch.test.ts @@ -1,13 +1,15 @@ import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { MoveResult, PokemonMove } from "#app/field/pokemon"; +import { MoveResult } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { StatusEffect } from "#app/enums/status-effect"; import { BattlerIndex } from "#app/battle"; -import { allMoves, RandomMoveAttr } from "#app/data/moves/move"; +import { RandomMoveAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; describe("Moves - Sketch", () => { let phaserGame: Phaser.Game; diff --git a/test/moves/solar_beam.test.ts b/test/moves/solar_beam.test.ts index dffd4f210e5..b8a28065b64 100644 --- a/test/moves/solar_beam.test.ts +++ b/test/moves/solar_beam.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { BattlerTagType } from "#enums/battler-tag-type"; import { WeatherType } from "#enums/weather-type"; import { MoveResult } from "#app/field/pokemon"; diff --git a/test/moves/sparkly_swirl.test.ts b/test/moves/sparkly_swirl.test.ts index 6cd357c7e0e..1908772598a 100644 --- a/test/moves/sparkly_swirl.test.ts +++ b/test/moves/sparkly_swirl.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { StatusEffect } from "#app/enums/status-effect"; import { CommandPhase } from "#app/phases/command-phase"; import { Abilities } from "#enums/abilities"; diff --git a/test/moves/spectral_thief.test.ts b/test/moves/spectral_thief.test.ts index 2e52b118a74..271cb03073a 100644 --- a/test/moves/spectral_thief.test.ts +++ b/test/moves/spectral_thief.test.ts @@ -1,7 +1,7 @@ import { Abilities } from "#enums/abilities"; import { BattlerIndex } from "#app/battle"; import { Stat } from "#enums/stat"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; diff --git a/test/moves/spit_up.test.ts b/test/moves/spit_up.test.ts index d71647bda52..7ef6e5e5b14 100644 --- a/test/moves/spit_up.test.ts +++ b/test/moves/spit_up.test.ts @@ -1,8 +1,8 @@ import { Stat } from "#enums/stat"; import { StockpilingTag } from "#app/data/battler-tags"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { BattlerTagType } from "#app/enums/battler-tag-type"; -import type { TurnMove } from "#app/field/pokemon"; +import type { TurnMove } from "#app/interfaces/turn-move"; import { MoveResult } from "#app/field/pokemon"; import GameManager from "#test/testUtils/gameManager"; import { Abilities } from "#enums/abilities"; diff --git a/test/moves/steamroller.test.ts b/test/moves/steamroller.test.ts index ba96928e01d..a0e4c29cce5 100644 --- a/test/moves/steamroller.test.ts +++ b/test/moves/steamroller.test.ts @@ -1,7 +1,7 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { BattlerTagType } from "#app/enums/battler-tag-type"; -import type { DamageCalculationResult } from "#app/field/pokemon"; +import type { DamageCalculationResult } from "#app/interfaces/damage-calculation-result"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/test/moves/stockpile.test.ts b/test/moves/stockpile.test.ts index 033f24d5229..f6e6a0087f6 100644 --- a/test/moves/stockpile.test.ts +++ b/test/moves/stockpile.test.ts @@ -1,6 +1,6 @@ import { Stat } from "#enums/stat"; import { StockpilingTag } from "#app/data/battler-tags"; -import type { TurnMove } from "#app/field/pokemon"; +import type { TurnMove } from "#app/interfaces/turn-move"; import { MoveResult } from "#app/field/pokemon"; import { CommandPhase } from "#app/phases/command-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; diff --git a/test/moves/substitute.test.ts b/test/moves/substitute.test.ts index 23f7f4af4b9..68b90bf7cf8 100644 --- a/test/moves/substitute.test.ts +++ b/test/moves/substitute.test.ts @@ -1,7 +1,8 @@ import { BattlerIndex } from "#app/battle"; import { ArenaTagSide } from "#app/data/arena-tag"; import { SubstituteTag, TrappedTag } from "#app/data/battler-tags"; -import { allMoves, StealHeldItemChanceAttr } from "#app/data/moves/move"; +import { StealHeldItemChanceAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { MoveResult } from "#app/field/pokemon"; import type { CommandPhase } from "#app/phases/command-phase"; import GameManager from "#test/testUtils/gameManager"; diff --git a/test/moves/swallow.test.ts b/test/moves/swallow.test.ts index baa03801079..86af584a174 100644 --- a/test/moves/swallow.test.ts +++ b/test/moves/swallow.test.ts @@ -1,7 +1,7 @@ import { Stat } from "#enums/stat"; import { StockpilingTag } from "#app/data/battler-tags"; import { BattlerTagType } from "#app/enums/battler-tag-type"; -import type { TurnMove } from "#app/field/pokemon"; +import type { TurnMove } from "#app/interfaces/turn-move"; import { MoveResult } from "#app/field/pokemon"; import { MovePhase } from "#app/phases/move-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; diff --git a/test/moves/telekinesis.test.ts b/test/moves/telekinesis.test.ts index 1355cb975f3..7537ba0168a 100644 --- a/test/moves/telekinesis.test.ts +++ b/test/moves/telekinesis.test.ts @@ -1,5 +1,5 @@ import { BattlerTagType } from "#enums/battler-tag-type"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/test/moves/tera_blast.test.ts b/test/moves/tera_blast.test.ts index c1a2b999fa0..9d17ea6a3cc 100644 --- a/test/moves/tera_blast.test.ts +++ b/test/moves/tera_blast.test.ts @@ -1,10 +1,11 @@ import { BattlerIndex } from "#app/battle"; import { Stat } from "#enums/stat"; -import { allMoves, TeraMoveCategoryAttr } from "#app/data/moves/move"; +import { TeraMoveCategoryAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import type Move from "#app/data/moves/move"; import { PokemonType } from "#enums/pokemon-type"; import { Abilities } from "#app/enums/abilities"; -import { HitResult } from "#app/field/pokemon"; +import { HitResult } from "#enums/hit-result"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/testUtils/gameManager"; diff --git a/test/moves/toxic.test.ts b/test/moves/toxic.test.ts index f2b1f82fe02..ab536364f6a 100644 --- a/test/moves/toxic.test.ts +++ b/test/moves/toxic.test.ts @@ -5,7 +5,7 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { StatusEffect } from "#enums/status-effect"; import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; describe("Moves - Toxic", () => { let phaserGame: Phaser.Game; diff --git a/test/moves/triple_arrows.test.ts b/test/moves/triple_arrows.test.ts index eb434b25815..d1d14f7d3e6 100644 --- a/test/moves/triple_arrows.test.ts +++ b/test/moves/triple_arrows.test.ts @@ -1,4 +1,5 @@ -import { allMoves, FlinchAttr, StatStageChangeAttr } from "#app/data/moves/move"; +import { FlinchAttr, StatStageChangeAttr } from "#app/data/moves/move"; +import { allMoves } from "#app/data/moves/all-moves"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import type Move from "#app/data/moves/move"; diff --git a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts index 3c7bda8febd..728129007e7 100644 --- a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts +++ b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts @@ -8,7 +8,8 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; import type BattleScene from "#app/battle-scene"; -import { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import { PlayerPokemon } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { AnOfferYouCantRefuseEncounter } from "#app/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; diff --git a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts index 9befe77e688..c1e6a635f31 100644 --- a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts +++ b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts @@ -11,7 +11,7 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import { Moves } from "#enums/moves"; import type BattleScene from "#app/battle-scene"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { Mode } from "#app/ui/ui"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; diff --git a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index 4bbe76e5c72..7b3d87463bf 100644 --- a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -15,7 +15,7 @@ import { import { Moves } from "#enums/moves"; import type BattleScene from "#app/battle-scene"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { Mode } from "#app/ui/ui"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; diff --git a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts index 77cd65e51b9..5ea836d8aa6 100644 --- a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts +++ b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts @@ -17,7 +17,7 @@ import { Moves } from "#enums/moves"; import { DancingLessonsEncounter } from "#app/data/mystery-encounters/encounters/dancing-lessons-encounter"; import { Mode } from "#app/ui/ui"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; diff --git a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts index d233e72932a..82d80bc3970 100644 --- a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts +++ b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts @@ -11,7 +11,7 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import { Moves } from "#enums/moves"; import type BattleScene from "#app/battle-scene"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { Mode } from "#app/ui/ui"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; diff --git a/test/mystery-encounter/encounters/part-timer-encounter.test.ts b/test/mystery-encounter/encounters/part-timer-encounter.test.ts index 639a2e140ff..308aa9839e9 100644 --- a/test/mystery-encounter/encounters/part-timer-encounter.test.ts +++ b/test/mystery-encounter/encounters/part-timer-encounter.test.ts @@ -14,7 +14,7 @@ import { CIVILIZATION_ENCOUNTER_BIOMES } from "#app/data/mystery-encounters/myst import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { PartTimerEncounter } from "#app/data/mystery-encounters/encounters/part-timer-encounter"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { Moves } from "#enums/moves"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; diff --git a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index a9e6a339d36..0d0298901d0 100644 --- a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -17,7 +17,7 @@ import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters import { Nature } from "#enums/nature"; import { BerryType } from "#enums/berry-type"; import { BattlerTagType } from "#enums/battler-tag-type"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { Mode } from "#app/ui/ui"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { BerryModifier, PokemonBaseStatTotalModifier } from "#app/modifier/modifier"; diff --git a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index df7bbb9f424..44c8e7a8915 100644 --- a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -12,7 +12,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Biome } from "#app/enums/biome"; import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; import { Species } from "#app/enums/species"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { HealShopCostModifier, HitHealModifier, TurnHealModifier } from "#app/modifier/modifier"; import { ModifierTier } from "#app/modifier/modifier-tier"; import { modifierTypes, type PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; diff --git a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts index 452dfcf3784..e4928406a18 100644 --- a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts +++ b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts @@ -10,7 +10,7 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import { Moves } from "#enums/moves"; import type BattleScene from "#app/battle-scene"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; diff --git a/test/testUtils/helpers/moveHelper.ts b/test/testUtils/helpers/moveHelper.ts index 543f46b2026..333f95f2014 100644 --- a/test/testUtils/helpers/moveHelper.ts +++ b/test/testUtils/helpers/moveHelper.ts @@ -1,7 +1,7 @@ import type { BattlerIndex } from "#app/battle"; import { Button } from "#app/enums/buttons"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; import Overrides from "#app/overrides"; import type { CommandPhase } from "#app/phases/command-phase"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; From b41eee3c7f64be3b2c83ea30fb33210e87f73766 Mon Sep 17 00:00:00 2001 From: damocleas Date: Mon, 14 Apr 2025 12:28:36 -0400 Subject: [PATCH 30/33] Revert "[Refactor] Move many interfaces and enums to their own file" (#5661) Revert "[Refactor] Move many interfaces and enums to their own file (#5646)" This reverts commit c82e01eed377cacc5d3ffd2ef9caa27270e8a2dc. --- src/@types/damage-result.ts | 10 - src/battle-scene.ts | 2 +- src/battle.ts | 3 +- src/data/ability.ts | 9 +- src/data/arena-tag.ts | 5 +- src/data/balance/egg-moves.ts | 2 +- src/data/balance/pokemon-evolutions.ts | 60 ++++- src/data/battle-anims.ts | 3 +- src/data/battler-tags.ts | 5 +- src/data/berry.ts | 2 +- src/data/challenge.ts | 2 +- src/data/moves/all-moves.ts | 3 - src/data/moves/move.ts | 21 +- src/data/moves/pokemon-move.ts | 93 -------- .../encounters/absolute-avarice-encounter.ts | 3 +- .../encounters/bug-type-superfan-encounter.ts | 4 +- .../encounters/clowning-around-encounter.ts | 2 +- .../encounters/dancing-lessons-encounter.ts | 3 +- .../encounters/field-trip-encounter.ts | 3 +- .../encounters/fiery-fallout-encounter.ts | 2 +- .../encounters/fun-and-games-encounter.ts | 2 +- .../global-trade-system-encounter.ts | 3 +- .../encounters/lost-at-sea-encounter.ts | 2 +- .../slumbering-snorlax-encounter.ts | 3 +- .../encounters/the-strong-stuff-encounter.ts | 2 +- .../encounters/trash-to-treasure-encounter.ts | 2 +- .../encounters/uncommon-breed-encounter.ts | 2 +- .../encounters/weird-dream-encounter.ts | 2 +- .../mystery-encounter-requirements.ts | 3 +- .../mystery-encounters/mystery-encounter.ts | 3 +- .../can-learn-move-requirement.ts | 2 +- .../utils/encounter-phase-utils.ts | 7 +- src/data/pokemon-forms.ts | 2 +- src/data/pokemon-species.ts | 7 +- src/data/trainers/trainer-config.ts | 2 +- src/enums/ai-type.ts | 5 - src/enums/evolution-item.ts | 48 ---- src/enums/field-position.ts | 5 - src/enums/hit-result.ts | 15 -- src/enums/learn-move-context.ts | 8 - src/enums/species-wild-evolution-delay.ts | 8 - src/field/damage-number-handler.ts | 4 +- src/field/pokemon.ts | 206 ++++++++++++++++-- src/interfaces/attack-move-result.ts | 12 - src/interfaces/damage-calculation-result.ts | 11 - src/interfaces/turn-move.ts | 12 - src/modifier/modifier-type.ts | 9 +- src/modifier/modifier.ts | 2 +- src/overrides.ts | 2 +- src/phases/command-phase.ts | 5 +- src/phases/damage-anim-phase.ts | 3 +- src/phases/encounter-phase.ts | 2 +- src/phases/evolution-phase.ts | 10 +- src/phases/faint-phase.ts | 7 +- src/phases/learn-move-phase.ts | 2 +- src/phases/move-charge-phase.ts | 2 +- src/phases/move-effect-phase.ts | 5 +- src/phases/move-header-phase.ts | 2 +- src/phases/move-phase.ts | 4 +- src/phases/pokemon-heal-phase.ts | 2 +- src/phases/pokemon-transform-phase.ts | 2 +- src/phases/select-target-phase.ts | 2 +- src/phases/summon-phase.ts | 2 +- src/phases/switch-summon-phase.ts | 3 +- src/phases/toggle-double-position-phase.ts | 2 +- src/phases/turn-start-phase.ts | 5 +- src/phases/weather-effect-phase.ts | 2 +- src/system/game-data.ts | 2 +- src/system/pokemon-data.ts | 3 +- src/ui/fight-ui-handler.ts | 2 +- src/ui/modifier-select-ui-handler.ts | 2 +- src/ui/party-ui-handler.ts | 6 +- src/ui/pokedex-page-ui-handler.ts | 2 +- src/ui/pokedex-scan-ui-handler.ts | 2 +- src/ui/pokedex-ui-handler.ts | 2 +- src/ui/pokemon-hatch-info-container.ts | 2 +- src/ui/starter-select-ui-handler.ts | 2 +- src/ui/summary-ui-handler.ts | 3 +- test/abilities/aura_break.test.ts | 2 +- test/abilities/battery.test.ts | 2 +- test/abilities/battle_bond.test.ts | 3 +- test/abilities/flower_veil.test.ts | 2 +- test/abilities/friend_guard.test.ts | 2 +- test/abilities/galvanize.test.ts | 4 +- test/abilities/hustle.test.ts | 2 +- test/abilities/infiltrator.test.ts | 2 +- test/abilities/libero.test.ts | 2 +- test/abilities/magic_bounce.test.ts | 2 +- test/abilities/power_spot.test.ts | 2 +- test/abilities/protean.test.ts | 2 +- test/abilities/sap_sipper.test.ts | 3 +- test/abilities/serene_grace.test.ts | 2 +- test/abilities/sheer_force.test.ts | 3 +- test/abilities/steely_spirit.test.ts | 2 +- test/abilities/supreme_overlord.test.ts | 2 +- test/abilities/tera_shell.test.ts | 2 +- test/abilities/unburden.test.ts | 3 +- test/abilities/wimp_out.test.ts | 9 +- test/abilities/wonder_skin.test.ts | 2 +- test/arena/arena_gravity.test.ts | 2 +- test/arena/grassy_terrain.test.ts | 2 +- test/arena/weather_fog.test.ts | 2 +- test/arena/weather_strong_winds.test.ts | 2 +- test/battle/damage_calculation.test.ts | 2 +- test/battlerTags/substitute.test.ts | 6 +- test/enemy_command.test.ts | 4 +- test/evolution.test.ts | 7 +- test/imports.test.ts | 2 +- test/items/reviver_seed.test.ts | 2 +- test/moves/astonish.test.ts | 2 +- test/moves/aurora_veil.test.ts | 3 +- test/moves/burning_jealousy.test.ts | 2 +- test/moves/ceaseless_edge.test.ts | 2 +- test/moves/copycat.test.ts | 3 +- test/moves/destiny_bond.test.ts | 2 +- test/moves/diamond_storm.test.ts | 2 +- test/moves/dig.test.ts | 2 +- test/moves/dragon_tail.test.ts | 2 +- test/moves/dynamax_cannon.test.ts | 2 +- test/moves/effectiveness.test.ts | 2 +- test/moves/fell_stinger.test.ts | 2 +- test/moves/fly.test.ts | 2 +- test/moves/freezy_frost.test.ts | 2 +- test/moves/fusion_flare_bolt.test.ts | 2 +- test/moves/glaive_rush.test.ts | 2 +- test/moves/hard_press.test.ts | 2 +- test/moves/hyper_beam.test.ts | 2 +- test/moves/lash_out.test.ts | 2 +- test/moves/last_respects.test.ts | 2 +- test/moves/light_screen.test.ts | 3 +- test/moves/magic_coat.test.ts | 2 +- test/moves/metronome.test.ts | 3 +- test/moves/moongeist_beam.test.ts | 3 +- test/moves/pledge_moves.test.ts | 3 +- test/moves/powder.test.ts | 3 +- test/moves/protect.test.ts | 2 +- test/moves/rage_fist.test.ts | 2 +- test/moves/reflect.test.ts | 3 +- test/moves/retaliate.test.ts | 2 +- test/moves/rollout.test.ts | 2 +- test/moves/round.test.ts | 2 +- test/moves/scale_shot.test.ts | 2 +- test/moves/secret_power.test.ts | 2 +- test/moves/shell_side_arm.test.ts | 3 +- test/moves/shell_trap.test.ts | 2 +- test/moves/sketch.test.ts | 6 +- test/moves/solar_beam.test.ts | 2 +- test/moves/sparkly_swirl.test.ts | 2 +- test/moves/spectral_thief.test.ts | 2 +- test/moves/spit_up.test.ts | 4 +- test/moves/steamroller.test.ts | 4 +- test/moves/stockpile.test.ts | 2 +- test/moves/substitute.test.ts | 3 +- test/moves/swallow.test.ts | 2 +- test/moves/telekinesis.test.ts | 2 +- test/moves/tera_blast.test.ts | 5 +- test/moves/toxic.test.ts | 2 +- test/moves/triple_arrows.test.ts | 3 +- ...an-offer-you-cant-refuse-encounter.test.ts | 3 +- .../bug-type-superfan-encounter.test.ts | 2 +- .../clowning-around-encounter.test.ts | 2 +- .../dancing-lessons-encounter.test.ts | 2 +- .../fight-or-flight-encounter.test.ts | 2 +- .../encounters/part-timer-encounter.test.ts | 2 +- .../the-strong-stuff-encounter.test.ts | 2 +- .../trash-to-treasure-encounter.test.ts | 2 +- .../uncommon-breed-encounter.test.ts | 2 +- test/testUtils/helpers/moveHelper.ts | 2 +- 168 files changed, 455 insertions(+), 490 deletions(-) delete mode 100644 src/@types/damage-result.ts delete mode 100644 src/data/moves/all-moves.ts delete mode 100644 src/data/moves/pokemon-move.ts delete mode 100644 src/enums/ai-type.ts delete mode 100644 src/enums/evolution-item.ts delete mode 100644 src/enums/field-position.ts delete mode 100644 src/enums/hit-result.ts delete mode 100644 src/enums/learn-move-context.ts delete mode 100644 src/enums/species-wild-evolution-delay.ts delete mode 100644 src/interfaces/attack-move-result.ts delete mode 100644 src/interfaces/damage-calculation-result.ts delete mode 100644 src/interfaces/turn-move.ts diff --git a/src/@types/damage-result.ts b/src/@types/damage-result.ts deleted file mode 100644 index 7086d843cf4..00000000000 --- a/src/@types/damage-result.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { HitResult } from "#enums/hit-result"; - -export type DamageResult = - | HitResult.EFFECTIVE - | HitResult.SUPER_EFFECTIVE - | HitResult.NOT_VERY_EFFECTIVE - | HitResult.ONE_HIT_KO - | HitResult.CONFUSION - | HitResult.INDIRECT_KO - | HitResult.INDIRECT; diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 12dbfca68e8..dd983f2b397 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -51,7 +51,7 @@ import { initGameSpeed } from "#app/system/game-speed"; import { Arena, ArenaBase } from "#app/field/arena"; import { GameData } from "#app/system/game-data"; import { addTextObject, getTextColor, TextStyle } from "#app/ui/text"; -import { allMoves } from "./data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { MusicPreference } from "#app/system/settings/settings"; import { getDefaultModifierTypeForTier, diff --git a/src/battle.ts b/src/battle.ts index 1122db2679a..fb5af223b8f 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -15,8 +15,7 @@ import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/mod import type { PokeballType } from "#enums/pokeball"; import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { SpeciesFormKey } from "#enums/species-form-key"; -import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; -import type { TurnMove } from "./interfaces/turn-move"; +import type { EnemyPokemon, PlayerPokemon, TurnMove } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattleSpec } from "#enums/battle-spec"; diff --git a/src/data/ability.ts b/src/data/ability.ts index 02cc12dd0f4..3e32a624f9f 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -1,8 +1,6 @@ -import type { EnemyPokemon } from "../field/pokemon"; -import type { PokemonMove } from "./moves/pokemon-move"; +import type { EnemyPokemon, PokemonMove } from "../field/pokemon"; import type Pokemon from "../field/pokemon"; -import { MoveResult, PlayerPokemon } from "../field/pokemon"; -import { HitResult } from "#enums/hit-result"; +import { HitResult, MoveResult, PlayerPokemon } from "../field/pokemon"; import { PokemonType } from "#enums/pokemon-type"; import { BooleanHolder, NumberHolder, toDmgValue, isNullOrUndefined, randSeedItem, randSeedInt, type Constructor } from "#app/utils"; import { getPokemonNameWithAffix } from "../messages"; @@ -12,8 +10,7 @@ import { BattlerTagLapseType, GroundedTag } from "./battler-tags"; import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "#app/data/status-effect"; import { Gender } from "./gender"; import type Move from "./moves/move"; -import { AttackMove, FlinchAttr, OneHitKOAttr, HitHealAttr, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./moves/move"; -import { allMoves } from "./moves/all-moves"; +import { AttackMove, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./moves/move"; import { MoveFlags } from "#enums/MoveFlags"; import { MoveTarget } from "#enums/MoveTarget"; import { MoveCategory } from "#enums/MoveCategory"; diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index c6a1515685f..871f622f70a 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -2,13 +2,12 @@ import { globalScene } from "#app/global-scene"; import type { Arena } from "#app/field/arena"; import { PokemonType } from "#enums/pokemon-type"; import { BooleanHolder, NumberHolder, toDmgValue } from "#app/utils"; -import { allMoves } from "./moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { MoveTarget } from "#enums/MoveTarget"; import { MoveCategory } from "#enums/MoveCategory"; import { getPokemonNameWithAffix } from "#app/messages"; import type Pokemon from "#app/field/pokemon"; -import { HitResult } from "#enums/hit-result"; -import { PokemonMove } from "./moves/pokemon-move"; +import { HitResult, PokemonMove } from "#app/field/pokemon"; import { StatusEffect } from "#enums/status-effect"; import type { BattlerIndex } from "#app/battle"; import { diff --git a/src/data/balance/egg-moves.ts b/src/data/balance/egg-moves.ts index 98f3347764c..74f6a2c1afb 100644 --- a/src/data/balance/egg-moves.ts +++ b/src/data/balance/egg-moves.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { getEnumKeys, getEnumValues } from "#app/utils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index 70b616be8e5..17f71f3c3c9 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -14,10 +14,66 @@ import { DamageMoneyRewardModifier, ExtraModifierModifier, MoneyMultiplierModifi import { SpeciesFormKey } from "#enums/species-form-key"; import { speciesStarterCosts } from "./starters"; import i18next from "i18next"; -import { SpeciesWildEvolutionDelay } from "#enums/species-wild-evolution-delay"; -import { EvolutionItem } from "#enums/evolution-item"; +export enum SpeciesWildEvolutionDelay { + NONE, + SHORT, + MEDIUM, + LONG, + VERY_LONG, + NEVER +} + +export enum EvolutionItem { + NONE, + + LINKING_CORD, + SUN_STONE, + MOON_STONE, + LEAF_STONE, + FIRE_STONE, + WATER_STONE, + THUNDER_STONE, + ICE_STONE, + DUSK_STONE, + DAWN_STONE, + SHINY_STONE, + CRACKED_POT, + SWEET_APPLE, + TART_APPLE, + STRAWBERRY_SWEET, + UNREMARKABLE_TEACUP, + UPGRADE, + DUBIOUS_DISC, + DRAGON_SCALE, + PRISM_SCALE, + RAZOR_CLAW, + RAZOR_FANG, + REAPER_CLOTH, + ELECTIRIZER, + MAGMARIZER, + PROTECTOR, + SACHET, + WHIPPED_DREAM, + SYRUPY_APPLE, + CHIPPED_POT, + GALARICA_CUFF, + GALARICA_WREATH, + AUSPICIOUS_ARMOR, + MALICIOUS_ARMOR, + MASTERPIECE_TEACUP, + SUN_FLUTE, + MOON_FLUTE, + + BLACK_AUGURITE = 51, + PEAT_BLOCK, + METAL_ALLOY, + SCROLL_OF_DARKNESS, + SCROLL_OF_WATERS, + LEADERS_CREST +} + /** * Pokemon Evolution tuple type consisting of: * @property 0 {@linkcode Species} The species of the Pokemon. diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index 396cf71d984..511c80bee72 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -1,6 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, SelfStatusMove } from "./moves/move"; -import { allMoves } from "./moves/all-moves"; +import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, SelfStatusMove, allMoves } from "./moves/move"; import { MoveFlags } from "#enums/MoveFlags"; import type Pokemon from "../field/pokemon"; import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils"; diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index c3dcfc49ef6..76e91485460 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -11,12 +11,12 @@ import { import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims"; import type Move from "#app/data/moves/move"; import { + allMoves, applyMoveAttrs, ConsecutiveUseDoublePowerAttr, HealOnAllyAttr, StatusCategoryOnAllyAttr, } from "#app/data/moves/move"; -import { allMoves } from "./moves/all-moves"; import { MoveFlags } from "#enums/MoveFlags"; import { MoveCategory } from "#enums/MoveCategory"; import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms"; @@ -24,8 +24,7 @@ import { getStatusEffectHealText } from "#app/data/status-effect"; import { TerrainType } from "#app/data/terrain"; import { PokemonType } from "#enums/pokemon-type"; import type Pokemon from "#app/field/pokemon"; -import { MoveResult } from "#app/field/pokemon"; -import { HitResult } from "#enums/hit-result"; +import { HitResult, MoveResult } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; diff --git a/src/data/berry.ts b/src/data/berry.ts index aaa0dda6e7f..8a58d337aa4 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -1,6 +1,6 @@ import { getPokemonNameWithAffix } from "../messages"; import type Pokemon from "../field/pokemon"; -import { HitResult } from "#enums/hit-result"; +import { HitResult } from "../field/pokemon"; import { getStatusEffectHealText } from "./status-effect"; import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils"; import { diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 9a3c329a70b..51616c3f00f 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -6,7 +6,7 @@ import type PokemonSpecies from "#app/data/pokemon-species"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "./moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import type { FixedBattleConfig } from "#app/battle"; import { ClassicFixedBossWaves, BattleType, getRandomTrainerFunc } from "#app/battle"; import Trainer, { TrainerVariant } from "#app/field/trainer"; diff --git a/src/data/moves/all-moves.ts b/src/data/moves/all-moves.ts deleted file mode 100644 index c7b6d11a08d..00000000000 --- a/src/data/moves/all-moves.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type Move from "./move"; - -export const allMoves: Move[] = []; diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 591894f5f1e..962a13bb840 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -12,17 +12,16 @@ import { TypeBoostTag, } from "../battler-tags"; import { getPokemonNameWithAffix } from "../../messages"; -import type { AttackMoveResult } from "#app/interfaces/attack-move-result"; -import type { TurnMove } from "#app/interfaces/turn-move"; +import type { AttackMoveResult, TurnMove } from "../../field/pokemon"; import type Pokemon from "../../field/pokemon"; import { EnemyPokemon, + FieldPosition, + HitResult, MoveResult, PlayerPokemon, + PokemonMove, } from "../../field/pokemon"; -import { HitResult } from "#enums/hit-result"; -import { FieldPosition } from "#enums/field-position"; -import { PokemonMove } from "./pokemon-move"; import { getNonVolatileStatusEffects, getStatusEffectHealText, @@ -122,7 +121,6 @@ import { MoveFlags } from "#enums/MoveFlags"; import { MoveEffectTrigger } from "#enums/MoveEffectTrigger"; import { MultiHitType } from "#enums/MultiHitType"; import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves } from "./invalid-moves"; -import { allMoves } from "./all-moves"; type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean; @@ -8259,7 +8257,11 @@ export function getMoveTargets(user: Pokemon, move: Moves, replaceTarget?: MoveT return { targets: set.filter(p => p?.isActive(true)).map(p => p.getBattlerIndex()).filter(t => t !== undefined), multiple }; } -allMoves.push(new SelfStatusMove(Moves.NONE, PokemonType.NORMAL, MoveCategory.STATUS, -1, -1, 0, 1)); +export const allMoves: Move[] = [ + new SelfStatusMove(Moves.NONE, PokemonType.NORMAL, MoveCategory.STATUS, -1, -1, 0, 1), +]; + +export const selfStatLowerMoves: Moves[] = []; export function initMoves() { allMoves.push( @@ -11248,4 +11250,9 @@ export function initMoves() { new AttackMove(Moves.MALIGNANT_CHAIN, PokemonType.POISON, MoveCategory.SPECIAL, 100, 100, 5, 50, 0, 9) .attr(StatusEffectAttr, StatusEffect.TOXIC) ); + allMoves.map(m => { + if (m.getAttrs(StatStageChangeAttr).some(a => a.selfTarget && a.stages < 0)) { + selfStatLowerMoves.push(m.id); + } + }); } diff --git a/src/data/moves/pokemon-move.ts b/src/data/moves/pokemon-move.ts deleted file mode 100644 index 49ccaba698b..00000000000 --- a/src/data/moves/pokemon-move.ts +++ /dev/null @@ -1,93 +0,0 @@ -import * as Utils from "#app/utils"; -import { allMoves } from "./all-moves"; -import type { Moves } from "#enums/moves"; -import type Pokemon from "#app/field/pokemon"; -import type Move from "./move"; - -/** - * Wrapper class for the {@linkcode Move} class for Pokemon to interact with. - * These are the moves assigned to a {@linkcode Pokemon} object. - * It links to {@linkcode Move} class via the move ID. - * Compared to {@linkcode Move}, this class also tracks if a move has received. - * PP Ups, amount of PP used, and things like that. - * @see {@linkcode isUsable} - checks if move is restricted, out of PP, or not implemented. - * @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID. - * @see {@linkcode usePp} - removes a point of PP from the move. - * @see {@linkcode getMovePp} - returns amount of PP a move currently has. - * @see {@linkcode getPpRatio} - returns the current PP amount / max PP amount. - * @see {@linkcode getName} - returns name of {@linkcode Move}. - **/ -export class PokemonMove { - public moveId: Moves; - public ppUsed: number; - public ppUp: number; - public virtual: boolean; - - /** - * If defined and nonzero, overrides the maximum PP of the move (e.g., due to move being copied by Transform). - * This also nullifies all effects of `ppUp`. - */ - public maxPpOverride?: number; - - constructor(moveId: Moves, ppUsed = 0, ppUp = 0, virtual = false, maxPpOverride?: number) { - this.moveId = moveId; - this.ppUsed = ppUsed; - this.ppUp = ppUp; - this.virtual = virtual; - this.maxPpOverride = maxPpOverride; - } - - /** - * Checks whether the move can be selected or performed by a Pokemon, without consideration for the move's targets. - * The move is unusable if it is out of PP, restricted by an effect, or unimplemented. - * - * @param pokemon - {@linkcode Pokemon} that would be using this move - * @param ignorePp - If `true`, skips the PP check - * @param ignoreRestrictionTags - If `true`, skips the check for move restriction tags (see {@link MoveRestrictionBattlerTag}) - * @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`. - */ - isUsable(pokemon: Pokemon, ignorePp = false, ignoreRestrictionTags = false): boolean { - if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon)) { - return false; - } - - if (this.getMove().name.endsWith(" (N)")) { - return false; - } - - return ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1; - } - - getMove(): Move { - return allMoves[this.moveId]; - } - - /** - * Sets {@link ppUsed} for this move and ensures the value does not exceed {@link getMovePp} - * @param {number} count Amount of PP to use - */ - usePp(count = 1) { - this.ppUsed = Math.min(this.ppUsed + count, this.getMovePp()); - } - - getMovePp(): number { - return this.maxPpOverride || this.getMove().pp + this.ppUp * Utils.toDmgValue(this.getMove().pp / 5); - } - - getPpRatio(): number { - return 1 - this.ppUsed / this.getMovePp(); - } - - getName(): string { - return this.getMove().name; - } - - /** - * Copies an existing move or creates a valid PokemonMove object from json representing one - * @param source - The data for the move to copy - * @return A valid pokemonmove object - */ - static loadMove(source: PokemonMove | any): PokemonMove { - return new PokemonMove(source.moveId, source.ppUsed, source.ppUp, source.virtual, source.maxPpOverride); - } -} diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index b781f14fad1..85f40a41e51 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -7,8 +7,7 @@ import { transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type Pokemon from "#app/field/pokemon"; -import { EnemyPokemon } from "#app/field/pokemon"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index c6971c42364..1e4c9a3b957 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -24,7 +24,7 @@ import { TrainerType } from "#enums/trainer-type"; import { Species } from "#enums/species"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import { getEncounterText, showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { Moves } from "#enums/moves"; @@ -50,7 +50,7 @@ import { } from "#app/modifier/modifier"; import i18next from "i18next"; import MoveInfoOverlay from "#app/ui/move-info-overlay"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { ModifierTier } from "#app/modifier/modifier-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index 15fad6dacbf..eca99fc0c13 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -37,7 +37,7 @@ import { Mode } from "#app/ui/ui"; import i18next from "i18next"; import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler"; import type { PlayerPokemon } from "#app/field/pokemon"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import { Ability } from "#app/data/ability"; import { BerryModifier } from "#app/modifier/modifier"; import { BerryType } from "#enums/berry-type"; diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index 90ea6a69c0d..75527e1f8c1 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -23,8 +23,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { TrainerSlot } from "#enums/trainer-slot"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { EnemyPokemon } from "#app/field/pokemon"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { modifierTypes } from "#app/modifier/modifier-type"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; diff --git a/src/data/mystery-encounters/encounters/field-trip-encounter.ts b/src/data/mystery-encounters/encounters/field-trip-encounter.ts index 4e330fab3d9..a1964aa5ab4 100644 --- a/src/data/mystery-encounters/encounters/field-trip-encounter.ts +++ b/src/data/mystery-encounters/encounters/field-trip-encounter.ts @@ -7,8 +7,7 @@ import { setEncounterExp, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import type { PlayerPokemon } from "#app/field/pokemon"; -import type { PokemonMove } from "#app/data/moves/pokemon-move"; +import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { modifierTypes } from "#app/modifier/modifier-type"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index d868184a7fa..6118fe3d0de 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -26,7 +26,7 @@ import { Gender } from "#app/data/gender"; import { PokemonType } from "#enums/pokemon-type"; import { BattlerIndex } from "#app/battle"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import { Moves } from "#enums/moves"; import { EncounterBattleAnim } from "#app/data/battle-anims"; import { WeatherType } from "#enums/weather-type"; diff --git a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts index a9fc24c70b7..282c6c149ff 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -13,7 +13,7 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst import { TrainerSlot } from "#enums/trainer-slot"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { FieldPosition } from "#enums/field-position"; +import { FieldPosition } from "#app/field/pokemon"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index fce496e5e17..f80620647b0 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -26,8 +26,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { NumberHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle, randSeedItem } from "#app/utils"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { EnemyPokemon } from "#app/field/pokemon"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { HiddenAbilityRateBoosterModifier, diff --git a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts index 030678b77b1..97fd5783ebb 100644 --- a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts +++ b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts @@ -11,7 +11,7 @@ import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encount import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; const OPTION_1_REQUIRED_MOVE = Moves.SURF; const OPTION_2_REQUIRED_MOVE = Moves.FLY; diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index 97a17af43d0..bfa1204a8ba 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -21,8 +21,7 @@ import { import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { Moves } from "#enums/moves"; import { BattlerIndex } from "#app/battle"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; -import { AiType } from "#enums/ai-type"; +import { AiType, PokemonMove } from "#app/field/pokemon"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index af0363f37e3..c994c6e993f 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -17,7 +17,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Species } from "#enums/species"; import { Nature } from "#enums/nature"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { Moves } from "#enums/moves"; diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index 2203ac041f8..e60fe0ddc18 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -25,7 +25,7 @@ import { ModifierTier } from "#app/modifier/modifier-tier"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Moves } from "#enums/moves"; import { BattlerIndex } from "#app/battle"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { randSeedInt } from "#app/utils"; diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index 4e3c238aeba..ed1866c7a1b 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -10,7 +10,7 @@ import { import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import type Pokemon from "#app/field/pokemon"; import type { EnemyPokemon } from "#app/field/pokemon"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index be0c0bdff54..22ec52e976c 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -16,7 +16,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import { NumberHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils"; import type PokemonSpecies from "#app/data/pokemon-species"; import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index 0c146fe485d..f9aedf2c1a7 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -1,7 +1,6 @@ import { globalScene } from "#app/global-scene"; import { allAbilities } from "#app/data/ability"; -import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; -import { EvolutionItem } from "#enums/evolution-item"; +import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { Nature } from "#enums/nature"; import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; import { StatusEffect } from "#enums/status-effect"; diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index 8010983f9f3..ff098d4d7dd 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -1,6 +1,5 @@ import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import type { PlayerPokemon } from "#app/field/pokemon"; -import type { PokemonMove } from "../moves/pokemon-move"; +import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; diff --git a/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts b/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts index 598b0ffae70..a7ffe3e26ca 100644 --- a/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts +++ b/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts @@ -1,6 +1,6 @@ import type { Moves } from "#app/enums/moves"; import type { PlayerPokemon } from "#app/field/pokemon"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import { isNullOrUndefined } from "#app/utils"; import { EncounterPokemonRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { globalScene } from "#app/global-scene"; diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 6ab650d5f9b..a9f6b787878 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -7,12 +7,9 @@ import { WEIGHT_INCREMENT_ON_SPAWN_MISS, } from "#app/data/mystery-encounters/mystery-encounters"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import type { PlayerPokemon } from "#app/field/pokemon"; -import type { AiType } from "#enums/ai-type"; +import type { AiType, PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { EnemyPokemon, PokemonSummonData } from "#app/field/pokemon"; -import { FieldPosition } from "#enums/field-position"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { EnemyPokemon, FieldPosition, PokemonMove, PokemonSummonData } from "#app/field/pokemon"; import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type"; import { getPartyLuckValue, diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index 6f36bfde74f..63e166c7fc4 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -1,7 +1,7 @@ import { PokemonFormChangeItemModifier } from "../modifier/modifier"; import type Pokemon from "../field/pokemon"; import { StatusEffect } from "#enums/status-effect"; -import { allMoves } from "./moves/all-moves"; +import { allMoves } from "./moves/move"; import { MoveCategory } from "#enums/MoveCategory"; import type { Constructor, nil } from "#app/utils"; import { Abilities } from "#enums/abilities"; diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index ced828fbc6b..a27c00121dc 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -13,8 +13,11 @@ import { uncatchableSpecies } from "#app/data/balance/biomes"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { GrowthRate } from "#app/data/exp"; import type { EvolutionLevel } from "#app/data/balance/pokemon-evolutions"; -import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; -import { SpeciesWildEvolutionDelay } from "#enums/species-wild-evolution-delay"; +import { + SpeciesWildEvolutionDelay, + pokemonEvolutions, + pokemonPrevolutions, +} from "#app/data/balance/pokemon-evolutions"; import { PokemonType } from "#enums/pokemon-type"; import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; import { diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index c87f72bd912..0ab7119dab9 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; import { modifierTypes } from "#app/modifier/modifier-type"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import { toReadableString, isNullOrUndefined, randSeedItem, randSeedInt } from "#app/utils"; import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { getPokemonSpecies } from "#app/data/pokemon-species"; diff --git a/src/enums/ai-type.ts b/src/enums/ai-type.ts deleted file mode 100644 index 13931172a4a..00000000000 --- a/src/enums/ai-type.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum AiType { - RANDOM, - SMART_RANDOM, - SMART -} diff --git a/src/enums/evolution-item.ts b/src/enums/evolution-item.ts deleted file mode 100644 index 3b5e493b378..00000000000 --- a/src/enums/evolution-item.ts +++ /dev/null @@ -1,48 +0,0 @@ -export enum EvolutionItem { - NONE, - - LINKING_CORD, - SUN_STONE, - MOON_STONE, - LEAF_STONE, - FIRE_STONE, - WATER_STONE, - THUNDER_STONE, - ICE_STONE, - DUSK_STONE, - DAWN_STONE, - SHINY_STONE, - CRACKED_POT, - SWEET_APPLE, - TART_APPLE, - STRAWBERRY_SWEET, - UNREMARKABLE_TEACUP, - UPGRADE, - DUBIOUS_DISC, - DRAGON_SCALE, - PRISM_SCALE, - RAZOR_CLAW, - RAZOR_FANG, - REAPER_CLOTH, - ELECTIRIZER, - MAGMARIZER, - PROTECTOR, - SACHET, - WHIPPED_DREAM, - SYRUPY_APPLE, - CHIPPED_POT, - GALARICA_CUFF, - GALARICA_WREATH, - AUSPICIOUS_ARMOR, - MALICIOUS_ARMOR, - MASTERPIECE_TEACUP, - SUN_FLUTE, - MOON_FLUTE, - - BLACK_AUGURITE = 51, - PEAT_BLOCK, - METAL_ALLOY, - SCROLL_OF_DARKNESS, - SCROLL_OF_WATERS, - LEADERS_CREST -} diff --git a/src/enums/field-position.ts b/src/enums/field-position.ts deleted file mode 100644 index 5b7f9c6c570..00000000000 --- a/src/enums/field-position.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum FieldPosition { - CENTER, - LEFT, - RIGHT -} diff --git a/src/enums/hit-result.ts b/src/enums/hit-result.ts deleted file mode 100644 index 3e62587dd6c..00000000000 --- a/src/enums/hit-result.ts +++ /dev/null @@ -1,15 +0,0 @@ -export enum HitResult { - EFFECTIVE = 1, - SUPER_EFFECTIVE, - NOT_VERY_EFFECTIVE, - ONE_HIT_KO, - NO_EFFECT, - STATUS, - HEAL, - FAIL, - MISS, - INDIRECT, - IMMUNE, - CONFUSION, - INDIRECT_KO -} diff --git a/src/enums/learn-move-context.ts b/src/enums/learn-move-context.ts deleted file mode 100644 index 26001cbcce8..00000000000 --- a/src/enums/learn-move-context.ts +++ /dev/null @@ -1,8 +0,0 @@ -export enum LearnMoveContext { - MISC, - LEVEL_UP, - RELEARN, - EVOLUTION, - EVOLUTION_FUSED, // If fusionSpecies has Evolved - EVOLUTION_FUSED_BASE, // If fusion's base species has Evolved -} diff --git a/src/enums/species-wild-evolution-delay.ts b/src/enums/species-wild-evolution-delay.ts deleted file mode 100644 index 7555dc0e8f6..00000000000 --- a/src/enums/species-wild-evolution-delay.ts +++ /dev/null @@ -1,8 +0,0 @@ -export enum SpeciesWildEvolutionDelay { - NONE, - SHORT, - MEDIUM, - LONG, - VERY_LONG, - NEVER -} diff --git a/src/field/damage-number-handler.ts b/src/field/damage-number-handler.ts index 3bb001bf005..a527b148fff 100644 --- a/src/field/damage-number-handler.ts +++ b/src/field/damage-number-handler.ts @@ -1,7 +1,7 @@ import { TextStyle, addTextObject } from "../ui/text"; -import type { DamageResult } from "#app/@types/damage-result"; +import type { DamageResult } from "./pokemon"; import type Pokemon from "./pokemon"; -import { HitResult } from "#enums/hit-result"; +import { HitResult } from "./pokemon"; import { formatStat, fixedInt } from "#app/utils"; import type { BattlerIndex } from "../battle"; import { globalScene } from "#app/global-scene"; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 162a5118f65..b59b7ba01fe 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -17,6 +17,7 @@ import { applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, + allMoves, TypelessAttr, CritOnlyAttr, getMoveTargets, @@ -41,7 +42,6 @@ import { VariableMoveTypeChartAttr, HpSplitAttr, } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; import { MoveTarget } from "#enums/MoveTarget"; import { MoveCategory } from "#enums/MoveCategory"; import type { PokemonSpeciesForm } from "#app/data/pokemon-species"; @@ -214,7 +214,7 @@ import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeMoveLearnedTrigger, - SpeciesFormChangePostMoveTrigger, + SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms"; import { TerrainType } from "#app/data/terrain"; import type { TrainerSlot } from "#enums/trainer-slot"; @@ -259,15 +259,21 @@ import { MoveFlags } from "#enums/MoveFlags"; import { timedEventManager } from "#app/global-event-manager"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; import { ResetStatusPhase } from "#app/phases/reset-status-phase"; -import { LearnMoveContext } from "#enums/learn-move-context"; -import { TurnMove } from "#app/interfaces/turn-move"; -import { AiType } from "#enums/ai-type"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; -import { DamageCalculationResult } from "#app/interfaces/damage-calculation-result"; -import { FieldPosition } from "#enums/field-position"; -import { AttackMoveResult } from "#app/interfaces/attack-move-result"; -import { HitResult } from "#enums/hit-result"; -import { DamageResult } from "#app/@types/damage-result"; + +export enum LearnMoveSituation { + MISC, + LEVEL_UP, + RELEARN, + EVOLUTION, + EVOLUTION_FUSED, // If fusionSpecies has Evolved + EVOLUTION_FUSED_BASE, // If fusion's base species has Evolved +} + +export enum FieldPosition { + CENTER, + LEFT, + RIGHT, +} export default abstract class Pokemon extends Phaser.GameObjects.Container { public id: number; @@ -2919,7 +2925,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { includeEvolutionMoves = false, simulateEvolutionChain = false, includeRelearnerMoves = false, - learnSituation: LearnMoveContext = LearnMoveContext.MISC, + learnSituation: LearnMoveSituation = LearnMoveSituation.MISC, ): LevelMoves { const ret: LevelMoves = []; let levelMoves: LevelMoves = []; @@ -2927,7 +2933,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { startingLevel = this.level; } if ( - learnSituation === LearnMoveContext.EVOLUTION_FUSED && + learnSituation === LearnMoveSituation.EVOLUTION_FUSED && this.fusionSpecies ) { // For fusion evolutions, get ONLY the moves of the component mon that evolved @@ -2979,7 +2985,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if ( this.fusionSpecies && - learnSituation !== LearnMoveContext.EVOLUTION_FUSED_BASE + learnSituation !== LearnMoveSituation.EVOLUTION_FUSED_BASE ) { // For fusion evolutions, get ONLY the moves of the component mon that evolved if (simulateEvolutionChain) { @@ -7773,6 +7779,24 @@ interface IllusionData { level?: number } +export interface TurnMove { + move: Moves; + targets: BattlerIndex[]; + result?: MoveResult; + virtual?: boolean; + turn?: number; + ignorePP?: boolean; +} + +export interface AttackMoveResult { + move: Moves; + result: DamageResult; + damage: number; + critical: boolean; + sourceId: number; + sourceBattlerIndex: BattlerIndex; +} + export class PokemonSummonData { /** [Atk, Def, SpAtk, SpDef, Spd, Acc, Eva] */ public statStages: number[] = [0, 0, 0, 0, 0, 0, 0]; @@ -7845,6 +7869,12 @@ export class PokemonTurnData { public extraTurns = 0; } +export enum AiType { + RANDOM, + SMART_RANDOM, + SMART, +} + export enum MoveResult { PENDING, SUCCESS, @@ -7852,3 +7882,151 @@ export enum MoveResult { MISS, OTHER, } + +export enum HitResult { + EFFECTIVE = 1, + SUPER_EFFECTIVE, + NOT_VERY_EFFECTIVE, + ONE_HIT_KO, + NO_EFFECT, + STATUS, + HEAL, + FAIL, + MISS, + INDIRECT, + IMMUNE, + CONFUSION, + INDIRECT_KO, +} + +export type DamageResult = + | HitResult.EFFECTIVE + | HitResult.SUPER_EFFECTIVE + | HitResult.NOT_VERY_EFFECTIVE + | HitResult.ONE_HIT_KO + | HitResult.CONFUSION + | HitResult.INDIRECT_KO + | HitResult.INDIRECT; + +/** Interface containing the results of a damage calculation for a given move */ +export interface DamageCalculationResult { + /** `true` if the move was cancelled (thus suppressing "No Effect" messages) */ + cancelled: boolean; + /** The effectiveness of the move */ + result: HitResult; + /** The damage dealt by the move */ + damage: number; +} + +/** + * Wrapper class for the {@linkcode Move} class for Pokemon to interact with. + * These are the moves assigned to a {@linkcode Pokemon} object. + * It links to {@linkcode Move} class via the move ID. + * Compared to {@linkcode Move}, this class also tracks if a move has received. + * PP Ups, amount of PP used, and things like that. + * @see {@linkcode isUsable} - checks if move is restricted, out of PP, or not implemented. + * @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID. + * @see {@linkcode usePp} - removes a point of PP from the move. + * @see {@linkcode getMovePp} - returns amount of PP a move currently has. + * @see {@linkcode getPpRatio} - returns the current PP amount / max PP amount. + * @see {@linkcode getName} - returns name of {@linkcode Move}. + **/ +export class PokemonMove { + public moveId: Moves; + public ppUsed: number; + public ppUp: number; + public virtual: boolean; + + /** + * If defined and nonzero, overrides the maximum PP of the move (e.g., due to move being copied by Transform). + * This also nullifies all effects of `ppUp`. + */ + public maxPpOverride?: number; + + constructor( + moveId: Moves, + ppUsed = 0, + ppUp = 0, + virtual = false, + maxPpOverride?: number, + ) { + this.moveId = moveId; + this.ppUsed = ppUsed; + this.ppUp = ppUp; + this.virtual = virtual; + this.maxPpOverride = maxPpOverride; + } + + /** + * Checks whether the move can be selected or performed by a Pokemon, without consideration for the move's targets. + * The move is unusable if it is out of PP, restricted by an effect, or unimplemented. + * + * @param {Pokemon} 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}) + * @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`. + */ + isUsable( + pokemon: Pokemon, + ignorePp = false, + ignoreRestrictionTags = false, + ): boolean { + if ( + this.moveId && + !ignoreRestrictionTags && + pokemon.isMoveRestricted(this.moveId, pokemon) + ) { + return false; + } + + if (this.getMove().name.endsWith(" (N)")) { + return false; + } + + return ( + ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1 + ); + } + + getMove(): Move { + return allMoves[this.moveId]; + } + + /** + * Sets {@link ppUsed} for this move and ensures the value does not exceed {@link getMovePp} + * @param {number} count Amount of PP to use + */ + usePp(count = 1) { + this.ppUsed = Math.min(this.ppUsed + count, this.getMovePp()); + } + + getMovePp(): number { + return ( + this.maxPpOverride || + this.getMove().pp + this.ppUp * toDmgValue(this.getMove().pp / 5) + ); + } + + getPpRatio(): number { + return 1 - this.ppUsed / this.getMovePp(); + } + + getName(): string { + return this.getMove().name; + } + + /** + * Copies an existing move or creates a valid PokemonMove object from json representing one + * @param {PokemonMove | any} source The data for the move to copy + * @return {PokemonMove} A valid pokemonmove object + */ + static loadMove(source: PokemonMove | any): PokemonMove { + return new PokemonMove( + source.moveId, + source.ppUsed, + source.ppUp, + source.virtual, + source.maxPpOverride, + ); + } +} diff --git a/src/interfaces/attack-move-result.ts b/src/interfaces/attack-move-result.ts deleted file mode 100644 index f91d31a69ee..00000000000 --- a/src/interfaces/attack-move-result.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { BattlerIndex } from "#app/battle"; -import type { DamageResult } from "#app/@types/damage-result"; -import type { Moves } from "#enums/moves"; - -export interface AttackMoveResult { - move: Moves; - result: DamageResult; - damage: number; - critical: boolean; - sourceId: number; - sourceBattlerIndex: BattlerIndex; -} diff --git a/src/interfaces/damage-calculation-result.ts b/src/interfaces/damage-calculation-result.ts deleted file mode 100644 index 1220ff7b57d..00000000000 --- a/src/interfaces/damage-calculation-result.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { HitResult } from "#enums/hit-result"; - -/** Interface containing the results of a damage calculation for a given move */ -export interface DamageCalculationResult { - /** `true` if the move was cancelled (thus suppressing "No Effect" messages) */ - cancelled: boolean; - /** The effectiveness of the move */ - result: HitResult; - /** The damage dealt by the move */ - damage: number; -} diff --git a/src/interfaces/turn-move.ts b/src/interfaces/turn-move.ts deleted file mode 100644 index 639d309256e..00000000000 --- a/src/interfaces/turn-move.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { BattlerIndex } from "#app/battle"; -import type { MoveResult } from "#app/field/pokemon"; -import type { Moves } from "#enums/moves"; - -export interface TurnMove { - move: Moves; - targets: BattlerIndex[]; - result?: MoveResult; - virtual?: boolean; - turn?: number; - ignorePP?: boolean; -} diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 852593d922c..8feb60c7778 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1,10 +1,8 @@ import { globalScene } from "#app/global-scene"; -import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; -import { EvolutionItem } from "#enums/evolution-item"; +import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { tmPoolTiers, tmSpecies } from "#app/data/balance/tms"; import { getBerryEffectDescription, getBerryName } from "#app/data/berry"; -import { AttackMove } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, AttackMove } from "#app/data/moves/move"; import { getNatureName, getNatureStatMultiplier } from "#app/data/nature"; import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; import { @@ -15,8 +13,7 @@ import { } from "#app/data/pokemon-forms"; import { getStatusEffectDescriptor } from "#app/data/status-effect"; import { PokemonType } from "#enums/pokemon-type"; -import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; -import type { PokemonMove } from "#app/data/moves/pokemon-move"; +import type { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 7860d0f9296..80f14ba22ce 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1,7 +1,7 @@ import { FusionSpeciesFormEvolution, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry"; import { getLevelTotalExp } from "#app/data/exp"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; import { type FormChangeItem, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; import { getStatusEffectHealText } from "#app/data/status-effect"; diff --git a/src/overrides.ts b/src/overrides.ts index 49efb5eed33..21c72cd7b98 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -1,5 +1,5 @@ import { type PokeballCounts } from "#app/battle-scene"; -import { EvolutionItem } from "#enums/evolution-item"; +import { EvolutionItem } from "#app/data/balance/pokemon-evolutions"; import { Gender } from "#app/data/gender"; import { FormChangeItem } from "#app/data/pokemon-forms"; import { Variant } from "#app/sprites/variant"; diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index c65f121d20e..8691ac453ca 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -11,9 +11,8 @@ import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Biome } from "#app/enums/biome"; import { Moves } from "#app/enums/moves"; import { PokeballType } from "#enums/pokeball"; -import type { PlayerPokemon } from "#app/field/pokemon"; -import type { TurnMove } from "#app/interfaces/turn-move"; -import { FieldPosition } from "#enums/field-position"; +import type { PlayerPokemon, TurnMove } from "#app/field/pokemon"; +import { FieldPosition } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { Command } from "#app/ui/command-ui-handler"; import { Mode } from "#app/ui/ui"; diff --git a/src/phases/damage-anim-phase.ts b/src/phases/damage-anim-phase.ts index 91b21376515..696a2e55b6f 100644 --- a/src/phases/damage-anim-phase.ts +++ b/src/phases/damage-anim-phase.ts @@ -1,8 +1,7 @@ import { globalScene } from "#app/global-scene"; import type { BattlerIndex } from "#app/battle"; import { BattleSpec } from "#enums/battle-spec"; -import type { DamageResult } from "#app/@types/damage-result"; -import { HitResult } from "#enums/hit-result"; +import { type DamageResult, HitResult } from "#app/field/pokemon"; import { fixedInt } from "#app/utils"; import { PokemonPhase } from "#app/phases/pokemon-phase"; diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index 9e28de32c4a..15f3d102e41 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -11,7 +11,7 @@ import { TrainerSlot } from "#enums/trainer-slot"; import { getRandomWeatherType } from "#app/data/weather"; import { EncounterPhaseEvent } from "#app/events/battle-scene"; import type Pokemon from "#app/field/pokemon"; -import { FieldPosition } from "#enums/field-position"; +import { FieldPosition } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { BoostBugSpawnModifier, IvScannerModifier, TurnHeldItemTransferModifier } from "#app/modifier/modifier"; import { ModifierPoolType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index 076b7dec80d..203c7542eff 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -10,7 +10,7 @@ import { Mode } from "#app/ui/ui"; import { cos, sin } from "#app/field/anims"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { LearnMoveContext } from "#enums/learn-move-context"; +import { LearnMoveSituation } from "#app/field/pokemon"; import { getTypeRgb } from "#app/data/type"; import i18next from "i18next"; import { getPokemonNameWithAffix } from "#app/messages"; @@ -343,11 +343,11 @@ export class EvolutionPhase extends Phase { this.evolutionHandler.canCancel = false; this.pokemon.evolve(this.evolution, this.pokemon.species).then(() => { - const learnSituation: LearnMoveContext = this.fusionSpeciesEvolved - ? LearnMoveContext.EVOLUTION_FUSED + const learnSituation: LearnMoveSituation = this.fusionSpeciesEvolved + ? LearnMoveSituation.EVOLUTION_FUSED : this.pokemon.fusionSpecies - ? LearnMoveContext.EVOLUTION_FUSED_BASE - : LearnMoveContext.EVOLUTION; + ? LearnMoveSituation.EVOLUTION_FUSED_BASE + : LearnMoveSituation.EVOLUTION; const levelMoves = this.pokemon .getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation) .filter(lm => lm[0] === EVOLVE_MOVE); diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 4c418679047..7e1ae4ec07b 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -12,16 +12,13 @@ import { import type { DestinyBondTag, GrudgeTag } from "#app/data/battler-tags"; import { BattlerTagLapseType } from "#app/data/battler-tags"; import { battleSpecDialogue } from "#app/data/dialogue"; -import { PostVictoryStatStageChangeAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, PostVictoryStatStageChangeAttr } from "#app/data/moves/move"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { BattleSpec } from "#app/enums/battle-spec"; import { StatusEffect } from "#app/enums/status-effect"; import type { EnemyPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { PlayerPokemon } from "#app/field/pokemon"; -import { HitResult } from "#enums/hit-result"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { HitResult, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { SwitchType } from "#enums/switch-type"; diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts index a939298f620..4107a9cf087 100644 --- a/src/phases/learn-move-phase.ts +++ b/src/phases/learn-move-phase.ts @@ -1,7 +1,7 @@ import { globalScene } from "#app/global-scene"; import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; import type Move from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms"; import { Moves } from "#enums/moves"; import { getPokemonNameWithAffix } from "#app/messages"; diff --git a/src/phases/move-charge-phase.ts b/src/phases/move-charge-phase.ts index ccaf6d054b9..26ad85bbe03 100644 --- a/src/phases/move-charge-phase.ts +++ b/src/phases/move-charge-phase.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import type { BattlerIndex } from "#app/battle"; import { MoveChargeAnim } from "#app/data/battle-anims"; import { applyMoveChargeAttrs, MoveEffectAttr, InstantChargeAttr } from "#app/data/moves/move"; -import type { PokemonMove } from "#app/data/moves/pokemon-move"; +import type { PokemonMove } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { MoveResult } from "#app/field/pokemon"; import { BooleanHolder } from "#app/utils"; diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index c13c411be68..acc7ac0f63a 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -49,10 +49,9 @@ import { MoveTarget } from "#enums/MoveTarget"; import { MoveCategory } from "#enums/MoveCategory"; import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms"; import { PokemonType } from "#enums/pokemon-type"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { MoveResult } from "#app/field/pokemon"; -import { HitResult } from "#enums/hit-result"; +import { HitResult, MoveResult } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { ContactHeldItemTransferChanceModifier, diff --git a/src/phases/move-header-phase.ts b/src/phases/move-header-phase.ts index c255b45190b..c320df462d1 100644 --- a/src/phases/move-header-phase.ts +++ b/src/phases/move-header-phase.ts @@ -1,5 +1,5 @@ import { applyMoveAttrs, MoveHeaderAttr } from "#app/data/moves/move"; -import type { PokemonMove } from "#app/data/moves/pokemon-move"; +import type { PokemonMove } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { BattlePhase } from "./battle-phase"; diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 032ac6d06ab..478229dcae8 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -16,6 +16,7 @@ import { CommonAnim } from "#app/data/battle-anims"; import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags"; import { AddArenaTrapTagAttr, + allMoves, applyMoveAttrs, BypassRedirectAttr, BypassSleepAttr, @@ -26,14 +27,13 @@ import { PreMoveMessageAttr, PreUseInterruptAttr, } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; import { MoveFlags } from "#enums/MoveFlags"; import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms"; import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect"; import { PokemonType } from "#enums/pokemon-type"; import { getTerrainBlockMessage, getWeatherBlockMessage } from "#app/data/weather"; import { MoveUsedEvent } from "#app/events/battle-scene"; -import type { PokemonMove } from "#app/data/moves/pokemon-move"; +import type { PokemonMove } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { MoveResult } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; diff --git a/src/phases/pokemon-heal-phase.ts b/src/phases/pokemon-heal-phase.ts index 84dc8a5e116..651c625b23a 100644 --- a/src/phases/pokemon-heal-phase.ts +++ b/src/phases/pokemon-heal-phase.ts @@ -3,7 +3,7 @@ import type { BattlerIndex } from "#app/battle"; import { CommonAnim } from "#app/data/battle-anims"; import { getStatusEffectHealText } from "#app/data/status-effect"; import { StatusEffect } from "#app/enums/status-effect"; -import { HitResult } from "#enums/hit-result"; +import { HitResult } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { HealingBoosterModifier } from "#app/modifier/modifier"; import { HealAchv } from "#app/system/achv"; diff --git a/src/phases/pokemon-transform-phase.ts b/src/phases/pokemon-transform-phase.ts index fb9a28a5a26..b33689321b5 100644 --- a/src/phases/pokemon-transform-phase.ts +++ b/src/phases/pokemon-transform-phase.ts @@ -2,7 +2,7 @@ import type { BattlerIndex } from "#app/battle"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; import { EFFECTIVE_STATS, BATTLE_STATS } from "#enums/stat"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import { PokemonPhase } from "./pokemon-phase"; import { getPokemonNameWithAffix } from "#app/messages"; diff --git a/src/phases/select-target-phase.ts b/src/phases/select-target-phase.ts index edd56ba60ed..035eaff41fa 100644 --- a/src/phases/select-target-phase.ts +++ b/src/phases/select-target-phase.ts @@ -5,7 +5,7 @@ import { Mode } from "#app/ui/ui"; import { CommandPhase } from "./command-phase"; import { PokemonPhase } from "./pokemon-phase"; import i18next from "#app/plugins/i18n"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; export class SelectTargetPhase extends PokemonPhase { // biome-ignore lint/complexity/noUselessConstructor: This makes `fieldIndex` required diff --git a/src/phases/summon-phase.ts b/src/phases/summon-phase.ts index e053b18e4d7..7379d509e55 100644 --- a/src/phases/summon-phase.ts +++ b/src/phases/summon-phase.ts @@ -5,7 +5,7 @@ import { TrainerSlot } from "#enums/trainer-slot"; import { PlayerGender } from "#app/enums/player-gender"; import { addPokeballOpenParticles } from "#app/field/anims"; import type Pokemon from "#app/field/pokemon"; -import { FieldPosition } from "#enums/field-position"; +import { FieldPosition } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import i18next from "i18next"; import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase"; diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index f39a3e62bb6..d63cdb90f25 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -6,8 +6,7 @@ import { PreSummonAbAttr, PreSwitchOutAbAttr, } from "#app/data/ability"; -import { ForceSwitchOutAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, ForceSwitchOutAttr } from "#app/data/moves/move"; import { getPokeballTintColor } from "#app/data/pokeball"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { TrainerSlot } from "#enums/trainer-slot"; diff --git a/src/phases/toggle-double-position-phase.ts b/src/phases/toggle-double-position-phase.ts index c4766f888aa..37f47d5cf95 100644 --- a/src/phases/toggle-double-position-phase.ts +++ b/src/phases/toggle-double-position-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { FieldPosition } from "#enums/field-position"; +import { FieldPosition } from "#app/field/pokemon"; import { BattlePhase } from "./battle-phase"; export class ToggleDoublePositionPhase extends BattlePhase { diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index 5941e0af163..d5b4160fe1b 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -1,10 +1,9 @@ import { applyAbAttrs, BypassSpeedChanceAbAttr, PreventBypassSpeedChanceAbAttr } from "#app/data/ability"; -import { MoveHeaderAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, MoveHeaderAttr } from "#app/data/moves/move"; import { Abilities } from "#app/enums/abilities"; import { Stat } from "#app/enums/stat"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import { BypassSpeedChanceModifier } from "#app/modifier/modifier"; import { Command } from "#app/ui/command-ui-handler"; import { randSeedShuffle, BooleanHolder } from "#app/utils"; diff --git a/src/phases/weather-effect-phase.ts b/src/phases/weather-effect-phase.ts index 256894457fc..5284c9fba85 100644 --- a/src/phases/weather-effect-phase.ts +++ b/src/phases/weather-effect-phase.ts @@ -14,7 +14,7 @@ import { getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weath import { BattlerTagType } from "#app/enums/battler-tag-type"; import { WeatherType } from "#app/enums/weather-type"; import type Pokemon from "#app/field/pokemon"; -import { HitResult } from "#enums/hit-result"; +import { HitResult } from "#app/field/pokemon"; import { BooleanHolder, toDmgValue } from "#app/utils"; import { CommonAnimPhase } from "./common-anim-phase"; diff --git a/src/system/game-data.ts b/src/system/game-data.ts index e87c735f459..53146301666 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -30,7 +30,7 @@ import { Nature } from "#enums/nature"; import { GameStats } from "#app/system/game-stats"; import { Tutorial } from "#app/tutorial"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { TrainerVariant } from "#app/field/trainer"; import type { Variant } from "#app/sprites/variant"; import { setSettingGamepad, SettingGamepad, settingGamepadDefaults } from "#app/system/settings/settings-gamepad"; diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 7579fc3b78d..97ce494a43a 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -5,8 +5,7 @@ import type { Nature } from "#enums/nature"; import type { PokeballType } from "#enums/pokeball"; import { getPokemonSpecies, getPokemonSpeciesForm } from "../data/pokemon-species"; import { Status } from "../data/status-effect"; -import Pokemon, { EnemyPokemon, PokemonSummonData } from "../field/pokemon"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import Pokemon, { EnemyPokemon, PokemonMove, PokemonSummonData } from "../field/pokemon"; import { TrainerSlot } from "#enums/trainer-slot"; import type { Variant } from "#app/sprites/variant"; import { loadBattlerTag } from "../data/battler-tags"; diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 63c0703fa18..27985629e3d 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"; import { MoveCategory } from "#enums/MoveCategory"; import i18next from "i18next"; import { Button } from "#enums/buttons"; -import type { PokemonMove } from "#app/data/moves/pokemon-move"; +import type { 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"; diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index f0ff351bb8a..26351d4dbf1 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -9,7 +9,7 @@ import { LockModifierTiersModifier, PokemonHeldItemModifier, HealShopCostModifie import { handleTutorial, Tutorial } from "../tutorial"; import { Button } from "#enums/buttons"; import MoveInfoOverlay from "./move-info-overlay"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "../data/moves/move"; import { formatMoney, NumberHolder } from "#app/utils"; import Overrides from "#app/overrides"; import i18next from "i18next"; diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index a42e0caadae..ba90108c274 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -1,5 +1,4 @@ -import type { PlayerPokemon } from "#app/field/pokemon"; -import type { PokemonMove } from "#app/data/moves/pokemon-move"; +import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { MoveResult } from "#app/field/pokemon"; import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "#app/ui/text"; @@ -12,8 +11,7 @@ import { PokemonHeldItemModifier, SwitchEffectTransferModifier, } from "#app/modifier/modifier"; -import { ForceSwitchOutAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, ForceSwitchOutAttr } from "#app/data/moves/move"; import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender"; import { StatusEffect } from "#enums/status-effect"; import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler"; diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts index 1011fc89ae0..407ebfcd843 100644 --- a/src/ui/pokedex-page-ui-handler.ts +++ b/src/ui/pokedex-page-ui-handler.ts @@ -9,7 +9,7 @@ import { allAbilities } from "#app/data/ability"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { GrowthRate, getGrowthRateColor } from "#app/data/exp"; import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { getNatureName } from "#app/data/nature"; import type { SpeciesFormChange } from "#app/data/pokemon-forms"; import { pokemonFormChanges } from "#app/data/pokemon-forms"; diff --git a/src/ui/pokedex-scan-ui-handler.ts b/src/ui/pokedex-scan-ui-handler.ts index 54c32fb34a1..b34246b97d1 100644 --- a/src/ui/pokedex-scan-ui-handler.ts +++ b/src/ui/pokedex-scan-ui-handler.ts @@ -7,7 +7,7 @@ import { isNullOrUndefined } from "#app/utils"; import { Mode } from "./ui"; import { FilterTextRow } from "./filter-text"; import { allAbilities } from "#app/data/ability"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { allSpecies } from "#app/data/pokemon-species"; import i18next from "i18next"; diff --git a/src/ui/pokedex-ui-handler.ts b/src/ui/pokedex-ui-handler.ts index 22ce5b833af..59b06d476a2 100644 --- a/src/ui/pokedex-ui-handler.ts +++ b/src/ui/pokedex-ui-handler.ts @@ -38,7 +38,7 @@ import type { OptionSelectConfig } from "./abstact-option-select-ui-handler"; import { FilterText, FilterTextRow } from "./filter-text"; import { allAbilities } from "#app/data/ability"; import { starterPassiveAbilities } from "#app/data/balance/passives"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { speciesTmMoves } from "#app/data/balance/tms"; import { pokemonPrevolutions, pokemonStarters } from "#app/data/balance/pokemon-evolutions"; import { Biome } from "#enums/biome"; diff --git a/src/ui/pokemon-hatch-info-container.ts b/src/ui/pokemon-hatch-info-container.ts index 77f9f5090a0..692f0f1d374 100644 --- a/src/ui/pokemon-hatch-info-container.ts +++ b/src/ui/pokemon-hatch-info-container.ts @@ -4,7 +4,7 @@ import { PokemonType } from "#enums/pokemon-type"; import { rgbHexToRgba, padInt } from "#app/utils"; import { TextStyle, addTextObject } from "#app/ui/text"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Species } from "#enums/species"; import { getEggTierForSpecies } from "#app/data/egg"; import { starterColors } from "#app/battle-scene"; diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 680f752096b..3e2940f45b9 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -13,7 +13,7 @@ import { allAbilities } from "#app/data/ability"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { GrowthRate, getGrowthRateColor } from "#app/data/exp"; import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { getNatureName } from "#app/data/nature"; import { pokemonFormChanges } from "#app/data/pokemon-forms"; import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index d82082f0872..04bcf71d7ae 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -12,8 +12,7 @@ import { toReadableString, formatStat, } from "#app/utils"; -import type { PlayerPokemon } from "#app/field/pokemon"; -import type { PokemonMove } from "#app/data/moves/pokemon-move"; +import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { argbFromRgba } from "@material/material-color-utilities"; import { getTypeRgb } from "#app/data/type"; diff --git a/test/abilities/aura_break.test.ts b/test/abilities/aura_break.test.ts index 30841fdbe0c..86b6c69ec8b 100644 --- a/test/abilities/aura_break.test.ts +++ b/test/abilities/aura_break.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/test/abilities/battery.test.ts b/test/abilities/battery.test.ts index 78db19e67ff..cc7570c3d31 100644 --- a/test/abilities/battery.test.ts +++ b/test/abilities/battery.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Abilities } from "#app/enums/abilities"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; diff --git a/test/abilities/battle_bond.test.ts b/test/abilities/battle_bond.test.ts index e615b5746c0..6305d7dedc5 100644 --- a/test/abilities/battle_bond.test.ts +++ b/test/abilities/battle_bond.test.ts @@ -1,5 +1,4 @@ -import { MultiHitAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, MultiHitAttr } from "#app/data/moves/move"; import { MultiHitType } from "#enums/MultiHitType"; import { Status } from "#app/data/status-effect"; import { Abilities } from "#enums/abilities"; diff --git a/test/abilities/flower_veil.test.ts b/test/abilities/flower_veil.test.ts index d91c92e8c9f..c26a952acff 100644 --- a/test/abilities/flower_veil.test.ts +++ b/test/abilities/flower_veil.test.ts @@ -7,7 +7,7 @@ import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { BattlerTagType } from "#enums/battler-tag-type"; import { allAbilities } from "#app/data/ability"; diff --git a/test/abilities/friend_guard.test.ts b/test/abilities/friend_guard.test.ts index cee82ca2c69..30175fe37e0 100644 --- a/test/abilities/friend_guard.test.ts +++ b/test/abilities/friend_guard.test.ts @@ -6,7 +6,7 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { BattlerIndex } from "#app/battle"; import { allAbilities } from "#app/data/ability"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { MoveCategory } from "#enums/MoveCategory"; describe("Moves - Friend Guard", () => { diff --git a/test/abilities/galvanize.test.ts b/test/abilities/galvanize.test.ts index 4efb6bb068f..c1e02c6c8d8 100644 --- a/test/abilities/galvanize.test.ts +++ b/test/abilities/galvanize.test.ts @@ -1,10 +1,10 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { PokemonType } from "#enums/pokemon-type"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; -import { HitResult } from "#enums/hit-result"; +import { HitResult } from "#app/field/pokemon"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/abilities/hustle.test.ts b/test/abilities/hustle.test.ts index fbfa23e90d6..40197cf9e97 100644 --- a/test/abilities/hustle.test.ts +++ b/test/abilities/hustle.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Abilities } from "#app/enums/abilities"; import { Stat } from "#app/enums/stat"; import { Moves } from "#enums/moves"; diff --git a/test/abilities/infiltrator.test.ts b/test/abilities/infiltrator.test.ts index e9ecf366a37..6278439651c 100644 --- a/test/abilities/infiltrator.test.ts +++ b/test/abilities/infiltrator.test.ts @@ -1,5 +1,5 @@ import { ArenaTagSide } from "#app/data/arena-tag"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Stat } from "#enums/stat"; diff --git a/test/abilities/libero.test.ts b/test/abilities/libero.test.ts index 96a6b3c5d93..22abf1c248f 100644 --- a/test/abilities/libero.test.ts +++ b/test/abilities/libero.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { PokemonType } from "#enums/pokemon-type"; import { Weather } from "#app/data/weather"; import type { PlayerPokemon } from "#app/field/pokemon"; diff --git a/test/abilities/magic_bounce.test.ts b/test/abilities/magic_bounce.test.ts index c785827c910..f9a076776aa 100644 --- a/test/abilities/magic_bounce.test.ts +++ b/test/abilities/magic_bounce.test.ts @@ -1,7 +1,7 @@ import { BattlerIndex } from "#app/battle"; import { allAbilities } from "#app/data/ability"; import { ArenaTagSide } from "#app/data/arena-tag"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Stat } from "#app/enums/stat"; diff --git a/test/abilities/power_spot.test.ts b/test/abilities/power_spot.test.ts index 68ace696d4a..e29b5ecf775 100644 --- a/test/abilities/power_spot.test.ts +++ b/test/abilities/power_spot.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Abilities } from "#app/enums/abilities"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; diff --git a/test/abilities/protean.test.ts b/test/abilities/protean.test.ts index ca5e67139e1..574033bb13f 100644 --- a/test/abilities/protean.test.ts +++ b/test/abilities/protean.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { PokemonType } from "#enums/pokemon-type"; import { Weather } from "#app/data/weather"; import type { PlayerPokemon } from "#app/field/pokemon"; diff --git a/test/abilities/sap_sipper.test.ts b/test/abilities/sap_sipper.test.ts index b27f97099b9..f4f02844cbc 100644 --- a/test/abilities/sap_sipper.test.ts +++ b/test/abilities/sap_sipper.test.ts @@ -9,8 +9,7 @@ import { Species } from "#enums/species"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { RandomMoveAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, RandomMoveAttr } from "#app/data/moves/move"; // See also: TypeImmunityAbAttr describe("Abilities - Sap Sipper", () => { diff --git a/test/abilities/serene_grace.test.ts b/test/abilities/serene_grace.test.ts index 30073f30b24..65ca96acbbc 100644 --- a/test/abilities/serene_grace.test.ts +++ b/test/abilities/serene_grace.test.ts @@ -4,7 +4,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { FlinchAttr } from "#app/data/moves/move"; diff --git a/test/abilities/sheer_force.test.ts b/test/abilities/sheer_force.test.ts index 74c7b30a846..4a1c20cde5c 100644 --- a/test/abilities/sheer_force.test.ts +++ b/test/abilities/sheer_force.test.ts @@ -7,8 +7,7 @@ import { Stat } from "#enums/stat"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { FlinchAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, FlinchAttr } from "#app/data/moves/move"; describe("Abilities - Sheer Force", () => { let phaserGame: Phaser.Game; diff --git a/test/abilities/steely_spirit.test.ts b/test/abilities/steely_spirit.test.ts index 6e8331ea51a..b180ff8919e 100644 --- a/test/abilities/steely_spirit.test.ts +++ b/test/abilities/steely_spirit.test.ts @@ -1,5 +1,5 @@ import { allAbilities } from "#app/data/ability"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/test/abilities/supreme_overlord.test.ts b/test/abilities/supreme_overlord.test.ts index 69ff4f393b6..a71bf0a9354 100644 --- a/test/abilities/supreme_overlord.test.ts +++ b/test/abilities/supreme_overlord.test.ts @@ -7,7 +7,7 @@ import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; describe("Abilities - Supreme Overlord", () => { let phaserGame: Phaser.Game; diff --git a/test/abilities/tera_shell.test.ts b/test/abilities/tera_shell.test.ts index bd88c21f52d..a99ecfd4ce1 100644 --- a/test/abilities/tera_shell.test.ts +++ b/test/abilities/tera_shell.test.ts @@ -2,7 +2,7 @@ import { BattlerIndex } from "#app/battle"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; -import { HitResult } from "#enums/hit-result"; +import { HitResult } from "#app/field/pokemon"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/abilities/unburden.test.ts b/test/abilities/unburden.test.ts index 7012c4cf065..8f18604011c 100644 --- a/test/abilities/unburden.test.ts +++ b/test/abilities/unburden.test.ts @@ -1,7 +1,6 @@ import { BattlerIndex } from "#app/battle"; import { PostItemLostAbAttr } from "#app/data/ability"; -import { StealHeldItemChanceAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, StealHeldItemChanceAttr } from "#app/data/moves/move"; import type Pokemon from "#app/field/pokemon"; import type { ContactHeldItemTransferChanceModifier } from "#app/modifier/modifier"; import { Abilities } from "#enums/abilities"; diff --git a/test/abilities/wimp_out.test.ts b/test/abilities/wimp_out.test.ts index c81fa2071c5..294025a10e7 100644 --- a/test/abilities/wimp_out.test.ts +++ b/test/abilities/wimp_out.test.ts @@ -1,6 +1,6 @@ import { BattlerIndex } from "#app/battle"; import { ArenaTagSide } from "#app/data/arena-tag"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import GameManager from "#test/testUtils/gameManager"; import { toDmgValue } from "#app/utils"; import { Abilities } from "#enums/abilities"; @@ -534,12 +534,12 @@ describe("Abilities - Wimp Out", () => { .enemyAbility(Abilities.WIMP_OUT) .startingLevel(50) .enemyLevel(1) - .enemyMoveset([Moves.SPLASH, Moves.ENDURE]) + .enemyMoveset([ Moves.SPLASH, Moves.ENDURE ]) .battleType("double") - .moveset([Moves.DRAGON_ENERGY, Moves.SPLASH]) + .moveset([ Moves.DRAGON_ENERGY, Moves.SPLASH ]) .startingWave(wave); - await game.classicMode.startBattle([Species.REGIDRAGO, Species.MAGIKARP]); + await game.classicMode.startBattle([ Species.REGIDRAGO, Species.MAGIKARP ]); // turn 1 game.move.select(Moves.DRAGON_ENERGY, 0); @@ -549,5 +549,6 @@ describe("Abilities - Wimp Out", () => { await game.phaseInterceptor.to("SelectModifierPhase"); expect(game.scene.currentBattle.waveIndex).toBe(wave + 1); + }); }); diff --git a/test/abilities/wonder_skin.test.ts b/test/abilities/wonder_skin.test.ts index fe24cdad5ec..18d5be36aef 100644 --- a/test/abilities/wonder_skin.test.ts +++ b/test/abilities/wonder_skin.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; diff --git a/test/arena/arena_gravity.test.ts b/test/arena/arena_gravity.test.ts index 7e72d14460a..a5ce84667f0 100644 --- a/test/arena/arena_gravity.test.ts +++ b/test/arena/arena_gravity.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; diff --git a/test/arena/grassy_terrain.test.ts b/test/arena/grassy_terrain.test.ts index 9ee9d2ef434..d92fb24be5a 100644 --- a/test/arena/grassy_terrain.test.ts +++ b/test/arena/grassy_terrain.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/test/arena/weather_fog.test.ts b/test/arena/weather_fog.test.ts index b240bfa7386..784c4886648 100644 --- a/test/arena/weather_fog.test.ts +++ b/test/arena/weather_fog.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Abilities } from "#app/enums/abilities"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Moves } from "#enums/moves"; diff --git a/test/arena/weather_strong_winds.test.ts b/test/arena/weather_strong_winds.test.ts index 50d25947612..3a9235d9eb9 100644 --- a/test/arena/weather_strong_winds.test.ts +++ b/test/arena/weather_strong_winds.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { StatusEffect } from "#app/enums/status-effect"; import { TurnStartPhase } from "#app/phases/turn-start-phase"; import { Abilities } from "#enums/abilities"; diff --git a/test/battle/damage_calculation.test.ts b/test/battle/damage_calculation.test.ts index 11bb8246ca1..dab1fc81caa 100644 --- a/test/battle/damage_calculation.test.ts +++ b/test/battle/damage_calculation.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import type { EnemyPersistentModifier } from "#app/modifier/modifier"; import { modifierTypes } from "#app/modifier/modifier-type"; import { Abilities } from "#enums/abilities"; diff --git a/test/battlerTags/substitute.test.ts b/test/battlerTags/substitute.test.ts index f2ee741bca2..fca3dc5ef7e 100644 --- a/test/battlerTags/substitute.test.ts +++ b/test/battlerTags/substitute.test.ts @@ -1,7 +1,5 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import type { PokemonTurnData } from "#app/field/pokemon"; -import type { PokemonMove } from "#app/data/moves/pokemon-move"; -import type { TurnMove } from "#app/interfaces/turn-move"; +import type { PokemonTurnData, TurnMove, PokemonMove } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { MoveResult } from "#app/field/pokemon"; import type BattleScene from "#app/battle-scene"; @@ -9,7 +7,7 @@ import { BattlerTagLapseType, BindTag, SubstituteTag } from "#app/data/battler-t import { Moves } from "#app/enums/moves"; import { PokemonAnimType } from "#app/enums/pokemon-anim-type"; import * as messages from "#app/messages"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import type { MoveEffectPhase } from "#app/phases/move-effect-phase"; import GameManager from "#test/testUtils/gameManager"; diff --git a/test/enemy_command.test.ts b/test/enemy_command.test.ts index cfa141cf89e..6d5cc2698a3 100644 --- a/test/enemy_command.test.ts +++ b/test/enemy_command.test.ts @@ -1,11 +1,11 @@ import type BattleScene from "#app/battle-scene"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { MoveCategory } from "#enums/MoveCategory"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; import type { EnemyPokemon } from "#app/field/pokemon"; -import { AiType } from "#enums/ai-type"; +import { AiType } from "#app/field/pokemon"; import { randSeedInt } from "#app/utils"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; diff --git a/test/evolution.test.ts b/test/evolution.test.ts index 62a06f868e8..dd6795bf161 100644 --- a/test/evolution.test.ts +++ b/test/evolution.test.ts @@ -1,5 +1,8 @@ -import { pokemonEvolutions, SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; -import { SpeciesWildEvolutionDelay } from "#enums/species-wild-evolution-delay"; +import { + pokemonEvolutions, + SpeciesFormEvolution, + SpeciesWildEvolutionDelay, +} from "#app/data/balance/pokemon-evolutions"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; diff --git a/test/imports.test.ts b/test/imports.test.ts index ada7eff0109..128308dbd14 100644 --- a/test/imports.test.ts +++ b/test/imports.test.ts @@ -4,7 +4,7 @@ import { describe, expect, it } from "vitest"; async function importModule() { try { initStatsKeys(); - const { PokemonMove } = await import("#app/data/moves/pokemon-move"); + const { PokemonMove } = await import("#app/field/pokemon"); const { Species } = await import("#enums/species"); return { PokemonMove, diff --git a/test/items/reviver_seed.test.ts b/test/items/reviver_seed.test.ts index e1e7e0d554e..c06f354a94a 100644 --- a/test/items/reviver_seed.test.ts +++ b/test/items/reviver_seed.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import type { PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { Abilities } from "#enums/abilities"; diff --git a/test/moves/astonish.test.ts b/test/moves/astonish.test.ts index 69a312d4517..53922060ae6 100644 --- a/test/moves/astonish.test.ts +++ b/test/moves/astonish.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { BerryPhase } from "#app/phases/berry-phase"; import { CommandPhase } from "#app/phases/command-phase"; diff --git a/test/moves/aurora_veil.test.ts b/test/moves/aurora_veil.test.ts index 06637d0764e..31f6497bae5 100644 --- a/test/moves/aurora_veil.test.ts +++ b/test/moves/aurora_veil.test.ts @@ -1,8 +1,7 @@ import type BattleScene from "#app/battle-scene"; import { ArenaTagSide } from "#app/data/arena-tag"; import type Move from "#app/data/moves/move"; -import { CritOnlyAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, CritOnlyAttr } from "#app/data/moves/move"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import type Pokemon from "#app/field/pokemon"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; diff --git a/test/moves/burning_jealousy.test.ts b/test/moves/burning_jealousy.test.ts index c618b46e842..60387df4226 100644 --- a/test/moves/burning_jealousy.test.ts +++ b/test/moves/burning_jealousy.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Abilities } from "#app/enums/abilities"; import { StatusEffect } from "#app/enums/status-effect"; import { Moves } from "#enums/moves"; diff --git a/test/moves/ceaseless_edge.test.ts b/test/moves/ceaseless_edge.test.ts index 227645df360..d54f1bd9f21 100644 --- a/test/moves/ceaseless_edge.test.ts +++ b/test/moves/ceaseless_edge.test.ts @@ -1,5 +1,5 @@ import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Abilities } from "#app/enums/abilities"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; diff --git a/test/moves/copycat.test.ts b/test/moves/copycat.test.ts index 615206275d4..0d9b0951f89 100644 --- a/test/moves/copycat.test.ts +++ b/test/moves/copycat.test.ts @@ -1,6 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { RandomMoveAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, RandomMoveAttr } from "#app/data/moves/move"; import { Stat } from "#app/enums/stat"; import { MoveResult } from "#app/field/pokemon"; import { Abilities } from "#enums/abilities"; diff --git a/test/moves/destiny_bond.test.ts b/test/moves/destiny_bond.test.ts index 9873d678b8c..c39d40427ad 100644 --- a/test/moves/destiny_bond.test.ts +++ b/test/moves/destiny_bond.test.ts @@ -1,6 +1,6 @@ import type { ArenaTrapTag } from "#app/data/arena-tag"; import { ArenaTagSide } from "#app/data/arena-tag"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; import { Moves } from "#enums/moves"; diff --git a/test/moves/diamond_storm.test.ts b/test/moves/diamond_storm.test.ts index 73a1aee3fd2..2363122f0d7 100644 --- a/test/moves/diamond_storm.test.ts +++ b/test/moves/diamond_storm.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/test/moves/dig.test.ts b/test/moves/dig.test.ts index 14e7efee19b..81339111656 100644 --- a/test/moves/dig.test.ts +++ b/test/moves/dig.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Abilities } from "#enums/abilities"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; diff --git a/test/moves/dragon_tail.test.ts b/test/moves/dragon_tail.test.ts index a571312473d..37e8aa2fe1b 100644 --- a/test/moves/dragon_tail.test.ts +++ b/test/moves/dragon_tail.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Status } from "#app/data/status-effect"; import { Challenges } from "#enums/challenges"; import { StatusEffect } from "#enums/status-effect"; diff --git a/test/moves/dynamax_cannon.test.ts b/test/moves/dynamax_cannon.test.ts index b2590449e4e..9cf3106b9c1 100644 --- a/test/moves/dynamax_cannon.test.ts +++ b/test/moves/dynamax_cannon.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Moves } from "#enums/moves"; diff --git a/test/moves/effectiveness.test.ts b/test/moves/effectiveness.test.ts index efcbc9c3293..fb03f1c10a0 100644 --- a/test/moves/effectiveness.test.ts +++ b/test/moves/effectiveness.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { TrainerSlot } from "#enums/trainer-slot"; import { PokemonType } from "#enums/pokemon-type"; diff --git a/test/moves/fell_stinger.test.ts b/test/moves/fell_stinger.test.ts index 766fedf68dc..2ffa44c5a3a 100644 --- a/test/moves/fell_stinger.test.ts +++ b/test/moves/fell_stinger.test.ts @@ -7,7 +7,7 @@ import { Moves } from "#enums/moves"; import { Stat } from "#enums/stat"; import { StatusEffect } from "#app/enums/status-effect"; import { WeatherType } from "#app/enums/weather-type"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; describe("Moves - Fell Stinger", () => { let phaserGame: Phaser.Game; diff --git a/test/moves/fly.test.ts b/test/moves/fly.test.ts index 37fa42b608d..0bd7d22b2a7 100644 --- a/test/moves/fly.test.ts +++ b/test/moves/fly.test.ts @@ -8,7 +8,7 @@ import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; describe("Moves - Fly", () => { let phaserGame: Phaser.Game; diff --git a/test/moves/freezy_frost.test.ts b/test/moves/freezy_frost.test.ts index d764600bc78..c1ac4054e70 100644 --- a/test/moves/freezy_frost.test.ts +++ b/test/moves/freezy_frost.test.ts @@ -5,7 +5,7 @@ import { Species } from "#enums/species"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { CommandPhase } from "#app/phases/command-phase"; describe("Moves - Freezy Frost", () => { diff --git a/test/moves/fusion_flare_bolt.test.ts b/test/moves/fusion_flare_bolt.test.ts index 32df10b4c7c..c340aeea63f 100644 --- a/test/moves/fusion_flare_bolt.test.ts +++ b/test/moves/fusion_flare_bolt.test.ts @@ -1,6 +1,6 @@ import { Stat } from "#enums/stat"; import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import type Move from "#app/data/moves/move"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; diff --git a/test/moves/glaive_rush.test.ts b/test/moves/glaive_rush.test.ts index 28d6328c095..d3531b172e2 100644 --- a/test/moves/glaive_rush.test.ts +++ b/test/moves/glaive_rush.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; diff --git a/test/moves/hard_press.test.ts b/test/moves/hard_press.test.ts index 425993fb1a9..8891f0bf0e2 100644 --- a/test/moves/hard_press.test.ts +++ b/test/moves/hard_press.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; diff --git a/test/moves/hyper_beam.test.ts b/test/moves/hyper_beam.test.ts index b1a244f2ea4..5cd54e9b46a 100644 --- a/test/moves/hyper_beam.test.ts +++ b/test/moves/hyper_beam.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Abilities } from "#app/enums/abilities"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Moves } from "#app/enums/moves"; diff --git a/test/moves/lash_out.test.ts b/test/moves/lash_out.test.ts index 16632ec0065..8395633f5c0 100644 --- a/test/moves/lash_out.test.ts +++ b/test/moves/lash_out.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/test/moves/last_respects.test.ts b/test/moves/last_respects.test.ts index 891b287dece..ccab8a43415 100644 --- a/test/moves/last_respects.test.ts +++ b/test/moves/last_respects.test.ts @@ -3,7 +3,7 @@ import { BattlerIndex } from "#app/battle"; import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import GameManager from "#test/testUtils/gameManager"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import type Move from "#app/data/moves/move"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import Phaser from "phaser"; diff --git a/test/moves/light_screen.test.ts b/test/moves/light_screen.test.ts index b77bb1c790b..9cc6944ed3e 100644 --- a/test/moves/light_screen.test.ts +++ b/test/moves/light_screen.test.ts @@ -1,8 +1,7 @@ import type BattleScene from "#app/battle-scene"; import { ArenaTagSide } from "#app/data/arena-tag"; import type Move from "#app/data/moves/move"; -import { CritOnlyAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, CritOnlyAttr } from "#app/data/moves/move"; import { Abilities } from "#app/enums/abilities"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import type Pokemon from "#app/field/pokemon"; diff --git a/test/moves/magic_coat.test.ts b/test/moves/magic_coat.test.ts index e96125a23ac..2cc8dea8938 100644 --- a/test/moves/magic_coat.test.ts +++ b/test/moves/magic_coat.test.ts @@ -1,6 +1,6 @@ import { BattlerIndex } from "#app/battle"; import { ArenaTagSide } from "#app/data/arena-tag"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Stat } from "#app/enums/stat"; diff --git a/test/moves/metronome.test.ts b/test/moves/metronome.test.ts index bf045f5e9f9..80f32a3a6fb 100644 --- a/test/moves/metronome.test.ts +++ b/test/moves/metronome.test.ts @@ -1,6 +1,5 @@ import { RechargingTag, SemiInvulnerableTag } from "#app/data/battler-tags"; -import { RandomMoveAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, RandomMoveAttr } from "#app/data/moves/move"; import { Abilities } from "#app/enums/abilities"; import { Stat } from "#app/enums/stat"; import { CommandPhase } from "#app/phases/command-phase"; diff --git a/test/moves/moongeist_beam.test.ts b/test/moves/moongeist_beam.test.ts index 94197683ea4..117fe513e17 100644 --- a/test/moves/moongeist_beam.test.ts +++ b/test/moves/moongeist_beam.test.ts @@ -1,5 +1,4 @@ -import { RandomMoveAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, RandomMoveAttr } from "#app/data/moves/move"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/test/moves/pledge_moves.test.ts b/test/moves/pledge_moves.test.ts index d3b8e60ac62..c866d15357c 100644 --- a/test/moves/pledge_moves.test.ts +++ b/test/moves/pledge_moves.test.ts @@ -1,8 +1,7 @@ import { BattlerIndex } from "#app/battle"; import { allAbilities } from "#app/data/ability"; import { ArenaTagSide } from "#app/data/arena-tag"; -import { FlinchAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, FlinchAttr } from "#app/data/moves/move"; import { PokemonType } from "#enums/pokemon-type"; import { ArenaTagType } from "#enums/arena-tag-type"; import { Stat } from "#enums/stat"; diff --git a/test/moves/powder.test.ts b/test/moves/powder.test.ts index 510564e0f53..522b0b74ca7 100644 --- a/test/moves/powder.test.ts +++ b/test/moves/powder.test.ts @@ -5,8 +5,7 @@ import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { BerryPhase } from "#app/phases/berry-phase"; -import { MoveResult } from "#app/field/pokemon"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { MoveResult, PokemonMove } from "#app/field/pokemon"; import { PokemonType } from "#enums/pokemon-type"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { StatusEffect } from "#enums/status-effect"; diff --git a/test/moves/protect.test.ts b/test/moves/protect.test.ts index 65de079982f..d50c490f7d3 100644 --- a/test/moves/protect.test.ts +++ b/test/moves/protect.test.ts @@ -5,7 +5,7 @@ import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Stat } from "#enums/stat"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; import { BattlerIndex } from "#app/battle"; import { MoveResult } from "#app/field/pokemon"; diff --git a/test/moves/rage_fist.test.ts b/test/moves/rage_fist.test.ts index 73d83f4929c..f44901c5aba 100644 --- a/test/moves/rage_fist.test.ts +++ b/test/moves/rage_fist.test.ts @@ -2,7 +2,7 @@ import { BattlerIndex } from "#app/battle"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import type Move from "#app/data/moves/move"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; diff --git a/test/moves/reflect.test.ts b/test/moves/reflect.test.ts index 272e5c2972c..ac879a7cc2b 100644 --- a/test/moves/reflect.test.ts +++ b/test/moves/reflect.test.ts @@ -1,8 +1,7 @@ import type BattleScene from "#app/battle-scene"; import { ArenaTagSide } from "#app/data/arena-tag"; import type Move from "#app/data/moves/move"; -import { CritOnlyAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, CritOnlyAttr } from "#app/data/moves/move"; import { Abilities } from "#app/enums/abilities"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import type Pokemon from "#app/field/pokemon"; diff --git a/test/moves/retaliate.test.ts b/test/moves/retaliate.test.ts index 57d29b4fdfc..e916c9ffeaa 100644 --- a/test/moves/retaliate.test.ts +++ b/test/moves/retaliate.test.ts @@ -3,7 +3,7 @@ import Phaser from "phaser"; import GameManager from "#test/testUtils/gameManager"; import { Species } from "#enums/species"; import { Moves } from "#enums/moves"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import type Move from "#app/data/moves/move"; describe("Moves - Retaliate", () => { diff --git a/test/moves/rollout.test.ts b/test/moves/rollout.test.ts index 456f029cda1..89270c2dfc7 100644 --- a/test/moves/rollout.test.ts +++ b/test/moves/rollout.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { CommandPhase } from "#app/phases/command-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; diff --git a/test/moves/round.test.ts b/test/moves/round.test.ts index ec9f3f69a5e..82f080a25ea 100644 --- a/test/moves/round.test.ts +++ b/test/moves/round.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import type { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; diff --git a/test/moves/scale_shot.test.ts b/test/moves/scale_shot.test.ts index ee759b8404a..2be632adb54 100644 --- a/test/moves/scale_shot.test.ts +++ b/test/moves/scale_shot.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; diff --git a/test/moves/secret_power.test.ts b/test/moves/secret_power.test.ts index 40802dcc51f..37f1664251b 100644 --- a/test/moves/secret_power.test.ts +++ b/test/moves/secret_power.test.ts @@ -2,7 +2,7 @@ import { Abilities } from "#enums/abilities"; import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Stat } from "#enums/stat"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Species } from "#enums/species"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; diff --git a/test/moves/shell_side_arm.test.ts b/test/moves/shell_side_arm.test.ts index 232182ffef0..a5b065b76cb 100644 --- a/test/moves/shell_side_arm.test.ts +++ b/test/moves/shell_side_arm.test.ts @@ -1,6 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { ShellSideArmCategoryAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, ShellSideArmCategoryAttr } from "#app/data/moves/move"; import type Move from "#app/data/moves/move"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; diff --git a/test/moves/shell_trap.test.ts b/test/moves/shell_trap.test.ts index d3ba67843ac..2df94cdb828 100644 --- a/test/moves/shell_trap.test.ts +++ b/test/moves/shell_trap.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; import { MoveResult } from "#app/field/pokemon"; diff --git a/test/moves/sketch.test.ts b/test/moves/sketch.test.ts index 94f37757a6a..dfbf2eca713 100644 --- a/test/moves/sketch.test.ts +++ b/test/moves/sketch.test.ts @@ -1,15 +1,13 @@ import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { MoveResult } from "#app/field/pokemon"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { MoveResult, PokemonMove } from "#app/field/pokemon"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { StatusEffect } from "#app/enums/status-effect"; import { BattlerIndex } from "#app/battle"; -import { RandomMoveAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, RandomMoveAttr } from "#app/data/moves/move"; describe("Moves - Sketch", () => { let phaserGame: Phaser.Game; diff --git a/test/moves/solar_beam.test.ts b/test/moves/solar_beam.test.ts index b8a28065b64..dffd4f210e5 100644 --- a/test/moves/solar_beam.test.ts +++ b/test/moves/solar_beam.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { BattlerTagType } from "#enums/battler-tag-type"; import { WeatherType } from "#enums/weather-type"; import { MoveResult } from "#app/field/pokemon"; diff --git a/test/moves/sparkly_swirl.test.ts b/test/moves/sparkly_swirl.test.ts index 1908772598a..6cd357c7e0e 100644 --- a/test/moves/sparkly_swirl.test.ts +++ b/test/moves/sparkly_swirl.test.ts @@ -1,4 +1,4 @@ -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { StatusEffect } from "#app/enums/status-effect"; import { CommandPhase } from "#app/phases/command-phase"; import { Abilities } from "#enums/abilities"; diff --git a/test/moves/spectral_thief.test.ts b/test/moves/spectral_thief.test.ts index 271cb03073a..2e52b118a74 100644 --- a/test/moves/spectral_thief.test.ts +++ b/test/moves/spectral_thief.test.ts @@ -1,7 +1,7 @@ import { Abilities } from "#enums/abilities"; import { BattlerIndex } from "#app/battle"; import { Stat } from "#enums/stat"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; diff --git a/test/moves/spit_up.test.ts b/test/moves/spit_up.test.ts index 7ef6e5e5b14..d71647bda52 100644 --- a/test/moves/spit_up.test.ts +++ b/test/moves/spit_up.test.ts @@ -1,8 +1,8 @@ import { Stat } from "#enums/stat"; import { StockpilingTag } from "#app/data/battler-tags"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { BattlerTagType } from "#app/enums/battler-tag-type"; -import type { TurnMove } from "#app/interfaces/turn-move"; +import type { TurnMove } from "#app/field/pokemon"; import { MoveResult } from "#app/field/pokemon"; import GameManager from "#test/testUtils/gameManager"; import { Abilities } from "#enums/abilities"; diff --git a/test/moves/steamroller.test.ts b/test/moves/steamroller.test.ts index a0e4c29cce5..ba96928e01d 100644 --- a/test/moves/steamroller.test.ts +++ b/test/moves/steamroller.test.ts @@ -1,7 +1,7 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { BattlerTagType } from "#app/enums/battler-tag-type"; -import type { DamageCalculationResult } from "#app/interfaces/damage-calculation-result"; +import type { DamageCalculationResult } from "#app/field/pokemon"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/test/moves/stockpile.test.ts b/test/moves/stockpile.test.ts index f6e6a0087f6..033f24d5229 100644 --- a/test/moves/stockpile.test.ts +++ b/test/moves/stockpile.test.ts @@ -1,6 +1,6 @@ import { Stat } from "#enums/stat"; import { StockpilingTag } from "#app/data/battler-tags"; -import type { TurnMove } from "#app/interfaces/turn-move"; +import type { TurnMove } from "#app/field/pokemon"; import { MoveResult } from "#app/field/pokemon"; import { CommandPhase } from "#app/phases/command-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; diff --git a/test/moves/substitute.test.ts b/test/moves/substitute.test.ts index 68b90bf7cf8..23f7f4af4b9 100644 --- a/test/moves/substitute.test.ts +++ b/test/moves/substitute.test.ts @@ -1,8 +1,7 @@ import { BattlerIndex } from "#app/battle"; import { ArenaTagSide } from "#app/data/arena-tag"; import { SubstituteTag, TrappedTag } from "#app/data/battler-tags"; -import { StealHeldItemChanceAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, StealHeldItemChanceAttr } from "#app/data/moves/move"; import { MoveResult } from "#app/field/pokemon"; import type { CommandPhase } from "#app/phases/command-phase"; import GameManager from "#test/testUtils/gameManager"; diff --git a/test/moves/swallow.test.ts b/test/moves/swallow.test.ts index 86af584a174..baa03801079 100644 --- a/test/moves/swallow.test.ts +++ b/test/moves/swallow.test.ts @@ -1,7 +1,7 @@ import { Stat } from "#enums/stat"; import { StockpilingTag } from "#app/data/battler-tags"; import { BattlerTagType } from "#app/enums/battler-tag-type"; -import type { TurnMove } from "#app/interfaces/turn-move"; +import type { TurnMove } from "#app/field/pokemon"; import { MoveResult } from "#app/field/pokemon"; import { MovePhase } from "#app/phases/move-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; diff --git a/test/moves/telekinesis.test.ts b/test/moves/telekinesis.test.ts index 7537ba0168a..1355cb975f3 100644 --- a/test/moves/telekinesis.test.ts +++ b/test/moves/telekinesis.test.ts @@ -1,5 +1,5 @@ import { BattlerTagType } from "#enums/battler-tag-type"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/test/moves/tera_blast.test.ts b/test/moves/tera_blast.test.ts index 9d17ea6a3cc..c1a2b999fa0 100644 --- a/test/moves/tera_blast.test.ts +++ b/test/moves/tera_blast.test.ts @@ -1,11 +1,10 @@ import { BattlerIndex } from "#app/battle"; import { Stat } from "#enums/stat"; -import { TeraMoveCategoryAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, TeraMoveCategoryAttr } from "#app/data/moves/move"; import type Move from "#app/data/moves/move"; import { PokemonType } from "#enums/pokemon-type"; import { Abilities } from "#app/enums/abilities"; -import { HitResult } from "#enums/hit-result"; +import { HitResult } from "#app/field/pokemon"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/testUtils/gameManager"; diff --git a/test/moves/toxic.test.ts b/test/moves/toxic.test.ts index ab536364f6a..f2b1f82fe02 100644 --- a/test/moves/toxic.test.ts +++ b/test/moves/toxic.test.ts @@ -5,7 +5,7 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { StatusEffect } from "#enums/status-effect"; import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves } from "#app/data/moves/move"; describe("Moves - Toxic", () => { let phaserGame: Phaser.Game; diff --git a/test/moves/triple_arrows.test.ts b/test/moves/triple_arrows.test.ts index d1d14f7d3e6..eb434b25815 100644 --- a/test/moves/triple_arrows.test.ts +++ b/test/moves/triple_arrows.test.ts @@ -1,5 +1,4 @@ -import { FlinchAttr, StatStageChangeAttr } from "#app/data/moves/move"; -import { allMoves } from "#app/data/moves/all-moves"; +import { allMoves, FlinchAttr, StatStageChangeAttr } from "#app/data/moves/move"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import type Move from "#app/data/moves/move"; diff --git a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts index 728129007e7..3c7bda8febd 100644 --- a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts +++ b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts @@ -8,8 +8,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; import type BattleScene from "#app/battle-scene"; -import { PlayerPokemon } from "#app/field/pokemon"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { AnOfferYouCantRefuseEncounter } from "#app/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; diff --git a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts index c1e6a635f31..9befe77e688 100644 --- a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts +++ b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts @@ -11,7 +11,7 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import { Moves } from "#enums/moves"; import type BattleScene from "#app/battle-scene"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import { Mode } from "#app/ui/ui"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; diff --git a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index 7b3d87463bf..4bbe76e5c72 100644 --- a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -15,7 +15,7 @@ import { import { Moves } from "#enums/moves"; import type BattleScene from "#app/battle-scene"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import { Mode } from "#app/ui/ui"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; diff --git a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts index 5ea836d8aa6..77cd65e51b9 100644 --- a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts +++ b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts @@ -17,7 +17,7 @@ import { Moves } from "#enums/moves"; import { DancingLessonsEncounter } from "#app/data/mystery-encounters/encounters/dancing-lessons-encounter"; import { Mode } from "#app/ui/ui"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; diff --git a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts index 82d80bc3970..d233e72932a 100644 --- a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts +++ b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts @@ -11,7 +11,7 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import { Moves } from "#enums/moves"; import type BattleScene from "#app/battle-scene"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import { Mode } from "#app/ui/ui"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; diff --git a/test/mystery-encounter/encounters/part-timer-encounter.test.ts b/test/mystery-encounter/encounters/part-timer-encounter.test.ts index 308aa9839e9..639a2e140ff 100644 --- a/test/mystery-encounter/encounters/part-timer-encounter.test.ts +++ b/test/mystery-encounter/encounters/part-timer-encounter.test.ts @@ -14,7 +14,7 @@ import { CIVILIZATION_ENCOUNTER_BIOMES } from "#app/data/mystery-encounters/myst import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { PartTimerEncounter } from "#app/data/mystery-encounters/encounters/part-timer-encounter"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import { Moves } from "#enums/moves"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; diff --git a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index 0d0298901d0..a9e6a339d36 100644 --- a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -17,7 +17,7 @@ import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters import { Nature } from "#enums/nature"; import { BerryType } from "#enums/berry-type"; import { BattlerTagType } from "#enums/battler-tag-type"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import { Mode } from "#app/ui/ui"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { BerryModifier, PokemonBaseStatTotalModifier } from "#app/modifier/modifier"; diff --git a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index 44c8e7a8915..df7bbb9f424 100644 --- a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -12,7 +12,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Biome } from "#app/enums/biome"; import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; import { Species } from "#app/enums/species"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import { HealShopCostModifier, HitHealModifier, TurnHealModifier } from "#app/modifier/modifier"; import { ModifierTier } from "#app/modifier/modifier-tier"; import { modifierTypes, type PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; diff --git a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts index e4928406a18..452dfcf3784 100644 --- a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts +++ b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts @@ -10,7 +10,7 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import { Moves } from "#enums/moves"; import type BattleScene from "#app/battle-scene"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; diff --git a/test/testUtils/helpers/moveHelper.ts b/test/testUtils/helpers/moveHelper.ts index 333f95f2014..543f46b2026 100644 --- a/test/testUtils/helpers/moveHelper.ts +++ b/test/testUtils/helpers/moveHelper.ts @@ -1,7 +1,7 @@ import type { BattlerIndex } from "#app/battle"; import { Button } from "#app/enums/buttons"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/data/moves/pokemon-move"; +import { PokemonMove } from "#app/field/pokemon"; import Overrides from "#app/overrides"; import type { CommandPhase } from "#app/phases/command-phase"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; From 8216a379bf37efaed799261ac757df485af0914f Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Mon, 14 Apr 2025 12:37:26 -0500 Subject: [PATCH 31/33] [Dev][GitHub] Update to node 22 (#5586) * Update node and workflows to use version 22.14 * Update @types/node package * Update engines field in package.json * Hardcode node version in github pages workflow * Update to checkout@v4 in github pages workflow --- .github/workflows/deploy-beta.yml | 2 +- .github/workflows/deploy.yml | 2 +- .github/workflows/github-pages.yml | 10 +++++----- .github/workflows/quality.yml | 1 + .github/workflows/test-shard-template.yml | 5 +++-- .nvmrc | 2 +- README.md | 2 +- package-lock.json | 17 +++++++++-------- package.json | 4 ++-- 9 files changed, 24 insertions(+), 21 deletions(-) diff --git a/.github/workflows/deploy-beta.yml b/.github/workflows/deploy-beta.yml index d8d8126193d..8b0e33a18c4 100644 --- a/.github/workflows/deploy-beta.yml +++ b/.github/workflows/deploy-beta.yml @@ -15,7 +15,7 @@ jobs: submodules: 'recursive' - uses: actions/setup-node@v4 with: - node-version: "20" + node-version-file: '.nvmrc' - name: Install dependencies run: npm ci - name: Build diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e40b18eb69b..00190e477d5 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -18,7 +18,7 @@ jobs: submodules: 'recursive' - uses: actions/setup-node@v4 with: - node-version: "20" + node-version-file: '.nvmrc' - name: Install dependencies run: npm ci - name: Build diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index 58067ac81ac..b7d5fb95c1e 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout repository for Typedoc - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' path: pokerogue_docs @@ -34,14 +34,14 @@ jobs: sudo apt update sudo apt install -y git openssh-client - - name: Setup Node 20.13.1 - uses: actions/setup-node@v1 + - name: Setup Node 22.14.1 + uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 - name: Checkout repository for Github Pages if: github.event_name == 'push' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: pokerogue_gh ref: gh-pages diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 7e33a77a73a..d9592662998 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -29,6 +29,7 @@ jobs: uses: actions/setup-node@v4 # Use the setup-node action version 4 with: node-version-file: '.nvmrc' + cache: 'npm' - name: Install Node.js dependencies # Step to install Node.js dependencies run: npm ci # Use 'npm ci' to install dependencies diff --git a/.github/workflows/test-shard-template.yml b/.github/workflows/test-shard-template.yml index 9fc41d1b965..cee452f3a59 100644 --- a/.github/workflows/test-shard-template.yml +++ b/.github/workflows/test-shard-template.yml @@ -19,13 +19,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out Git repository - uses: actions/checkout@v4 + uses: actions/checkout@v4.2.2 with: submodules: 'recursive' - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version-file: '.nvmrc' + cache: 'npm' - name: Install Node.js dependencies run: npm ci - name: Run tests diff --git a/.nvmrc b/.nvmrc index 9bcccb9439d..517f38666b4 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v20.13.1 +v22.14.0 diff --git a/README.md b/README.md index 5bb3ecfd26f..56392808b3c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ If you have the motivation and experience with Typescript/Javascript (or are wil #### Prerequisites -- node: 20.13.1 +- node: 22.14.0 - npm: [how to install](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) #### Running Locally diff --git a/package-lock.json b/package-lock.json index 6b880370f0b..622eac908de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "@hpcc-js/wasm": "^2.22.4", "@stylistic/eslint-plugin-ts": "^4.1.0", "@types/jsdom": "^21.1.7", - "@types/node": "^20.12.13", + "@types/node": "^22.13.14", "@typescript-eslint/eslint-plugin": "^8.28.0", "@typescript-eslint/parser": "^8.28.0", "@vitest/coverage-istanbul": "^3.0.9", @@ -2582,12 +2582,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.14.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz", - "integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==", + "version": "22.13.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz", + "integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==", "dev": true, + "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.20.0" } }, "node_modules/@types/statuses": { @@ -7312,9 +7313,9 @@ "dev": true }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index c84e926fc35..ffe4c06bea0 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "@hpcc-js/wasm": "^2.22.4", "@stylistic/eslint-plugin-ts": "^4.1.0", "@types/jsdom": "^21.1.7", - "@types/node": "^20.12.13", + "@types/node": "^22.13.14", "@typescript-eslint/eslint-plugin": "^8.28.0", "@typescript-eslint/parser": "^8.28.0", "@vitest/coverage-istanbul": "^3.0.9", @@ -67,6 +67,6 @@ "phaser3-rex-plugins": "^1.80.14" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" } } From 3ec8f236f92b022e370eedcc6b695c057c6d7ac2 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Mon, 14 Apr 2025 20:13:05 -0400 Subject: [PATCH 32/33] [Refactor] Change how rival event rewards are generated (#5638) * Change how rival event rewards are generated * Simplify to switch case Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/trainers/trainer-config.ts | 13 ------------- src/phases/trainer-victory-phase.ts | 6 ------ src/phases/victory-phase.ts | 21 +++++++++++++++------ 3 files changed, 15 insertions(+), 25 deletions(-) diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 0ab7119dab9..4efe294f7d0 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -116,7 +116,6 @@ export class TrainerConfig { public modifierRewardFuncs: ModifierTypeFunc[] = []; public partyTemplates: TrainerPartyTemplate[]; public partyTemplateFunc: PartyTemplateFunc; - public eventRewardFuncs: ModifierTypeFunc[] = []; public partyMemberFuncs: PartyMemberFuncs = {}; public speciesPools: TrainerTierPools; public speciesFilter: PokemonSpeciesFilter; @@ -517,16 +516,6 @@ export class TrainerConfig { // return ret; // } - /** - * Sets eventRewardFuncs to the active event rewards for the specified wave - * @param wave Associated with {@linkcode getFixedBattleEventRewards} - * @returns this - */ - setEventModifierRewardFuncs(wave: number): TrainerConfig { - this.eventRewardFuncs = timedEventManager.getFixedBattleEventRewards(wave).map(r => modifierTypes[r]); - return this; - } - setModifierRewardFuncs(...modifierTypeFuncs: (() => ModifierTypeFunc)[]): TrainerConfig { this.modifierRewardFuncs = modifierTypeFuncs.map(func => () => { const modifierTypeFunc = func(); @@ -3692,7 +3681,6 @@ export const trainerConfigs: TrainerConfigs = { () => modifierTypes.SUPER_EXP_CHARM, () => modifierTypes.EXP_SHARE, ) - .setEventModifierRewardFuncs(8) .setPartyMemberFunc( 0, getRandomPartyMemberFunc( @@ -3760,7 +3748,6 @@ export const trainerConfigs: TrainerConfigs = { .setMixedBattleBgm("battle_rival") .setPartyTemplates(trainerPartyTemplates.RIVAL_2) .setModifierRewardFuncs(() => modifierTypes.EXP_SHARE) - .setEventModifierRewardFuncs(25) .setPartyMemberFunc( 0, getRandomPartyMemberFunc( diff --git a/src/phases/trainer-victory-phase.ts b/src/phases/trainer-victory-phase.ts index 637ddea8b56..f17071f118e 100644 --- a/src/phases/trainer-victory-phase.ts +++ b/src/phases/trainer-victory-phase.ts @@ -26,12 +26,6 @@ export class TrainerVictoryPhase extends BattlePhase { globalScene.unshiftPhase(new ModifierRewardPhase(modifierRewardFunc)); } - if (timedEventManager.isEventActive()) { - for (const rewardFunc of globalScene.currentBattle.trainer?.config.eventRewardFuncs!) { - globalScene.unshiftPhase(new ModifierRewardPhase(rewardFunc)); - } - } - const trainerType = globalScene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct? // Validate Voucher for boss trainers if (vouchers.hasOwnProperty(TrainerType[trainerType])) { diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts index 78bf72195e8..9f4412fe270 100644 --- a/src/phases/victory-phase.ts +++ b/src/phases/victory-phase.ts @@ -13,6 +13,7 @@ import { SelectModifierPhase } from "./select-modifier-phase"; import { TrainerVictoryPhase } from "./trainer-victory-phase"; import { handleMysteryEncounterVictory } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { globalScene } from "#app/global-scene"; +import { timedEventManager } from "#app/global-event-manager"; export class VictoryPhase extends PokemonPhase { /** If true, indicates that the phase is intended for EXP purposes only, and not to continue a battle to next phase */ @@ -53,12 +54,20 @@ export class VictoryPhase extends PokemonPhase { } if (globalScene.gameMode.isEndless || !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) { globalScene.pushPhase(new EggLapsePhase()); - if ( - globalScene.gameMode.isClassic && - globalScene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_BOSS_2 - ) { - // Should get Lock Capsule on 165 before shop phase so it can be used in the rewards shop - globalScene.pushPhase(new ModifierRewardPhase(modifierTypes.LOCK_CAPSULE)); + if (globalScene.gameMode.isClassic) { + switch (globalScene.currentBattle.waveIndex) { + case ClassicFixedBossWaves.RIVAL_1: + case ClassicFixedBossWaves.RIVAL_2: + // Get event modifiers for this wave + timedEventManager + .getFixedBattleEventRewards(globalScene.currentBattle.waveIndex) + .map(r => globalScene.pushPhase(new ModifierRewardPhase(modifierTypes[r]))); + break; + case ClassicFixedBossWaves.EVIL_BOSS_2: + // Should get Lock Capsule on 165 before shop phase so it can be used in the rewards shop + globalScene.pushPhase(new ModifierRewardPhase(modifierTypes.LOCK_CAPSULE)); + break; + } } if (globalScene.currentBattle.waveIndex % 10) { globalScene.pushPhase(new SelectModifierPhase(undefined, undefined, this.getFixedBattleCustomModifiers())); From 4740b593a02dfdc0b45e5515d56bf30a0d7cf4f8 Mon Sep 17 00:00:00 2001 From: Madmadness65 <59298170+Madmadness65@users.noreply.github.com> Date: Tue, 15 Apr 2025 00:27:14 -0500 Subject: [PATCH 33/33] =?UTF-8?q?[Balance]=20Fix=20Depot=20Agent=20trainer?= =?UTF-8?q?=20type=20lacking=20Pok=C3=A9mon=20(#5623)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix Depot Agent trainer type lacking Pokémon Also removes a stray duplicate Barboach from the Fisherman. --- src/data/trainers/trainer-config.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 4efe294f7d0..d9922ecc097 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -1318,7 +1318,16 @@ export const trainerConfigs: TrainerConfigs = { [TrainerPoolTier.RARE]: [Species.BELLOSSOM, Species.HITMONTOP, Species.MIME_JR, Species.ORICORIO], [TrainerPoolTier.SUPER_RARE]: [Species.QUAXLY, Species.JANGMO_O], }), - [TrainerType.DEPOT_AGENT]: new TrainerConfig(++t).setMoneyMultiplier(1.45).setEncounterBgm(TrainerType.CLERK), + [TrainerType.DEPOT_AGENT]: new TrainerConfig(++t) + .setMoneyMultiplier(1.45) + .setEncounterBgm(TrainerType.CLERK) + .setPartyTemplates( + trainerPartyTemplates.TWO_AVG, + trainerPartyTemplates.THREE_WEAK, + trainerPartyTemplates.THREE_AVG, + trainerPartyTemplates.FOUR_WEAK, + ) + .setSpeciesFilter(s => s.isOfType(PokemonType.GROUND)), [TrainerType.DOCTOR]: new TrainerConfig(++t) .setHasGenders("Nurse", "lass") .setHasDouble("Medical Team") @@ -1369,7 +1378,6 @@ export const trainerConfigs: TrainerConfigs = { Species.CHINCHOU, Species.CORSOLA, Species.WAILMER, - Species.BARBOACH, Species.CLAMPERL, Species.LUVDISC, Species.MANTYKE,