From c46507d49b503086fa0d37bf9ad89a979ff4eb17 Mon Sep 17 00:00:00 2001 From: Frederico Santos Date: Wed, 2 Oct 2024 01:58:28 +0100 Subject: [PATCH 01/16] [Update] Update subproject commit reference in locales --- public/locales | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales b/public/locales index 005989574fe..3ccef8472dd 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 005989574fe5638897fa2933b6f57ae4b9c83635 +Subproject commit 3ccef8472dd7cc7c362538489954cb8fdad27e5f From a6bcd6eeea064bc2428c24b6e3d0431fffcc2812 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 1 Oct 2024 18:58:16 -0700 Subject: [PATCH 02/16] [Refactor] Remove obsolete comments in egg gacha and starter select UIs (#4542) --- src/ui/egg-gacha-ui-handler.ts | 6 +++--- src/ui/starter-select-ui-handler.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index 2eb1a100f38..e8753122b6d 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -8,7 +8,7 @@ import { VoucherType, getVoucherTypeIcon } from "../system/voucher"; import { getPokemonSpecies } from "../data/pokemon-species"; import { addWindow } from "./ui-theme"; import { Tutorial, handleTutorial } from "../tutorial"; -import {Button} from "#enums/buttons"; +import { Button } from "#enums/buttons"; import Overrides from "#app/overrides"; import { GachaType } from "#app/enums/gacha-types"; import i18next from "i18next"; @@ -99,7 +99,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { const gachaInfoContainer = this.scene.add.container(160, 46); - const currentLanguage = i18next.resolvedLanguage!; // TODO: is this bang correct? + const currentLanguage = i18next.resolvedLanguage ?? "en"; let gachaTextStyle = TextStyle.WINDOW_ALT; let gachaX = 4; let gachaY = 0; @@ -217,7 +217,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { { multiplier: multiplierOne, description: `25 ${i18next.t("egg:pulls")}`, icon: getVoucherTypeIcon(VoucherType.GOLDEN) } ]; - const resolvedLanguage = i18next.resolvedLanguage ?? "en"; // TODO: is this bang correct? + const resolvedLanguage = i18next.resolvedLanguage ?? "en"; const pullOptionsText = pullOptions.map(option =>{ const desc = option.description.split(" "); if (desc[0].length < 2) { diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 3ecc7da42e3..cc1ba5ada07 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -358,8 +358,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { setup() { const ui = this.getUi(); - const currentLanguage = i18next.resolvedLanguage ?? "en"; // TODO: is this bang correct? - const langSettingKey = Object.keys(languageSettings).find(lang => currentLanguage.includes(lang)) ?? "en"; // TODO: is this bang correct? + const currentLanguage = i18next.resolvedLanguage ?? "en"; + const langSettingKey = Object.keys(languageSettings).find(lang => currentLanguage.includes(lang)) ?? "en"; const textSettings = languageSettings[langSettingKey]; this.starterSelectContainer = this.scene.add.container(0, -this.scene.game.canvas.height / 6); From 7473c31c779d262329924587b51ea2c8f5d6b9a1 Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Tue, 1 Oct 2024 18:58:56 -0700 Subject: [PATCH 03/16] [P2] Fix Tera Shell to apply to all hits of multi-strike moves (#4541) * Apply Tera Shell to all hits for multi-hit moves * fix undefined property error * ugh * Remove obsolete bangs --- src/data/ability.ts | 1 + src/field/pokemon.ts | 5 +++++ src/phases/move-effect-phase.ts | 4 +++- src/test/abilities/tera_shell.test.ts | 23 +++++++++++++++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index a698831a1eb..8a7365b2c28 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -519,6 +519,7 @@ export class FullHpResistTypeAbAttr extends PreDefendAbAttr { if (pokemon.isFullHp() && typeMultiplier.value > 0.5) { typeMultiplier.value = 0.5; + pokemon.turnData.moveEffectiveness = 0.5; return true; } return false; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 6afc0770a7f..22df5d3a6c3 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1538,6 +1538,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns The type damage multiplier, indicating the effectiveness of the move */ getMoveEffectiveness(source: Pokemon, move: Move, ignoreAbility: boolean = false, simulated: boolean = true, cancelled?: Utils.BooleanHolder): TypeDamageMultiplier { + if (!Utils.isNullOrUndefined(this.turnData?.moveEffectiveness)) { + return this.turnData?.moveEffectiveness; + } + if (move.hasAttr(TypelessAttr)) { return 1; } @@ -5019,6 +5023,7 @@ export class PokemonTurnData { public order: number; public statStagesIncreased: boolean = false; public statStagesDecreased: boolean = false; + public moveEffectiveness: TypeDamageMultiplier | null = null; } export enum AiType { diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 263a576c4f0..ca1fb87654f 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -354,12 +354,14 @@ export class MoveEffectPhase extends PokemonPhase { } else { // Queue message for number of hits made by multi-move // If multi-hit attack only hits once, still want to render a message - const hitsTotal = user.turnData.hitCount! - Math.max(user.turnData.hitsLeft!, 0); // TODO: are those bangs correct? + const hitsTotal = user.turnData.hitCount - Math.max(user.turnData.hitsLeft, 0); if (hitsTotal > 1 || (user.turnData.hitsLeft && user.turnData.hitsLeft > 0)) { // If there are multiple hits, or if there are hits of the multi-hit move left this.scene.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal })); } this.scene.applyModifiers(HitHealModifier, this.player, user); + // Clear all cached move effectiveness values among targets + this.getTargets().forEach((target) => target.turnData.moveEffectiveness = null); } } diff --git a/src/test/abilities/tera_shell.test.ts b/src/test/abilities/tera_shell.test.ts index 13df49136ca..9995f7c34c3 100644 --- a/src/test/abilities/tera_shell.test.ts +++ b/src/test/abilities/tera_shell.test.ts @@ -1,3 +1,4 @@ +import { BattlerIndex } from "#app/battle"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; @@ -106,4 +107,26 @@ describe("Abilities - Tera Shell", () => { expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp() - 40); } ); + + it( + "should change the effectiveness of all strikes of a multi-strike move", + async () => { + game.override.enemyMoveset([Moves.DOUBLE_HIT]); + + await game.classicMode.startBattle([Species.SNORLAX]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + vi.spyOn(playerPokemon, "apply"); + + game.move.select(Moves.SPLASH); + + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.move.forceHit(); + for (let i = 0; i < 2; i++) { + await game.phaseInterceptor.to("MoveEffectPhase"); + expect(playerPokemon.apply).toHaveLastReturnedWith(HitResult.NOT_VERY_EFFECTIVE); + } + expect(playerPokemon.apply).toHaveReturnedTimes(2); + } + ); }); From 8a2900ad2906dae3501a0f4cb70a35260aa5112b Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Wed, 2 Oct 2024 09:20:19 -0400 Subject: [PATCH 04/16] [Refactor] Move some relevant files, tables etc to new balance folder (#4510) * Move biomes.ts * Move starter costs, passives, friendship, starter-candy to starters.ts * Change relative imports to absolute imports * Add docstrings, passives.ts * Constants in rates.ts for egg pity, egg tier rates, hatch waves, variant chances --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> --- src/battle-scene.ts | 4 +- src/data/{ => balance}/biomes.ts | 6 +- src/data/{ => balance}/egg-moves.ts | 4 +- src/data/balance/passives.ts | 574 ++++++++ src/data/{ => balance}/pokemon-evolutions.ts | 14 +- src/data/{ => balance}/pokemon-level-moves.ts | 0 src/data/balance/rates.ts | 53 + src/data/balance/starters.ts | 671 +++++++++ src/data/{ => balance}/tms.ts | 2 +- src/data/challenge.ts | 21 +- src/data/daily-run.ts | 15 +- src/data/egg.ts | 69 +- .../an-offer-you-cant-refuse-encounter.ts | 5 +- .../the-expert-pokemon-breeder-encounter.ts | 7 +- .../the-pokemon-salesman-encounter.ts | 5 +- .../encounters/training-session-encounter.ts | 4 +- .../mystery-encounter-requirements.ts | 12 +- .../mystery-encounters/mystery-encounters.ts | 2 +- .../utils/encounter-phase-utils.ts | 2 +- .../utils/encounter-pokemon-utils.ts | 7 +- src/data/pokemon-species.ts | 1288 +---------------- src/data/trainer-config.ts | 34 +- src/field/arena.ts | 30 +- src/field/pokemon.ts | 77 +- src/field/trainer.ts | 20 +- src/loading-scene.ts | 26 +- src/modifier/modifier-type.ts | 36 +- src/modifier/modifier.ts | 37 +- src/phases/command-phase.ts | 4 +- src/phases/evolution-phase.ts | 24 +- src/phases/game-over-phase.ts | 2 +- src/phases/select-biome-phase.ts | 2 +- src/system/achv.ts | 2 +- src/system/game-data.ts | 75 +- src/test/evolution.test.ts | 2 +- .../uncommon-breed-encounter.test.ts | 2 +- .../mystery-encounter-utils.test.ts | 9 +- src/test/vitest.setup.ts | 6 +- src/ui/game-stats-ui-handler.ts | 24 +- src/ui/party-ui-handler.ts | 32 +- src/ui/pokemon-hatch-info-container.ts | 18 +- src/ui/starter-select-ui-handler.ts | 112 +- src/ui/summary-ui-handler.ts | 44 +- 43 files changed, 1729 insertions(+), 1654 deletions(-) rename src/data/{ => balance}/biomes.ts (99%) rename src/data/{ => balance}/egg-moves.ts (99%) create mode 100644 src/data/balance/passives.ts rename src/data/{ => balance}/pokemon-evolutions.ts (99%) rename src/data/{ => balance}/pokemon-level-moves.ts (100%) create mode 100644 src/data/balance/rates.ts create mode 100644 src/data/balance/starters.ts rename src/data/{ => balance}/tms.ts (99%) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index c5283b091d4..df852126bc2 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -25,7 +25,7 @@ import { trainerConfigs, TrainerSlot } from "./data/trainer-config"; import Trainer, { TrainerVariant } from "./field/trainer"; import TrainerData from "./system/trainer-data"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; -import { pokemonPrevolutions } from "./data/pokemon-evolutions"; +import { pokemonPrevolutions } from "./data/balance/pokemon-evolutions"; import PokeballTray from "./ui/pokeball-tray"; import InvertPostFX from "./pipelines/invert"; import { Achv, achvs, ModifierAchv, MoneyAchv } from "./system/achv"; @@ -42,7 +42,7 @@ import PokemonSpriteSparkleHandler from "./field/pokemon-sprite-sparkle-handler" import CharSprite from "./ui/char-sprite"; import DamageNumberHandler from "./field/damage-number-handler"; import PokemonInfoContainer from "./ui/pokemon-info-container"; -import { biomeDepths, getBiomeName } from "./data/biomes"; +import { biomeDepths, getBiomeName } from "./data/balance/biomes"; import { SceneBase } from "./scene-base"; import CandyBar from "./ui/candy-bar"; import { Variant, variantData } from "./data/variant"; diff --git a/src/data/biomes.ts b/src/data/balance/biomes.ts similarity index 99% rename from src/data/biomes.ts rename to src/data/balance/biomes.ts index 0e37cc94ff5..f35bd56b3a4 100644 --- a/src/data/biomes.ts +++ b/src/data/balance/biomes.ts @@ -1,6 +1,6 @@ -import { Type } from "./type"; -import * as Utils from "../utils"; -import { pokemonEvolutions, SpeciesFormEvolution } from "./pokemon-evolutions"; +import { Type } from "#app/data/type"; +import * as Utils from "#app/utils"; +import { pokemonEvolutions, SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; import i18next from "i18next"; import { Biome } from "#enums/biome"; import { Species } from "#enums/species"; diff --git a/src/data/egg-moves.ts b/src/data/balance/egg-moves.ts similarity index 99% rename from src/data/egg-moves.ts rename to src/data/balance/egg-moves.ts index 741b707af2f..8df92e179d9 100644 --- a/src/data/egg-moves.ts +++ b/src/data/balance/egg-moves.ts @@ -1,5 +1,5 @@ -import { allMoves } from "./move"; -import * as Utils from "../utils"; +import { allMoves } from "#app/data/move"; +import * as Utils from "#app/utils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/data/balance/passives.ts b/src/data/balance/passives.ts new file mode 100644 index 00000000000..6fb0e80e085 --- /dev/null +++ b/src/data/balance/passives.ts @@ -0,0 +1,574 @@ +import { Abilities } from "#app/enums/abilities"; +import { Species } from "#app/enums/species"; + +export const starterPassiveAbilities = { + [Species.BULBASAUR]: Abilities.GRASSY_SURGE, + [Species.CHARMANDER]: Abilities.BEAST_BOOST, + [Species.SQUIRTLE]: Abilities.STURDY, + [Species.CATERPIE]: Abilities.MAGICIAN, + [Species.WEEDLE]: Abilities.TINTED_LENS, + [Species.PIDGEY]: Abilities.SHEER_FORCE, + [Species.RATTATA]: Abilities.STRONG_JAW, + [Species.SPEAROW]: Abilities.MOXIE, + [Species.EKANS]: Abilities.REGENERATOR, + [Species.SANDSHREW]: Abilities.TOUGH_CLAWS, + [Species.NIDORAN_F]: Abilities.FLARE_BOOST, + [Species.NIDORAN_M]: Abilities.GUTS, + [Species.VULPIX]: Abilities.FUR_COAT, + [Species.ZUBAT]: Abilities.INTIMIDATE, + [Species.ODDISH]: Abilities.TRIAGE, + [Species.PARAS]: Abilities.TRIAGE, + [Species.VENONAT]: Abilities.SIMPLE, + [Species.DIGLETT]: Abilities.STURDY, + [Species.MEOWTH]: Abilities.TOUGH_CLAWS, + [Species.PSYDUCK]: Abilities.SIMPLE, + [Species.MANKEY]: Abilities.IRON_FIST, + [Species.GROWLITHE]: Abilities.SPEED_BOOST, + [Species.POLIWAG]: Abilities.NO_GUARD, + [Species.ABRA]: Abilities.PSYCHIC_SURGE, + [Species.MACHOP]: Abilities.QUICK_FEET, + [Species.BELLSPROUT]: Abilities.FLOWER_GIFT, + [Species.TENTACOOL]: Abilities.TOXIC_CHAIN, + [Species.GEODUDE]: Abilities.DRY_SKIN, + [Species.PONYTA]: Abilities.MAGIC_GUARD, + [Species.SLOWPOKE]: Abilities.UNAWARE, + [Species.MAGNEMITE]: Abilities.LEVITATE, + [Species.FARFETCHD]: Abilities.SNIPER, + [Species.DODUO]: Abilities.PARENTAL_BOND, + [Species.SEEL]: Abilities.WATER_BUBBLE, + [Species.GRIMER]: Abilities.WATER_ABSORB, + [Species.SHELLDER]: Abilities.ICE_SCALES, + [Species.GASTLY]: Abilities.SHADOW_SHIELD, + [Species.ONIX]: Abilities.ROCKY_PAYLOAD, + [Species.DROWZEE]: Abilities.MAGICIAN, + [Species.KRABBY]: Abilities.THERMAL_EXCHANGE, + [Species.VOLTORB]: Abilities.TRANSISTOR, + [Species.EXEGGCUTE]: Abilities.RIPEN, + [Species.CUBONE]: Abilities.PARENTAL_BOND, + [Species.LICKITUNG]: Abilities.CHEEK_POUCH, + [Species.KOFFING]: Abilities.PARENTAL_BOND, + [Species.RHYHORN]: Abilities.FILTER, + [Species.TANGELA]: Abilities.SEED_SOWER, + [Species.KANGASKHAN]: Abilities.GUTS, + [Species.HORSEA]: Abilities.DRAGONS_MAW, + [Species.GOLDEEN]: Abilities.MULTISCALE, + [Species.STARYU]: Abilities.REGENERATOR, + [Species.SCYTHER]: Abilities.TINTED_LENS, + [Species.PINSIR]: Abilities.TINTED_LENS, + [Species.TAUROS]: Abilities.STAMINA, + [Species.MAGIKARP]: Abilities.MULTISCALE, + [Species.LAPRAS]: Abilities.LIGHTNING_ROD, + [Species.DITTO]: Abilities.ADAPTABILITY, + [Species.EEVEE]: Abilities.PICKUP, + [Species.PORYGON]: Abilities.PROTEAN, + [Species.OMANYTE]: Abilities.STURDY, + [Species.KABUTO]: Abilities.TOUGH_CLAWS, + [Species.AERODACTYL]: Abilities.ORICHALCUM_PULSE, + [Species.ARTICUNO]: Abilities.SNOW_WARNING, + [Species.ZAPDOS]: Abilities.DRIZZLE, + [Species.MOLTRES]: Abilities.DROUGHT, + [Species.DRATINI]: Abilities.AERILATE, + [Species.MEWTWO]: Abilities.NEUROFORCE, + [Species.MEW]: Abilities.PROTEAN, + [Species.CHIKORITA]: Abilities.THICK_FAT, + [Species.CYNDAQUIL]: Abilities.DROUGHT, + [Species.TOTODILE]: Abilities.TOUGH_CLAWS, + [Species.SENTRET]: Abilities.PICKUP, + [Species.HOOTHOOT]: Abilities.AERILATE, + [Species.LEDYBA]: Abilities.PRANKSTER, + [Species.SPINARAK]: Abilities.PRANKSTER, + [Species.CHINCHOU]: Abilities.WATER_BUBBLE, + [Species.PICHU]: Abilities.ELECTRIC_SURGE, + [Species.CLEFFA]: Abilities.ANALYTIC, + [Species.IGGLYBUFF]: Abilities.HUGE_POWER, + [Species.TOGEPI]: Abilities.PIXILATE, + [Species.NATU]: Abilities.TINTED_LENS, + [Species.MAREEP]: Abilities.ELECTROMORPHOSIS, + [Species.HOPPIP]: Abilities.FLUFFY, + [Species.AIPOM]: Abilities.SCRAPPY, + [Species.SUNKERN]: Abilities.DROUGHT, + [Species.YANMA]: Abilities.SHEER_FORCE, + [Species.WOOPER]: Abilities.COMATOSE, + [Species.MURKROW]: Abilities.DARK_AURA, + [Species.MISDREAVUS]: Abilities.BEADS_OF_RUIN, + [Species.UNOWN]: Abilities.PICKUP, + [Species.GIRAFARIG]: Abilities.PARENTAL_BOND, + [Species.PINECO]: Abilities.IRON_BARBS, + [Species.DUNSPARCE]: Abilities.UNAWARE, + [Species.GLIGAR]: Abilities.TOXIC_BOOST, + [Species.SNUBBULL]: Abilities.PIXILATE, + [Species.QWILFISH]: Abilities.TOXIC_DEBRIS, + [Species.SHUCKLE]: Abilities.HARVEST, + [Species.HERACROSS]: Abilities.TECHNICIAN, + [Species.SNEASEL]: Abilities.TOUGH_CLAWS, + [Species.TEDDIURSA]: Abilities.THICK_FAT, + [Species.SLUGMA]: Abilities.DESOLATE_LAND, + [Species.SWINUB]: Abilities.SLUSH_RUSH, + [Species.CORSOLA]: Abilities.STORM_DRAIN, + [Species.REMORAID]: Abilities.SIMPLE, + [Species.DELIBIRD]: Abilities.HUGE_POWER, + [Species.SKARMORY]: Abilities.LIGHTNING_ROD, + [Species.HOUNDOUR]: Abilities.DROUGHT, + [Species.PHANPY]: Abilities.SPEED_BOOST, + [Species.STANTLER]: Abilities.SPEED_BOOST, + [Species.SMEARGLE]: Abilities.PRANKSTER, + [Species.TYROGUE]: Abilities.MOXIE, + [Species.SMOOCHUM]: Abilities.PSYCHIC_SURGE, + [Species.ELEKID]: Abilities.SHEER_FORCE, + [Species.MAGBY]: Abilities.CONTRARY, + [Species.MILTANK]: Abilities.STAMINA, + [Species.RAIKOU]: Abilities.TRANSISTOR, + [Species.ENTEI]: Abilities.MOXIE, + [Species.SUICUNE]: Abilities.UNAWARE, + [Species.LARVITAR]: Abilities.SAND_RUSH, + [Species.LUGIA]: Abilities.DELTA_STREAM, + [Species.HO_OH]: Abilities.MAGIC_GUARD, + [Species.CELEBI]: Abilities.PSYCHIC_SURGE, + [Species.TREECKO]: Abilities.TINTED_LENS, + [Species.TORCHIC]: Abilities.RECKLESS, + [Species.MUDKIP]: Abilities.DRIZZLE, + [Species.POOCHYENA]: Abilities.TOUGH_CLAWS, + [Species.ZIGZAGOON]: Abilities.RUN_AWAY, + [Species.WURMPLE]: Abilities.SIMPLE, + [Species.LOTAD]: Abilities.DRIZZLE, + [Species.SEEDOT]: Abilities.SHARPNESS, + [Species.TAILLOW]: Abilities.AERILATE, + [Species.WINGULL]: Abilities.SWIFT_SWIM, + [Species.RALTS]: Abilities.PSYCHIC_SURGE, + [Species.SURSKIT]: Abilities.WATER_BUBBLE, + [Species.SHROOMISH]: Abilities.GUTS, + [Species.SLAKOTH]: Abilities.GUTS, + [Species.NINCADA]: Abilities.MAGIC_GUARD, + [Species.WHISMUR]: Abilities.PUNK_ROCK, + [Species.MAKUHITA]: Abilities.STAMINA, + [Species.AZURILL]: Abilities.MISTY_SURGE, + [Species.NOSEPASS]: Abilities.LEVITATE, + [Species.SKITTY]: Abilities.SCRAPPY, + [Species.SABLEYE]: Abilities.UNNERVE, + [Species.MAWILE]: Abilities.UNNERVE, + [Species.ARON]: Abilities.EARTH_EATER, + [Species.MEDITITE]: Abilities.MINDS_EYE, + [Species.ELECTRIKE]: Abilities.ELECTRIC_SURGE, + [Species.PLUSLE]: Abilities.POWER_SPOT, + [Species.MINUN]: Abilities.POWER_SPOT, + [Species.VOLBEAT]: Abilities.HONEY_GATHER, + [Species.ILLUMISE]: Abilities.HONEY_GATHER, + [Species.GULPIN]: Abilities.EARTH_EATER, + [Species.CARVANHA]: Abilities.SHEER_FORCE, + [Species.WAILMER]: Abilities.LEVITATE, + [Species.NUMEL]: Abilities.FUR_COAT, + [Species.TORKOAL]: Abilities.ANALYTIC, + [Species.SPOINK]: Abilities.PSYCHIC_SURGE, + [Species.SPINDA]: Abilities.SIMPLE, + [Species.TRAPINCH]: Abilities.ADAPTABILITY, + [Species.CACNEA]: Abilities.SAND_RUSH, + [Species.SWABLU]: Abilities.ADAPTABILITY, + [Species.ZANGOOSE]: Abilities.POISON_HEAL, + [Species.SEVIPER]: Abilities.MULTISCALE, + [Species.LUNATONE]: Abilities.SHADOW_SHIELD, + [Species.SOLROCK]: Abilities.DROUGHT, + [Species.BARBOACH]: Abilities.SIMPLE, + [Species.CORPHISH]: Abilities.TOUGH_CLAWS, + [Species.BALTOY]: Abilities.WELL_BAKED_BODY, + [Species.LILEEP]: Abilities.SEED_SOWER, + [Species.ANORITH]: Abilities.WATER_ABSORB, + [Species.FEEBAS]: Abilities.MAGIC_GUARD, + [Species.CASTFORM]: Abilities.ADAPTABILITY, + [Species.KECLEON]: Abilities.ADAPTABILITY, + [Species.SHUPPET]: Abilities.SHADOW_SHIELD, + [Species.DUSKULL]: Abilities.UNNERVE, + [Species.TROPIUS]: Abilities.RIPEN, + [Species.ABSOL]: Abilities.SHARPNESS, + [Species.WYNAUT]: Abilities.STURDY, + [Species.SNORUNT]: Abilities.SNOW_WARNING, + [Species.SPHEAL]: Abilities.UNAWARE, + [Species.CLAMPERL]: Abilities.DRIZZLE, + [Species.RELICANTH]: Abilities.PRIMORDIAL_SEA, + [Species.LUVDISC]: Abilities.MULTISCALE, + [Species.BAGON]: Abilities.DRAGONS_MAW, + [Species.BELDUM]: Abilities.LEVITATE, + [Species.REGIROCK]: Abilities.SAND_STREAM, + [Species.REGICE]: Abilities.SNOW_WARNING, + [Species.REGISTEEL]: Abilities.FILTER, + [Species.LATIAS]: Abilities.PRISM_ARMOR, + [Species.LATIOS]: Abilities.TINTED_LENS, + [Species.KYOGRE]: Abilities.MOLD_BREAKER, + [Species.GROUDON]: Abilities.TURBOBLAZE, + [Species.RAYQUAZA]: Abilities.UNNERVE, + [Species.JIRACHI]: Abilities.COMATOSE, + [Species.DEOXYS]: Abilities.PROTEAN, + [Species.TURTWIG]: Abilities.THICK_FAT, + [Species.CHIMCHAR]: Abilities.BEAST_BOOST, + [Species.PIPLUP]: Abilities.DRIZZLE, + [Species.STARLY]: Abilities.ROCK_HEAD, + [Species.BIDOOF]: Abilities.SAP_SIPPER, + [Species.KRICKETOT]: Abilities.SHARPNESS, + [Species.SHINX]: Abilities.SPEED_BOOST, + [Species.BUDEW]: Abilities.GRASSY_SURGE, + [Species.CRANIDOS]: Abilities.ROCK_HEAD, + [Species.SHIELDON]: Abilities.EARTH_EATER, + [Species.BURMY]: Abilities.STURDY, + [Species.COMBEE]: Abilities.INTIMIDATE, + [Species.PACHIRISU]: Abilities.HONEY_GATHER, + [Species.BUIZEL]: Abilities.MOXIE, + [Species.CHERUBI]: Abilities.ORICHALCUM_PULSE, + [Species.SHELLOS]: Abilities.REGENERATOR, + [Species.DRIFLOON]: Abilities.MAGIC_GUARD, + [Species.BUNEARY]: Abilities.ADAPTABILITY, + [Species.GLAMEOW]: Abilities.INTIMIDATE, + [Species.CHINGLING]: Abilities.PUNK_ROCK, + [Species.STUNKY]: Abilities.NEUTRALIZING_GAS, + [Species.BRONZOR]: Abilities.BULLETPROOF, + [Species.BONSLY]: Abilities.SAP_SIPPER, + [Species.MIME_JR]: Abilities.OPPORTUNIST, + [Species.HAPPINY]: Abilities.FUR_COAT, + [Species.CHATOT]: Abilities.PUNK_ROCK, + [Species.SPIRITOMB]: Abilities.VESSEL_OF_RUIN, + [Species.GIBLE]: Abilities.SAND_STREAM, + [Species.MUNCHLAX]: Abilities.RIPEN, + [Species.RIOLU]: Abilities.MINDS_EYE, + [Species.HIPPOPOTAS]: Abilities.UNAWARE, + [Species.SKORUPI]: Abilities.SUPER_LUCK, + [Species.CROAGUNK]: Abilities.MOXIE, + [Species.CARNIVINE]: Abilities.ARENA_TRAP, + [Species.FINNEON]: Abilities.WATER_BUBBLE, + [Species.MANTYKE]: Abilities.UNAWARE, + [Species.SNOVER]: Abilities.THICK_FAT, + [Species.ROTOM]: Abilities.HADRON_ENGINE, + [Species.UXIE]: Abilities.UNAWARE, + [Species.MESPRIT]: Abilities.MOODY, + [Species.AZELF]: Abilities.NEUROFORCE, + [Species.DIALGA]: Abilities.LEVITATE, + [Species.PALKIA]: Abilities.SPEED_BOOST, + [Species.HEATRAN]: Abilities.EARTH_EATER, + [Species.REGIGIGAS]: Abilities.SCRAPPY, + [Species.GIRATINA]: Abilities.SHADOW_SHIELD, + [Species.CRESSELIA]: Abilities.SHADOW_SHIELD, + [Species.PHIONE]: Abilities.SIMPLE, + [Species.MANAPHY]: Abilities.PRIMORDIAL_SEA, + [Species.DARKRAI]: Abilities.UNNERVE, + [Species.SHAYMIN]: Abilities.WIND_RIDER, + [Species.ARCEUS]: Abilities.ADAPTABILITY, + [Species.VICTINI]: Abilities.SHEER_FORCE, + [Species.SNIVY]: Abilities.MULTISCALE, + [Species.TEPIG]: Abilities.ROCK_HEAD, + [Species.OSHAWOTT]: Abilities.INTREPID_SWORD, + [Species.PATRAT]: Abilities.NO_GUARD, + [Species.LILLIPUP]: Abilities.FUR_COAT, + [Species.PURRLOIN]: Abilities.PICKUP, + [Species.PANSAGE]: Abilities.WELL_BAKED_BODY, + [Species.PANSEAR]: Abilities.WATER_ABSORB, + [Species.PANPOUR]: Abilities.SAP_SIPPER, + [Species.MUNNA]: Abilities.NEUTRALIZING_GAS, + [Species.PIDOVE]: Abilities.SNIPER, + [Species.BLITZLE]: Abilities.ELECTRIC_SURGE, + [Species.ROGGENROLA]: Abilities.SOLID_ROCK, + [Species.WOOBAT]: Abilities.OPPORTUNIST, + [Species.DRILBUR]: Abilities.SAND_STREAM, + [Species.AUDINO]: Abilities.FRIEND_GUARD, + [Species.TIMBURR]: Abilities.ROCKY_PAYLOAD, + [Species.TYMPOLE]: Abilities.POISON_HEAL, + [Species.THROH]: Abilities.STAMINA, + [Species.SAWK]: Abilities.SCRAPPY, + [Species.SEWADDLE]: Abilities.SHARPNESS, + [Species.VENIPEDE]: Abilities.STAMINA, + [Species.COTTONEE]: Abilities.FLUFFY, + [Species.PETILIL]: Abilities.SIMPLE, + [Species.BASCULIN]: Abilities.SUPREME_OVERLORD, + [Species.SANDILE]: Abilities.TOUGH_CLAWS, + [Species.DARUMAKA]: Abilities.GORILLA_TACTICS, + [Species.MARACTUS]: Abilities.WELL_BAKED_BODY, + [Species.DWEBBLE]: Abilities.ROCKY_PAYLOAD, + [Species.SCRAGGY]: Abilities.PROTEAN, + [Species.SIGILYPH]: Abilities.FLARE_BOOST, + [Species.YAMASK]: Abilities.PURIFYING_SALT, + [Species.TIRTOUGA]: Abilities.WATER_ABSORB, + [Species.ARCHEN]: Abilities.MULTISCALE, + [Species.TRUBBISH]: Abilities.NEUTRALIZING_GAS, + [Species.ZORUA]: Abilities.DARK_AURA, + [Species.MINCCINO]: Abilities.FUR_COAT, + [Species.GOTHITA]: Abilities.UNNERVE, + [Species.SOLOSIS]: Abilities.PSYCHIC_SURGE, + [Species.DUCKLETT]: Abilities.DRIZZLE, + [Species.VANILLITE]: Abilities.SLUSH_RUSH, + [Species.DEERLING]: Abilities.FUR_COAT, + [Species.EMOLGA]: Abilities.TRANSISTOR, + [Species.KARRABLAST]: Abilities.QUICK_DRAW, + [Species.FOONGUS]: Abilities.THICK_FAT, + [Species.FRILLISH]: Abilities.POISON_HEAL, + [Species.ALOMOMOLA]: Abilities.MULTISCALE, + [Species.JOLTIK]: Abilities.TRANSISTOR, + [Species.FERROSEED]: Abilities.ROUGH_SKIN, + [Species.KLINK]: Abilities.STEELY_SPIRIT, + [Species.TYNAMO]: Abilities.POISON_HEAL, + [Species.ELGYEM]: Abilities.PRISM_ARMOR, + [Species.LITWICK]: Abilities.SOUL_HEART, + [Species.AXEW]: Abilities.DRAGONS_MAW, + [Species.CUBCHOO]: Abilities.TOUGH_CLAWS, + [Species.CRYOGONAL]: Abilities.SNOW_WARNING, + [Species.SHELMET]: Abilities.PROTEAN, + [Species.STUNFISK]: Abilities.STORM_DRAIN, + [Species.MIENFOO]: Abilities.NO_GUARD, + [Species.DRUDDIGON]: Abilities.INTIMIDATE, + [Species.GOLETT]: Abilities.SHADOW_SHIELD, + [Species.PAWNIARD]: Abilities.SWORD_OF_RUIN, + [Species.BOUFFALANT]: Abilities.ROCK_HEAD, + [Species.RUFFLET]: Abilities.SPEED_BOOST, + [Species.VULLABY]: Abilities.THICK_FAT, + [Species.HEATMOR]: Abilities.CONTRARY, + [Species.DURANT]: Abilities.COMPOUND_EYES, + [Species.DEINO]: Abilities.PARENTAL_BOND, + [Species.LARVESTA]: Abilities.DROUGHT, + [Species.COBALION]: Abilities.INTREPID_SWORD, + [Species.TERRAKION]: Abilities.ROCKY_PAYLOAD, + [Species.VIRIZION]: Abilities.SHARPNESS, + [Species.TORNADUS]: Abilities.DRIZZLE, + [Species.THUNDURUS]: Abilities.DRIZZLE, + [Species.RESHIRAM]: Abilities.ORICHALCUM_PULSE, + [Species.ZEKROM]: Abilities.HADRON_ENGINE, + [Species.LANDORUS]: Abilities.STORM_DRAIN, + [Species.KYUREM]: Abilities.SNOW_WARNING, + [Species.KELDEO]: Abilities.GRIM_NEIGH, + [Species.MELOETTA]: Abilities.MINDS_EYE, + [Species.GENESECT]: Abilities.PROTEAN, + [Species.CHESPIN]: Abilities.DAUNTLESS_SHIELD, + [Species.FENNEKIN]: Abilities.PSYCHIC_SURGE, + [Species.FROAKIE]: Abilities.STAKEOUT, + [Species.BUNNELBY]: Abilities.GUTS, + [Species.FLETCHLING]: Abilities.MAGIC_GUARD, + [Species.SCATTERBUG]: Abilities.PRANKSTER, + [Species.LITLEO]: Abilities.BEAST_BOOST, + [Species.FLABEBE]: Abilities.GRASSY_SURGE, + [Species.SKIDDO]: Abilities.SEED_SOWER, + [Species.PANCHAM]: Abilities.FUR_COAT, + [Species.FURFROU]: Abilities.FLUFFY, + [Species.ESPURR]: Abilities.FUR_COAT, + [Species.HONEDGE]: Abilities.SHARPNESS, + [Species.SPRITZEE]: Abilities.FUR_COAT, + [Species.SWIRLIX]: Abilities.WELL_BAKED_BODY, + [Species.INKAY]: Abilities.UNNERVE, + [Species.BINACLE]: Abilities.SAP_SIPPER, + [Species.SKRELP]: Abilities.DRAGONS_MAW, + [Species.CLAUNCHER]: Abilities.SWIFT_SWIM, + [Species.HELIOPTILE]: Abilities.PROTEAN, + [Species.TYRUNT]: Abilities.RECKLESS, + [Species.AMAURA]: Abilities.ICE_SCALES, + [Species.HAWLUCHA]: Abilities.MOXIE, + [Species.DEDENNE]: Abilities.PIXILATE, + [Species.CARBINK]: Abilities.SOLID_ROCK, + [Species.GOOMY]: Abilities.REGENERATOR, + [Species.KLEFKI]: Abilities.LEVITATE, + [Species.PHANTUMP]: Abilities.SHADOW_TAG, + [Species.PUMPKABOO]: Abilities.WELL_BAKED_BODY, + [Species.BERGMITE]: Abilities.ICE_SCALES, + [Species.NOIBAT]: Abilities.PUNK_ROCK, + [Species.XERNEAS]: Abilities.HARVEST, + [Species.YVELTAL]: Abilities.SOUL_HEART, + [Species.ZYGARDE]: Abilities.HUGE_POWER, + [Species.DIANCIE]: Abilities.LEVITATE, + [Species.HOOPA]: Abilities.OPPORTUNIST, + [Species.VOLCANION]: Abilities.FILTER, + [Species.ROWLET]: Abilities.SNIPER, + [Species.LITTEN]: Abilities.OPPORTUNIST, + [Species.POPPLIO]: Abilities.PUNK_ROCK, + [Species.PIKIPEK]: Abilities.TECHNICIAN, + [Species.YUNGOOS]: Abilities.TOUGH_CLAWS, + [Species.GRUBBIN]: Abilities.SPEED_BOOST, + [Species.CRABRAWLER]: Abilities.WATER_BUBBLE, + [Species.ORICORIO]: Abilities.ADAPTABILITY, + [Species.CUTIEFLY]: Abilities.TINTED_LENS, + [Species.ROCKRUFF]: Abilities.ROCKY_PAYLOAD, + [Species.WISHIWASHI]: Abilities.REGENERATOR, + [Species.MAREANIE]: Abilities.TOXIC_DEBRIS, + [Species.MUDBRAY]: Abilities.CUD_CHEW, + [Species.DEWPIDER]: Abilities.TINTED_LENS, + [Species.FOMANTIS]: Abilities.SHARPNESS, + [Species.MORELULL]: Abilities.TRIAGE, + [Species.SALANDIT]: Abilities.DRAGONS_MAW, + [Species.STUFFUL]: Abilities.SCRAPPY, + [Species.BOUNSWEET]: Abilities.MOXIE, + [Species.COMFEY]: Abilities.FRIEND_GUARD, + [Species.ORANGURU]: Abilities.POWER_SPOT, + [Species.PASSIMIAN]: Abilities.LIBERO, + [Species.WIMPOD]: Abilities.REGENERATOR, + [Species.SANDYGAST]: Abilities.SAND_SPIT, + [Species.PYUKUMUKU]: Abilities.PURIFYING_SALT, + [Species.TYPE_NULL]: Abilities.ADAPTABILITY, + [Species.MINIOR]: Abilities.STURDY, + [Species.KOMALA]: Abilities.GUTS, + [Species.TURTONATOR]: Abilities.DAUNTLESS_SHIELD, + [Species.TOGEDEMARU]: Abilities.ROUGH_SKIN, + [Species.MIMIKYU]: Abilities.TOUGH_CLAWS, + [Species.BRUXISH]: Abilities.MULTISCALE, + [Species.DRAMPA]: Abilities.THICK_FAT, + [Species.DHELMISE]: Abilities.WATER_BUBBLE, + [Species.JANGMO_O]: Abilities.DAUNTLESS_SHIELD, + [Species.TAPU_KOKO]: Abilities.TRANSISTOR, + [Species.TAPU_LELE]: Abilities.SHEER_FORCE, + [Species.TAPU_BULU]: Abilities.TRIAGE, + [Species.TAPU_FINI]: Abilities.FAIRY_AURA, + [Species.COSMOG]: Abilities.BEAST_BOOST, + [Species.NIHILEGO]: Abilities.LEVITATE, + [Species.BUZZWOLE]: Abilities.MOXIE, + [Species.PHEROMOSA]: Abilities.TINTED_LENS, + [Species.XURKITREE]: Abilities.TRANSISTOR, + [Species.CELESTEELA]: Abilities.HEATPROOF, + [Species.KARTANA]: Abilities.SHARPNESS, + [Species.GUZZLORD]: Abilities.POISON_HEAL, + [Species.NECROZMA]: Abilities.BEAST_BOOST, + [Species.MAGEARNA]: Abilities.STEELY_SPIRIT, + [Species.MARSHADOW]: Abilities.IRON_FIST, + [Species.POIPOLE]: Abilities.SHEER_FORCE, + [Species.STAKATAKA]: Abilities.SOLID_ROCK, + [Species.BLACEPHALON]: Abilities.MAGIC_GUARD, + [Species.ZERAORA]: Abilities.TOUGH_CLAWS, + [Species.MELTAN]: Abilities.STEELY_SPIRIT, + [Species.GROOKEY]: Abilities.GRASS_PELT, + [Species.SCORBUNNY]: Abilities.NO_GUARD, + [Species.SOBBLE]: Abilities.SUPER_LUCK, + [Species.SKWOVET]: Abilities.HARVEST, + [Species.ROOKIDEE]: Abilities.IRON_BARBS, + [Species.BLIPBUG]: Abilities.PSYCHIC_SURGE, + [Species.NICKIT]: Abilities.MAGICIAN, + [Species.GOSSIFLEUR]: Abilities.GRASSY_SURGE, + [Species.WOOLOO]: Abilities.SIMPLE, + [Species.CHEWTLE]: Abilities.ROCKY_PAYLOAD, + [Species.YAMPER]: Abilities.SHEER_FORCE, + [Species.ROLYCOLY]: Abilities.SOLID_ROCK, + [Species.APPLIN]: Abilities.DRAGONS_MAW, + [Species.SILICOBRA]: Abilities.SAND_RUSH, + [Species.CRAMORANT]: Abilities.LIGHTNING_ROD, + [Species.ARROKUDA]: Abilities.INTIMIDATE, + [Species.TOXEL]: Abilities.ELECTRIC_SURGE, + [Species.SIZZLIPEDE]: Abilities.SPEED_BOOST, + [Species.CLOBBOPUS]: Abilities.WATER_BUBBLE, + [Species.SINISTEA]: Abilities.SHADOW_SHIELD, + [Species.HATENNA]: Abilities.FAIRY_AURA, + [Species.IMPIDIMP]: Abilities.FUR_COAT, + [Species.MILCERY]: Abilities.REGENERATOR, + [Species.FALINKS]: Abilities.PARENTAL_BOND, + [Species.PINCURCHIN]: Abilities.ELECTROMORPHOSIS, + [Species.SNOM]: Abilities.SNOW_WARNING, + [Species.STONJOURNER]: Abilities.STURDY, + [Species.EISCUE]: Abilities.ICE_SCALES, + [Species.INDEEDEE]: Abilities.FRIEND_GUARD, + [Species.MORPEKO]: Abilities.MOODY, + [Species.CUFANT]: Abilities.EARTH_EATER, + [Species.DRACOZOLT]: Abilities.NO_GUARD, + [Species.ARCTOZOLT]: Abilities.TRANSISTOR, + [Species.DRACOVISH]: Abilities.SWIFT_SWIM, + [Species.ARCTOVISH]: Abilities.STRONG_JAW, + [Species.DURALUDON]: Abilities.STEELWORKER, + [Species.DREEPY]: Abilities.PARENTAL_BOND, + [Species.ZACIAN]: Abilities.UNNERVE, + [Species.ZAMAZENTA]: Abilities.UNNERVE, + [Species.ETERNATUS]: Abilities.NEUTRALIZING_GAS, + [Species.KUBFU]: Abilities.IRON_FIST, + [Species.ZARUDE]: Abilities.TOUGH_CLAWS, + [Species.REGIELEKI]: Abilities.ELECTRIC_SURGE, + [Species.REGIDRAGO]: Abilities.MULTISCALE, + [Species.GLASTRIER]: Abilities.FILTER, + [Species.SPECTRIER]: Abilities.SHADOW_SHIELD, + [Species.CALYREX]: Abilities.HARVEST, + [Species.ENAMORUS]: Abilities.FAIRY_AURA, + [Species.SPRIGATITO]: Abilities.MAGICIAN, + [Species.FUECOCO]: Abilities.PUNK_ROCK, + [Species.QUAXLY]: Abilities.OPPORTUNIST, + [Species.LECHONK]: Abilities.SIMPLE, + [Species.TAROUNTULA]: Abilities.HONEY_GATHER, + [Species.NYMBLE]: Abilities.GUTS, + [Species.PAWMI]: Abilities.TRANSISTOR, + [Species.TANDEMAUS]: Abilities.SCRAPPY, + [Species.FIDOUGH]: Abilities.WATER_ABSORB, + [Species.SMOLIV]: Abilities.RIPEN, + [Species.SQUAWKABILLY]: Abilities.MOXIE, + [Species.NACLI]: Abilities.SOLID_ROCK, + [Species.CHARCADET]: Abilities.PRISM_ARMOR, + [Species.TADBULB]: Abilities.STAMINA, + [Species.WATTREL]: Abilities.SHEER_FORCE, + [Species.MASCHIFF]: Abilities.STRONG_JAW, + [Species.SHROODLE]: Abilities.CORROSION, + [Species.BRAMBLIN]: Abilities.SHADOW_SHIELD, + [Species.TOEDSCOOL]: Abilities.PRANKSTER, + [Species.KLAWF]: Abilities.WATER_ABSORB, + [Species.CAPSAKID]: Abilities.PARENTAL_BOND, + [Species.RELLOR]: Abilities.PRANKSTER, + [Species.FLITTLE]: Abilities.DAZZLING, + [Species.TINKATINK]: Abilities.STEELWORKER, + [Species.WIGLETT]: Abilities.STURDY, + [Species.BOMBIRDIER]: Abilities.UNBURDEN, + [Species.FINIZEN]: Abilities.IRON_FIST, + [Species.VAROOM]: Abilities.LEVITATE, + [Species.CYCLIZAR]: Abilities.PROTEAN, + [Species.ORTHWORM]: Abilities.REGENERATOR, + [Species.GLIMMET]: Abilities.LEVITATE, + [Species.GREAVARD]: Abilities.FUR_COAT, + [Species.FLAMIGO]: Abilities.MOXIE, + [Species.CETODDLE]: Abilities.ICE_SCALES, + [Species.VELUZA]: Abilities.SUPER_LUCK, + [Species.DONDOZO]: Abilities.PARENTAL_BOND, + [Species.TATSUGIRI]: Abilities.ADAPTABILITY, + [Species.GREAT_TUSK]: Abilities.INTIMIDATE, + [Species.SCREAM_TAIL]: Abilities.UNAWARE, + [Species.BRUTE_BONNET]: Abilities.CHLOROPHYLL, + [Species.FLUTTER_MANE]: Abilities.DAZZLING, + [Species.SLITHER_WING]: Abilities.SCRAPPY, + [Species.SANDY_SHOCKS]: Abilities.EARTH_EATER, + [Species.IRON_TREADS]: Abilities.STEELY_SPIRIT, + [Species.IRON_BUNDLE]: Abilities.SNOW_WARNING, + [Species.IRON_HANDS]: Abilities.IRON_FIST, + [Species.IRON_JUGULIS]: Abilities.LIGHTNING_ROD, + [Species.IRON_MOTH]: Abilities.LEVITATE, + [Species.IRON_THORNS]: Abilities.SAND_STREAM, + [Species.FRIGIBAX]: Abilities.SNOW_WARNING, + [Species.GIMMIGHOUL]: Abilities.HONEY_GATHER, + [Species.WO_CHIEN]: Abilities.VESSEL_OF_RUIN, + [Species.CHIEN_PAO]: Abilities.INTIMIDATE, + [Species.TING_LU]: Abilities.STAMINA, + [Species.CHI_YU]: Abilities.BERSERK, + [Species.ROARING_MOON]: Abilities.TOUGH_CLAWS, + [Species.IRON_VALIANT]: Abilities.ADAPTABILITY, + [Species.KORAIDON]: Abilities.OPPORTUNIST, + [Species.MIRAIDON]: Abilities.OPPORTUNIST, + [Species.WALKING_WAKE]: Abilities.BEAST_BOOST, + [Species.IRON_LEAVES]: Abilities.SHARPNESS, + [Species.POLTCHAGEIST]: Abilities.TRIAGE, + [Species.OKIDOGI]: Abilities.FUR_COAT, + [Species.MUNKIDORI]: Abilities.NEUROFORCE, + [Species.FEZANDIPITI]: Abilities.LEVITATE, + [Species.OGERPON]: Abilities.OPPORTUNIST, + [Species.GOUGING_FIRE]: Abilities.BEAST_BOOST, + [Species.RAGING_BOLT]: Abilities.BEAST_BOOST, + [Species.IRON_BOULDER]: Abilities.SHARPNESS, + [Species.IRON_CROWN]: Abilities.SHARPNESS, + [Species.TERAPAGOS]: Abilities.SOUL_HEART, + [Species.PECHARUNT]: Abilities.TOXIC_CHAIN, + [Species.ALOLA_RATTATA]: Abilities.ADAPTABILITY, + [Species.ALOLA_SANDSHREW]: Abilities.ICE_SCALES, + [Species.ALOLA_VULPIX]: Abilities.SHEER_FORCE, + [Species.ALOLA_DIGLETT]: Abilities.STURDY, + [Species.ALOLA_MEOWTH]: Abilities.DARK_AURA, + [Species.ALOLA_GEODUDE]: Abilities.DRY_SKIN, + [Species.ALOLA_GRIMER]: Abilities.TOXIC_DEBRIS, + [Species.ETERNAL_FLOETTE]: Abilities.MAGIC_GUARD, + [Species.GALAR_MEOWTH]: Abilities.STEELWORKER, + [Species.GALAR_PONYTA]: Abilities.MOXIE, + [Species.GALAR_SLOWPOKE]: Abilities.UNAWARE, + [Species.GALAR_FARFETCHD]: Abilities.INTREPID_SWORD, + [Species.GALAR_ARTICUNO]: Abilities.SERENE_GRACE, + [Species.GALAR_ZAPDOS]: Abilities.TOUGH_CLAWS, + [Species.GALAR_MOLTRES]: Abilities.DARK_AURA, + [Species.GALAR_CORSOLA]: Abilities.SHADOW_SHIELD, + [Species.GALAR_ZIGZAGOON]: Abilities.POISON_HEAL, + [Species.GALAR_DARUMAKA]: Abilities.FLASH_FIRE, + [Species.GALAR_YAMASK]: Abilities.TABLETS_OF_RUIN, + [Species.GALAR_STUNFISK]: Abilities.ARENA_TRAP, + [Species.HISUI_GROWLITHE]: Abilities.RECKLESS, + [Species.HISUI_VOLTORB]: Abilities.TRANSISTOR, + [Species.HISUI_QWILFISH]: Abilities.MERCILESS, + [Species.HISUI_SNEASEL]: Abilities.SCRAPPY, + [Species.HISUI_ZORUA]: Abilities.ADAPTABILITY, + [Species.PALDEA_TAUROS]: Abilities.ADAPTABILITY, + [Species.PALDEA_WOOPER]: Abilities.THICK_FAT, + [Species.BLOODMOON_URSALUNA]: Abilities.BERSERK +}; diff --git a/src/data/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts similarity index 99% rename from src/data/pokemon-evolutions.ts rename to src/data/balance/pokemon-evolutions.ts index fc1dfffb1ec..c838f6b2c49 100644 --- a/src/data/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -1,11 +1,11 @@ -import { Gender } from "./gender"; -import { PokeballType } from "./pokeball"; -import Pokemon from "../field/pokemon"; +import { Gender } from "#app/data/gender"; +import { PokeballType } from "#app/data/pokeball"; +import Pokemon from "#app/field/pokemon"; import { Stat } from "#enums/stat"; -import { Type } from "./type"; -import * as Utils from "../utils"; -import { WeatherType } from "./weather"; -import { Nature } from "./nature"; +import { Type } from "#app/data/type"; +import * as Utils from "#app/utils"; +import { WeatherType } from "#app/data/weather"; +import { Nature } from "#app/data/nature"; import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/data/pokemon-level-moves.ts b/src/data/balance/pokemon-level-moves.ts similarity index 100% rename from src/data/pokemon-level-moves.ts rename to src/data/balance/pokemon-level-moves.ts diff --git a/src/data/balance/rates.ts b/src/data/balance/rates.ts new file mode 100644 index 00000000000..c5eaf40e608 --- /dev/null +++ b/src/data/balance/rates.ts @@ -0,0 +1,53 @@ +/** + * Rates for shinies and other random properties are defined in this file. + * CHANCE is defined as x/65536 + * RATE is defined as 1/x + */ + +// #region Encounterable properties +/** `64/65536 -> 1/1024` */ +export const BASE_SHINY_CHANCE = 64; + +/** `256/65536 -> 1/256` */ +export const BASE_HIDDEN_ABILITY_CHANCE = 256; + +// #region Egg properties + +// Threshold x at which a gacha egg is determined to be a certain tier +// Specifically, the tier is determined by the highest threshold a random value between 0-255 meets or exceeds +// Legendary Up Gacha raises these thresholds by 1, thereby giving Legendary eggs 2/256 chance +export const GACHA_DEFAULT_COMMON_EGG_THRESHOLD = 52; // Default 204/256 chance, 203/256 chance in Legendary Up Gacha +export const GACHA_DEFAULT_RARE_EGG_THRESHOLD = 8; // Default 44/256 chance +export const GACHA_DEFAULT_EPIC_EGG_THRESHOLD = 1; // Default 7/256 chance, leaving Legendary as 1/256 chance +export const GACHA_LEGENDARY_UP_THRESHOLD_OFFSET = 1; // The offset to threshold for Legendary Up gacha eggs. +x/256 Legendary Egg chance, -x/256 Common Egg chance + +// The number of eggs without finding a certain tier egg it takes for egg pity to kick in and that tier to be forced +// These numbers are roughly the 80% mark. That is, 80% of the time you'll get an egg before this gets triggered. +export const EGG_PITY_LEGENDARY_THRESHOLD = 412; +export const EGG_PITY_EPIC_THRESHOLD = 59; +export const EGG_PITY_RARE_THRESHOLD = 9; + +// Waves to hatch an egg of a given tier +export const HATCH_WAVES_COMMON_EGG = 10; +export const HATCH_WAVES_RARE_EGG = 25; +export const HATCH_WAVES_EPIC_EGG = 50; +export const HATCH_WAVES_LEGENDARY_EGG = 100; +export const HATCH_WAVES_MANAPHY_EGG = 50; + +// Rates for specific random properties in 1/x +export const GACHA_DEFAULT_SHINY_RATE = 128; +export const GACHA_SHINY_UP_SHINY_RATE = 64; +export const SAME_SPECIES_EGG_SHINY_RATE = 12; +export const SAME_SPECIES_EGG_HA_RATE = 8; +export const MANAPHY_EGG_MANAPHY_RATE = 8; +export const GACHA_EGG_HA_RATE = 192; + +// 1/x for legendary eggs, 1/x*2 for epic eggs, 1/x*4 for rare eggs, and 1/x*8 for common eggs +export const GACHA_DEFAULT_RARE_EGGMOVE_RATE = 6; +export const SAME_SPECIES_EGG_RARE_EGGMOVE_RATE = 3; +export const GACHA_MOVE_UP_RARE_EGGMOVE_RATE = 3; + +// #region Variant properties +// The chance x/10 of a shiny being a variant, then of being specifically an epic variant +export const SHINY_VARIANT_CHANCE = 4; +export const SHINY_EPIC_CHANCE = 1; diff --git a/src/data/balance/starters.ts b/src/data/balance/starters.ts new file mode 100644 index 00000000000..5117f800086 --- /dev/null +++ b/src/data/balance/starters.ts @@ -0,0 +1,671 @@ +import { Species } from "#enums/species"; + +export const POKERUS_STARTER_COUNT = 5; + +/** + * Function to get the cumulative friendship threshold at which a candy is earned + * @param starterCost The cost of the starter, found in {@linkcode speciesStarterCosts} + * @returns aforementioned threshold + */ +export function getStarterValueFriendshipCap(starterCost: number): number { + switch (starterCost) { + case 1: + return 20; + case 2: + return 40; + case 3: + return 60; + case 4: + return 100; + case 5: + return 140; + case 6: + return 200; + case 7: + return 280; + case 8: + case 9: + return 450; + default: + return 600; + } +} + +export const speciesStarterCosts = { + [Species.BULBASAUR]: 3, + [Species.CHARMANDER]: 3, + [Species.SQUIRTLE]: 3, + [Species.CATERPIE]: 2, + [Species.WEEDLE]: 1, + [Species.PIDGEY]: 1, + [Species.RATTATA]: 1, + [Species.SPEAROW]: 1, + [Species.EKANS]: 2, + [Species.PIKACHU]: 3, + [Species.SANDSHREW]: 2, + [Species.NIDORAN_F]: 3, + [Species.NIDORAN_M]: 3, + [Species.CLEFAIRY]: 3, + [Species.VULPIX]: 3, + [Species.JIGGLYPUFF]: 2, + [Species.ZUBAT]: 3, + [Species.ODDISH]: 3, + [Species.PARAS]: 2, + [Species.VENONAT]: 2, + [Species.DIGLETT]: 2, + [Species.MEOWTH]: 3, + [Species.PSYDUCK]: 2, + [Species.MANKEY]: 4, + [Species.GROWLITHE]: 4, + [Species.POLIWAG]: 2, + [Species.ABRA]: 4, + [Species.MACHOP]: 3, + [Species.BELLSPROUT]: 2, + [Species.TENTACOOL]: 3, + [Species.GEODUDE]: 3, + [Species.PONYTA]: 2, + [Species.SLOWPOKE]: 3, + [Species.MAGNEMITE]: 4, + [Species.FARFETCHD]: 2, + [Species.DODUO]: 3, + [Species.SEEL]: 1, + [Species.GRIMER]: 2, + [Species.SHELLDER]: 5, + [Species.GASTLY]: 4, + [Species.ONIX]: 3, + [Species.DROWZEE]: 2, + [Species.KRABBY]: 3, + [Species.VOLTORB]: 2, + [Species.EXEGGCUTE]: 3, + [Species.CUBONE]: 3, + [Species.HITMONLEE]: 4, + [Species.HITMONCHAN]: 4, + [Species.LICKITUNG]: 3, + [Species.KOFFING]: 2, + [Species.RHYHORN]: 3, + [Species.CHANSEY]: 3, + [Species.TANGELA]: 3, + [Species.KANGASKHAN]: 4, + [Species.HORSEA]: 3, + [Species.GOLDEEN]: 2, + [Species.STARYU]: 3, + [Species.MR_MIME]: 3, + [Species.SCYTHER]: 5, + [Species.JYNX]: 4, + [Species.ELECTABUZZ]: 4, + [Species.MAGMAR]: 4, + [Species.PINSIR]: 4, + [Species.TAUROS]: 4, + [Species.MAGIKARP]: 4, + [Species.LAPRAS]: 4, + [Species.DITTO]: 2, + [Species.EEVEE]: 3, + [Species.PORYGON]: 4, + [Species.OMANYTE]: 3, + [Species.KABUTO]: 3, + [Species.AERODACTYL]: 5, + [Species.SNORLAX]: 5, + [Species.ARTICUNO]: 6, + [Species.ZAPDOS]: 6, + [Species.MOLTRES]: 6, + [Species.DRATINI]: 4, + [Species.MEWTWO]: 8, + [Species.MEW]: 6, + + [Species.CHIKORITA]: 2, + [Species.CYNDAQUIL]: 3, + [Species.TOTODILE]: 3, + [Species.SENTRET]: 1, + [Species.HOOTHOOT]: 2, + [Species.LEDYBA]: 1, + [Species.SPINARAK]: 1, + [Species.CHINCHOU]: 2, + [Species.PICHU]: 2, + [Species.CLEFFA]: 2, + [Species.IGGLYBUFF]: 1, + [Species.TOGEPI]: 3, + [Species.NATU]: 2, + [Species.MAREEP]: 2, + [Species.MARILL]: 4, + [Species.SUDOWOODO]: 3, + [Species.HOPPIP]: 2, + [Species.AIPOM]: 2, + [Species.SUNKERN]: 1, + [Species.YANMA]: 3, + [Species.WOOPER]: 2, + [Species.MURKROW]: 3, + [Species.MISDREAVUS]: 2, + [Species.UNOWN]: 1, + [Species.WOBBUFFET]: 2, + [Species.GIRAFARIG]: 3, + [Species.PINECO]: 2, + [Species.DUNSPARCE]: 3, + [Species.GLIGAR]: 3, + [Species.SNUBBULL]: 2, + [Species.QWILFISH]: 3, + [Species.SHUCKLE]: 3, + [Species.HERACROSS]: 5, + [Species.SNEASEL]: 4, + [Species.TEDDIURSA]: 4, + [Species.SLUGMA]: 2, + [Species.SWINUB]: 3, + [Species.CORSOLA]: 2, + [Species.REMORAID]: 2, + [Species.DELIBIRD]: 2, + [Species.MANTINE]: 3, + [Species.SKARMORY]: 4, + [Species.HOUNDOUR]: 3, + [Species.PHANPY]: 3, + [Species.STANTLER]: 3, + [Species.SMEARGLE]: 1, + [Species.TYROGUE]: 3, + [Species.SMOOCHUM]: 3, + [Species.ELEKID]: 3, + [Species.MAGBY]: 3, + [Species.MILTANK]: 4, + [Species.RAIKOU]: 6, + [Species.ENTEI]: 6, + [Species.SUICUNE]: 6, + [Species.LARVITAR]: 4, + [Species.LUGIA]: 8, + [Species.HO_OH]: 8, + [Species.CELEBI]: 6, + + [Species.TREECKO]: 3, + [Species.TORCHIC]: 4, + [Species.MUDKIP]: 3, + [Species.POOCHYENA]: 2, + [Species.ZIGZAGOON]: 2, + [Species.WURMPLE]: 1, + [Species.LOTAD]: 3, + [Species.SEEDOT]: 2, + [Species.TAILLOW]: 3, + [Species.WINGULL]: 2, + [Species.RALTS]: 3, + [Species.SURSKIT]: 2, + [Species.SHROOMISH]: 3, + [Species.SLAKOTH]: 4, + [Species.NINCADA]: 4, + [Species.WHISMUR]: 2, + [Species.MAKUHITA]: 3, + [Species.AZURILL]: 4, + [Species.NOSEPASS]: 2, + [Species.SKITTY]: 1, + [Species.SABLEYE]: 2, + [Species.MAWILE]: 3, + [Species.ARON]: 3, + [Species.MEDITITE]: 3, + [Species.ELECTRIKE]: 2, + [Species.PLUSLE]: 2, + [Species.MINUN]: 2, + [Species.VOLBEAT]: 2, + [Species.ILLUMISE]: 2, + [Species.ROSELIA]: 3, + [Species.GULPIN]: 1, + [Species.CARVANHA]: 3, + [Species.WAILMER]: 2, + [Species.NUMEL]: 2, + [Species.TORKOAL]: 3, + [Species.SPOINK]: 2, + [Species.SPINDA]: 1, + [Species.TRAPINCH]: 3, + [Species.CACNEA]: 2, + [Species.SWABLU]: 2, + [Species.ZANGOOSE]: 4, + [Species.SEVIPER]: 3, + [Species.LUNATONE]: 3, + [Species.SOLROCK]: 3, + [Species.BARBOACH]: 2, + [Species.CORPHISH]: 3, + [Species.BALTOY]: 2, + [Species.LILEEP]: 3, + [Species.ANORITH]: 3, + [Species.FEEBAS]: 4, + [Species.CASTFORM]: 1, + [Species.KECLEON]: 2, + [Species.SHUPPET]: 2, + [Species.DUSKULL]: 3, + [Species.TROPIUS]: 3, + [Species.CHIMECHO]: 3, + [Species.ABSOL]: 4, + [Species.WYNAUT]: 2, + [Species.SNORUNT]: 2, + [Species.SPHEAL]: 2, + [Species.CLAMPERL]: 3, + [Species.RELICANTH]: 3, + [Species.LUVDISC]: 1, + [Species.BAGON]: 4, + [Species.BELDUM]: 4, + [Species.REGIROCK]: 6, + [Species.REGICE]: 6, + [Species.REGISTEEL]: 6, + [Species.LATIAS]: 7, + [Species.LATIOS]: 7, + [Species.KYOGRE]: 9, + [Species.GROUDON]: 9, + [Species.RAYQUAZA]: 9, + [Species.JIRACHI]: 7, + [Species.DEOXYS]: 7, + + [Species.TURTWIG]: 3, + [Species.CHIMCHAR]: 3, + [Species.PIPLUP]: 3, + [Species.STARLY]: 3, + [Species.BIDOOF]: 2, + [Species.KRICKETOT]: 1, + [Species.SHINX]: 2, + [Species.BUDEW]: 3, + [Species.CRANIDOS]: 3, + [Species.SHIELDON]: 3, + [Species.BURMY]: 2, + [Species.COMBEE]: 2, + [Species.PACHIRISU]: 2, + [Species.BUIZEL]: 2, + [Species.CHERUBI]: 1, + [Species.SHELLOS]: 3, + [Species.DRIFLOON]: 2, + [Species.BUNEARY]: 2, + [Species.GLAMEOW]: 2, + [Species.CHINGLING]: 2, + [Species.STUNKY]: 2, + [Species.BRONZOR]: 3, + [Species.BONSLY]: 2, + [Species.MIME_JR]: 2, + [Species.HAPPINY]: 2, + [Species.CHATOT]: 2, + [Species.SPIRITOMB]: 4, + [Species.GIBLE]: 4, + [Species.MUNCHLAX]: 4, + [Species.RIOLU]: 3, + [Species.HIPPOPOTAS]: 3, + [Species.SKORUPI]: 3, + [Species.CROAGUNK]: 2, + [Species.CARNIVINE]: 2, + [Species.FINNEON]: 1, + [Species.MANTYKE]: 2, + [Species.SNOVER]: 2, + [Species.ROTOM]: 5, + [Species.UXIE]: 6, + [Species.MESPRIT]: 6, + [Species.AZELF]: 6, + [Species.DIALGA]: 8, + [Species.PALKIA]: 8, + [Species.HEATRAN]: 6, + [Species.REGIGIGAS]: 7, + [Species.GIRATINA]: 8, + [Species.CRESSELIA]: 6, + [Species.PHIONE]: 4, + [Species.MANAPHY]: 7, + [Species.DARKRAI]: 7, + [Species.SHAYMIN]: 6, + [Species.ARCEUS]: 9, + + [Species.VICTINI]: 7, + [Species.SNIVY]: 3, + [Species.TEPIG]: 3, + [Species.OSHAWOTT]: 3, + [Species.PATRAT]: 1, + [Species.LILLIPUP]: 3, + [Species.PURRLOIN]: 2, + [Species.PANSAGE]: 2, + [Species.PANSEAR]: 2, + [Species.PANPOUR]: 2, + [Species.MUNNA]: 2, + [Species.PIDOVE]: 1, + [Species.BLITZLE]: 2, + [Species.ROGGENROLA]: 3, + [Species.WOOBAT]: 3, + [Species.DRILBUR]: 4, + [Species.AUDINO]: 3, + [Species.TIMBURR]: 4, + [Species.TYMPOLE]: 3, + [Species.THROH]: 4, + [Species.SAWK]: 4, + [Species.SEWADDLE]: 2, + [Species.VENIPEDE]: 3, + [Species.COTTONEE]: 3, + [Species.PETILIL]: 3, + [Species.BASCULIN]: 4, + [Species.SANDILE]: 4, + [Species.DARUMAKA]: 4, + [Species.MARACTUS]: 2, + [Species.DWEBBLE]: 2, + [Species.SCRAGGY]: 3, + [Species.SIGILYPH]: 4, + [Species.YAMASK]: 3, + [Species.TIRTOUGA]: 3, + [Species.ARCHEN]: 3, + [Species.TRUBBISH]: 2, + [Species.ZORUA]: 3, + [Species.MINCCINO]: 3, + [Species.GOTHITA]: 3, + [Species.SOLOSIS]: 3, + [Species.DUCKLETT]: 2, + [Species.VANILLITE]: 3, + [Species.DEERLING]: 2, + [Species.EMOLGA]: 2, + [Species.KARRABLAST]: 3, + [Species.FOONGUS]: 2, + [Species.FRILLISH]: 3, + [Species.ALOMOMOLA]: 4, + [Species.JOLTIK]: 3, + [Species.FERROSEED]: 3, + [Species.KLINK]: 3, + [Species.TYNAMO]: 2, + [Species.ELGYEM]: 2, + [Species.LITWICK]: 3, + [Species.AXEW]: 4, + [Species.CUBCHOO]: 2, + [Species.CRYOGONAL]: 4, + [Species.SHELMET]: 2, + [Species.STUNFISK]: 3, + [Species.MIENFOO]: 3, + [Species.DRUDDIGON]: 4, + [Species.GOLETT]: 3, + [Species.PAWNIARD]: 4, + [Species.BOUFFALANT]: 4, + [Species.RUFFLET]: 3, + [Species.VULLABY]: 3, + [Species.HEATMOR]: 3, + [Species.DURANT]: 4, + [Species.DEINO]: 4, + [Species.LARVESTA]: 4, + [Species.COBALION]: 6, + [Species.TERRAKION]: 6, + [Species.VIRIZION]: 6, + [Species.TORNADUS]: 7, + [Species.THUNDURUS]: 7, + [Species.RESHIRAM]: 8, + [Species.ZEKROM]: 8, + [Species.LANDORUS]: 7, + [Species.KYUREM]: 8, + [Species.KELDEO]: 6, + [Species.MELOETTA]: 7, + [Species.GENESECT]: 6, + + [Species.CHESPIN]: 3, + [Species.FENNEKIN]: 3, + [Species.FROAKIE]: 4, + [Species.BUNNELBY]: 3, + [Species.FLETCHLING]: 3, + [Species.SCATTERBUG]: 2, + [Species.LITLEO]: 2, + [Species.FLABEBE]: 3, + [Species.SKIDDO]: 2, + [Species.PANCHAM]: 3, + [Species.FURFROU]: 3, + [Species.ESPURR]: 2, + [Species.HONEDGE]: 4, + [Species.SPRITZEE]: 2, + [Species.SWIRLIX]: 3, + [Species.INKAY]: 3, + [Species.BINACLE]: 3, + [Species.SKRELP]: 2, + [Species.CLAUNCHER]: 3, + [Species.HELIOPTILE]: 3, + [Species.TYRUNT]: 3, + [Species.AMAURA]: 3, + [Species.HAWLUCHA]: 4, + [Species.DEDENNE]: 2, + [Species.CARBINK]: 2, + [Species.GOOMY]: 4, + [Species.KLEFKI]: 3, + [Species.PHANTUMP]: 2, + [Species.PUMPKABOO]: 2, + [Species.BERGMITE]: 3, + [Species.NOIBAT]: 3, + [Species.XERNEAS]: 8, + [Species.YVELTAL]: 8, + [Species.ZYGARDE]: 8, + [Species.DIANCIE]: 7, + [Species.HOOPA]: 7, + [Species.VOLCANION]: 6, + [Species.ETERNAL_FLOETTE]: 4, + + [Species.ROWLET]: 3, + [Species.LITTEN]: 3, + [Species.POPPLIO]: 4, + [Species.PIKIPEK]: 2, + [Species.YUNGOOS]: 2, + [Species.GRUBBIN]: 3, + [Species.CRABRAWLER]: 3, + [Species.ORICORIO]: 3, + [Species.CUTIEFLY]: 3, + [Species.ROCKRUFF]: 3, + [Species.WISHIWASHI]: 2, + [Species.MAREANIE]: 2, + [Species.MUDBRAY]: 3, + [Species.DEWPIDER]: 3, + [Species.FOMANTIS]: 2, + [Species.MORELULL]: 2, + [Species.SALANDIT]: 3, + [Species.STUFFUL]: 3, + [Species.BOUNSWEET]: 3, + [Species.COMFEY]: 4, + [Species.ORANGURU]: 4, + [Species.PASSIMIAN]: 4, + [Species.WIMPOD]: 3, + [Species.SANDYGAST]: 3, + [Species.PYUKUMUKU]: 2, + [Species.TYPE_NULL]: 5, + [Species.MINIOR]: 4, + [Species.KOMALA]: 3, + [Species.TURTONATOR]: 4, + [Species.TOGEDEMARU]: 3, + [Species.MIMIKYU]: 4, + [Species.BRUXISH]: 4, + [Species.DRAMPA]: 4, + [Species.DHELMISE]: 4, + [Species.JANGMO_O]: 4, + [Species.TAPU_KOKO]: 6, + [Species.TAPU_LELE]: 6, + [Species.TAPU_BULU]: 6, + [Species.TAPU_FINI]: 6, + [Species.COSMOG]: 7, + [Species.NIHILEGO]: 6, + [Species.BUZZWOLE]: 6, + [Species.PHEROMOSA]: 7, + [Species.XURKITREE]: 6, + [Species.CELESTEELA]: 6, + [Species.KARTANA]: 7, + [Species.GUZZLORD]: 6, + [Species.NECROZMA]: 8, + [Species.MAGEARNA]: 7, + [Species.MARSHADOW]: 7, + [Species.POIPOLE]: 7, + [Species.STAKATAKA]: 6, + [Species.BLACEPHALON]: 7, + [Species.ZERAORA]: 6, + [Species.MELTAN]: 6, + [Species.ALOLA_RATTATA]: 1, + [Species.ALOLA_SANDSHREW]: 2, + [Species.ALOLA_VULPIX]: 3, + [Species.ALOLA_DIGLETT]: 2, + [Species.ALOLA_MEOWTH]: 3, + [Species.ALOLA_GEODUDE]: 3, + [Species.ALOLA_GRIMER]: 3, + + [Species.GROOKEY]: 3, + [Species.SCORBUNNY]: 4, + [Species.SOBBLE]: 3, + [Species.SKWOVET]: 2, + [Species.ROOKIDEE]: 3, + [Species.BLIPBUG]: 2, + [Species.NICKIT]: 1, + [Species.GOSSIFLEUR]: 2, + [Species.WOOLOO]: 2, + [Species.CHEWTLE]: 3, + [Species.YAMPER]: 2, + [Species.ROLYCOLY]: 3, + [Species.APPLIN]: 3, + [Species.SILICOBRA]: 3, + [Species.CRAMORANT]: 3, + [Species.ARROKUDA]: 3, + [Species.TOXEL]: 3, + [Species.SIZZLIPEDE]: 3, + [Species.CLOBBOPUS]: 2, + [Species.SINISTEA]: 3, + [Species.HATENNA]: 3, + [Species.IMPIDIMP]: 3, + [Species.MILCERY]: 3, + [Species.FALINKS]: 4, + [Species.PINCURCHIN]: 3, + [Species.SNOM]: 3, + [Species.STONJOURNER]: 3, + [Species.EISCUE]: 3, + [Species.INDEEDEE]: 4, + [Species.MORPEKO]: 3, + [Species.CUFANT]: 3, + [Species.DRACOZOLT]: 5, + [Species.ARCTOZOLT]: 4, + [Species.DRACOVISH]: 5, + [Species.ARCTOVISH]: 4, + [Species.DURALUDON]: 5, + [Species.DREEPY]: 4, + [Species.ZACIAN]: 9, + [Species.ZAMAZENTA]: 8, + [Species.ETERNATUS]: 10, + [Species.KUBFU]: 6, + [Species.ZARUDE]: 6, + [Species.REGIELEKI]: 6, + [Species.REGIDRAGO]: 6, + [Species.GLASTRIER]: 6, + [Species.SPECTRIER]: 7, + [Species.CALYREX]: 8, + [Species.GALAR_MEOWTH]: 3, + [Species.GALAR_PONYTA]: 2, + [Species.GALAR_SLOWPOKE]: 3, + [Species.GALAR_FARFETCHD]: 3, + [Species.GALAR_CORSOLA]: 3, + [Species.GALAR_ZIGZAGOON]: 3, + [Species.GALAR_DARUMAKA]: 4, + [Species.GALAR_YAMASK]: 3, + [Species.GALAR_STUNFISK]: 2, + [Species.GALAR_MR_MIME]: 3, + [Species.GALAR_ARTICUNO]: 6, + [Species.GALAR_ZAPDOS]: 6, + [Species.GALAR_MOLTRES]: 6, + [Species.HISUI_GROWLITHE]: 4, + [Species.HISUI_VOLTORB]: 3, + [Species.HISUI_QWILFISH]: 4, + [Species.HISUI_SNEASEL]: 5, + [Species.HISUI_ZORUA]: 3, + [Species.ENAMORUS]: 7, + + [Species.SPRIGATITO]: 4, + [Species.FUECOCO]: 4, + [Species.QUAXLY]: 4, + [Species.LECHONK]: 2, + [Species.TAROUNTULA]: 1, + [Species.NYMBLE]: 3, + [Species.PAWMI]: 3, + [Species.TANDEMAUS]: 4, + [Species.FIDOUGH]: 2, + [Species.SMOLIV]: 3, + [Species.SQUAWKABILLY]: 2, + [Species.NACLI]: 4, + [Species.CHARCADET]: 4, + [Species.TADBULB]: 3, + [Species.WATTREL]: 3, + [Species.MASCHIFF]: 3, + [Species.SHROODLE]: 2, + [Species.BRAMBLIN]: 3, + [Species.TOEDSCOOL]: 3, + [Species.KLAWF]: 3, + [Species.CAPSAKID]: 3, + [Species.RELLOR]: 2, + [Species.FLITTLE]: 3, + [Species.TINKATINK]: 4, + [Species.WIGLETT]: 2, + [Species.BOMBIRDIER]: 3, + [Species.FINIZEN]: 3, + [Species.VAROOM]: 4, + [Species.CYCLIZAR]: 4, + [Species.ORTHWORM]: 4, + [Species.GLIMMET]: 4, + [Species.GREAVARD]: 3, + [Species.FLAMIGO]: 4, + [Species.CETODDLE]: 3, + [Species.VELUZA]: 4, + [Species.DONDOZO]: 4, + [Species.TATSUGIRI]: 4, + [Species.GREAT_TUSK]: 6, + [Species.SCREAM_TAIL]: 6, + [Species.BRUTE_BONNET]: 6, + [Species.FLUTTER_MANE]: 7, + [Species.SLITHER_WING]: 6, + [Species.SANDY_SHOCKS]: 6, + [Species.IRON_TREADS]: 6, + [Species.IRON_BUNDLE]: 6, + [Species.IRON_HANDS]: 6, + [Species.IRON_JUGULIS]: 6, + [Species.IRON_MOTH]: 6, + [Species.IRON_THORNS]: 6, + [Species.FRIGIBAX]: 4, + [Species.GIMMIGHOUL]: 4, + [Species.WO_CHIEN]: 6, + [Species.CHIEN_PAO]: 7, + [Species.TING_LU]: 6, + [Species.CHI_YU]: 7, + [Species.ROARING_MOON]: 6, + [Species.IRON_VALIANT]: 6, + [Species.KORAIDON]: 9, + [Species.MIRAIDON]: 9, + [Species.WALKING_WAKE]: 6, + [Species.IRON_LEAVES]: 6, + [Species.POLTCHAGEIST]: 4, + [Species.OKIDOGI]: 6, + [Species.MUNKIDORI]: 6, + [Species.FEZANDIPITI]: 6, + [Species.OGERPON]: 7, + [Species.GOUGING_FIRE]: 7, + [Species.RAGING_BOLT]: 6, + [Species.IRON_BOULDER]: 7, + [Species.IRON_CROWN]: 6, + [Species.TERAPAGOS]: 8, + [Species.PECHARUNT]: 6, + [Species.PALDEA_TAUROS]: 5, + [Species.PALDEA_WOOPER]: 3, + [Species.BLOODMOON_URSALUNA]: 6, +}; + +const starterCandyCosts: { passive: number; costReduction: [number, number]; egg: number; }[] = [ + { passive: 40, costReduction: [25, 60], egg: 30 }, // 1 Cost + { passive: 40, costReduction: [25, 60], egg: 30 }, // 2 Cost + { passive: 35, costReduction: [20, 50], egg: 25 }, // 3 Cost + { passive: 30, costReduction: [15, 40], egg: 20 }, // 4 Cost + { passive: 25, costReduction: [12, 35], egg: 18 }, // 5 Cost + { passive: 20, costReduction: [10, 30], egg: 15 }, // 6 Cost + { passive: 15, costReduction: [8, 20], egg: 12 }, // 7 Cost + { passive: 10, costReduction: [5, 15], egg: 10 }, // 8 Cost + { passive: 10, costReduction: [5, 15], egg: 10 }, // 9 Cost + { passive: 10, costReduction: [5, 15], egg: 10 }, // 10 Cost +]; + +/** + * Getter for {@linkcode starterCandyCosts} for passive unlock candy cost based on initial point cost + * @param starterCost the default point cost of the starter found in {@linkcode speciesStarterCosts} + * @returns the candy cost for passive unlock + */ +export function getPassiveCandyCount(starterCost: number): number { + return starterCandyCosts[starterCost - 1].passive; +} + +/** + * Getter for {@linkcode starterCandyCosts} for value reduction unlock candy cost based on initial point cost + * @param starterCost the default point cost of the starter found in {@linkcode speciesStarterCosts} + * @returns respective candy cost for the two cost reductions as an array 2 numbers + */ +export function getValueReductionCandyCounts(starterCost: number): [number, number] { + return starterCandyCosts[starterCost - 1].costReduction; +} + +/** + * Getter for {@linkcode starterCandyCosts} for egg purchase candy cost based on initial point cost + * @param starterCost the default point cost of the starter found in {@linkcode speciesStarterCosts} + * @returns the candy cost for the purchasable egg + */ +export function getSameSpeciesEggCandyCounts(starterCost: number): number { + return starterCandyCosts[starterCost - 1].egg; +} + diff --git a/src/data/tms.ts b/src/data/balance/tms.ts similarity index 99% rename from src/data/tms.ts rename to src/data/balance/tms.ts index 9dea6d74a86..e08b677c30c 100644 --- a/src/data/tms.ts +++ b/src/data/balance/tms.ts @@ -1,4 +1,4 @@ -import { ModifierTier } from "../modifier/modifier-tier"; +import { ModifierTier } from "#app/modifier/modifier-tier"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 4cdaef7b1b3..e3ee818cee9 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -1,20 +1,21 @@ -import * as Utils from "../utils"; +import * as Utils from "#app/utils"; import i18next from "i18next"; import { defaultStarterSpecies, DexAttrProps, GameData } from "#app/system/game-data"; -import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm, speciesStarters } from "./pokemon-species"; +import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { speciesStarterCosts } from "#app/data/balance/starters"; import Pokemon, { PokemonMove } from "#app/field/pokemon"; import { BattleType, FixedBattleConfig } from "#app/battle"; import Trainer, { TrainerVariant } from "#app/field/trainer"; import { GameMode } from "#app/game-mode"; -import { Type } from "./type"; +import { Type } from "#app/data/type"; import { Challenges } from "#enums/challenges"; import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; -import { Nature } from "./nature"; -import { Moves } from "#app/enums/moves"; -import { TypeColor, TypeShadow } from "#app/enums/color"; -import { pokemonEvolutions } from "./pokemon-evolutions"; -import { pokemonFormChanges } from "./pokemon-forms"; +import { Nature } from "#app/data/nature"; +import { Moves } from "#enums/moves"; +import { TypeColor, TypeShadow } from "#enums/color"; +import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; +import { pokemonFormChanges } from "#app/data/pokemon-forms"; /** A constant for the default max cost of the starting party before a run */ const DEFAULT_PARTY_MAX_COST = 10; @@ -637,7 +638,7 @@ export class FreshStartChallenge extends Challenge { applyStarterCost(species: Species, cost: Utils.NumberHolder): boolean { if (defaultStarterSpecies.includes(species)) { - cost.value = speciesStarters[species]; + cost.value = speciesStarterCosts[species]; return true; } return false; @@ -719,7 +720,7 @@ export class LowerStarterMaxCostChallenge extends Challenge { } applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder): boolean { - if (speciesStarters[pokemon.speciesId] > DEFAULT_PARTY_MAX_COST - this.value) { + if (speciesStarterCosts[pokemon.speciesId] > DEFAULT_PARTY_MAX_COST - this.value) { valid.value = false; return true; } diff --git a/src/data/daily-run.ts b/src/data/daily-run.ts index 370b13ea3a3..0decab63f4f 100644 --- a/src/data/daily-run.ts +++ b/src/data/daily-run.ts @@ -1,10 +1,11 @@ import { PartyMemberStrength } from "#enums/party-member-strength"; import { Species } from "#enums/species"; -import BattleScene from "../battle-scene"; -import { PlayerPokemon } from "../field/pokemon"; -import { Starter } from "../ui/starter-select-ui-handler"; -import * as Utils from "../utils"; -import PokemonSpecies, { PokemonSpeciesForm, getPokemonSpecies, getPokemonSpeciesForm, speciesStarters } from "./pokemon-species"; +import BattleScene from "#app/battle-scene"; +import { PlayerPokemon } from "#app/field/pokemon"; +import { Starter } from "#app/ui/starter-select-ui-handler"; +import * as Utils from "#app/utils"; +import PokemonSpecies, { PokemonSpeciesForm, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { speciesStarterCosts } from "#app/data/balance/starters"; export interface DailyRunConfig { seed: integer; @@ -46,9 +47,9 @@ export function getDailyRunStarters(scene: BattleScene, seed: string): Starter[] for (let c = 0; c < starterCosts.length; c++) { const cost = starterCosts[c]; - const costSpecies = Object.keys(speciesStarters) + const costSpecies = Object.keys(speciesStarterCosts) .map(s => parseInt(s) as Species) - .filter(s => speciesStarters[s] === cost); + .filter(s => speciesStarterCosts[s] === cost); const randPkmSpecies = getPokemonSpecies(Utils.randSeedItem(costSpecies)); const starterSpecies = getPokemonSpecies(randPkmSpecies.getTrainerSpeciesForLevel(startingLevel, true, PartyMemberStrength.STRONGER)); starters.push(getDailyRunStarter(scene, starterSpecies, startingLevel)); diff --git a/src/data/egg.ts b/src/data/egg.ts index b37240a2028..c83554f2a19 100644 --- a/src/data/egg.ts +++ b/src/data/egg.ts @@ -1,30 +1,19 @@ -import BattleScene from "../battle-scene"; -import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "./pokemon-species"; -import { VariantTier } from "../enums/variant-tier"; -import * as Utils from "../utils"; +import BattleScene from "#app/battle-scene"; +import PokemonSpecies, { 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 Overrides from "#app/overrides"; -import { pokemonPrevolutions } from "./pokemon-evolutions"; +import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { PlayerPokemon } from "#app/field/pokemon"; import i18next from "i18next"; import { EggTier } from "#enums/egg-type"; import { Species } from "#enums/species"; -import { EggSourceType } from "#app/enums/egg-source-types"; +import { EggSourceType } from "#enums/egg-source-types"; +import { MANAPHY_EGG_MANAPHY_RATE, SAME_SPECIES_EGG_HA_RATE, GACHA_EGG_HA_RATE, GACHA_DEFAULT_RARE_EGGMOVE_RATE, SAME_SPECIES_EGG_RARE_EGGMOVE_RATE, GACHA_MOVE_UP_RARE_EGGMOVE_RATE, GACHA_DEFAULT_SHINY_RATE, GACHA_SHINY_UP_SHINY_RATE, SAME_SPECIES_EGG_SHINY_RATE, EGG_PITY_LEGENDARY_THRESHOLD, EGG_PITY_EPIC_THRESHOLD, EGG_PITY_RARE_THRESHOLD, SHINY_VARIANT_CHANCE, SHINY_EPIC_CHANCE, GACHA_DEFAULT_COMMON_EGG_THRESHOLD, GACHA_DEFAULT_RARE_EGG_THRESHOLD, GACHA_DEFAULT_EPIC_EGG_THRESHOLD, GACHA_LEGENDARY_UP_THRESHOLD_OFFSET, HATCH_WAVES_MANAPHY_EGG, HATCH_WAVES_COMMON_EGG, HATCH_WAVES_RARE_EGG, HATCH_WAVES_EPIC_EGG, HATCH_WAVES_LEGENDARY_EGG } from "#app/data/balance/rates"; export const EGG_SEED = 1073741824; -// Rates for specific random properties in 1/x -const DEFAULT_SHINY_RATE = 128; -const GACHA_SHINY_UP_SHINY_RATE = 64; -const SAME_SPECIES_EGG_SHINY_RATE = 12; -const SAME_SPECIES_EGG_HA_RATE = 8; -const MANAPHY_EGG_MANAPHY_RATE = 8; -const GACHA_EGG_HA_RATE = 192; - -// 1/x for legendary eggs, 1/x*2 for epic eggs, 1/x*4 for rare eggs, and 1/x*8 for common eggs -const DEFAULT_RARE_EGGMOVE_RATE = 6; -const SAME_SPECIES_EGG_RARE_EGGMOVE_RATE = 3; -const GACHA_MOVE_UP_RARE_EGGMOVE_RATE = 3; - /** Egg options to override egg properties */ export interface IEggOptions { /** Id. Used to check if egg type will be manaphy (id % 204 === 0) */ @@ -323,7 +312,7 @@ export class Egg { //// private rollEggMoveIndex() { - let baseChance = DEFAULT_RARE_EGGMOVE_RATE; + let baseChance = GACHA_DEFAULT_RARE_EGGMOVE_RATE; switch (this._sourceType) { case EggSourceType.SAME_SPECIES_EGG: baseChance = SAME_SPECIES_EGG_RARE_EGGMOVE_RATE; @@ -341,24 +330,24 @@ export class Egg { private getEggTierDefaultHatchWaves(eggTier?: EggTier): number { if (this._species === Species.PHIONE || this._species === Species.MANAPHY) { - return 50; + return HATCH_WAVES_MANAPHY_EGG; } switch (eggTier ?? this._tier) { case EggTier.COMMON: - return 10; + return HATCH_WAVES_COMMON_EGG; case EggTier.GREAT: - return 25; + return HATCH_WAVES_RARE_EGG; case EggTier.ULTRA: - return 50; + return HATCH_WAVES_EPIC_EGG; } - return 100; + return HATCH_WAVES_LEGENDARY_EGG; } private rollEggTier(): EggTier { - const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? 1 : 0; + const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0; const tierValue = Utils.randInt(256); - return tierValue >= 52 + tierValueOffset ? EggTier.COMMON : tierValue >= 8 + tierValueOffset ? EggTier.GREAT : tierValue >= 1 + tierValueOffset ? EggTier.ULTRA : EggTier.MASTER; + return tierValue >= GACHA_DEFAULT_COMMON_EGG_THRESHOLD + tierValueOffset ? EggTier.COMMON : tierValue >= GACHA_DEFAULT_RARE_EGG_THRESHOLD + tierValueOffset ? EggTier.GREAT : tierValue >= GACHA_DEFAULT_EPIC_EGG_THRESHOLD + tierValueOffset ? EggTier.ULTRA : EggTier.MASTER; } private rollSpecies(scene: BattleScene): Species | null { @@ -409,8 +398,8 @@ export class Egg { const ignoredSpecies = [Species.PHIONE, Species.MANAPHY, Species.ETERNATUS]; - let speciesPool = Object.keys(speciesStarters) - .filter(s => speciesStarters[s] >= minStarterValue && speciesStarters[s] <= maxStarterValue) + let speciesPool = Object.keys(speciesStarterCosts) + .filter(s => speciesStarterCosts[s] >= minStarterValue && speciesStarterCosts[s] <= maxStarterValue) .map(s => parseInt(s) as Species) .filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1); @@ -441,7 +430,7 @@ export class Egg { let totalWeight = 0; const speciesWeights : number[] = []; for (const speciesId of speciesPool) { - let weight = Math.floor((((maxStarterValue - speciesStarters[speciesId]) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100); + let weight = Math.floor((((maxStarterValue - speciesStarterCosts[speciesId]) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100); const species = getPokemonSpecies(speciesId); if (species.isRegional()) { weight = Math.floor(weight / 2); @@ -475,7 +464,7 @@ export class Egg { * @returns True if the egg is shiny **/ private rollShiny(): boolean { - let shinyChance = DEFAULT_SHINY_RATE; + let shinyChance = GACHA_DEFAULT_SHINY_RATE; switch (this._sourceType) { case EggSourceType.GACHA_SHINY: shinyChance = GACHA_SHINY_UP_SHINY_RATE; @@ -498,9 +487,9 @@ export class Egg { } const rand = Utils.randSeedInt(10); - if (rand >= 4) { + if (rand >= SHINY_VARIANT_CHANCE) { return VariantTier.STANDARD; // 6/10 - } else if (rand >= 1) { + } else if (rand >= SHINY_EPIC_CHANCE) { return VariantTier.RARE; // 3/10 } else { return VariantTier.EPIC; // 1/10 @@ -508,16 +497,16 @@ export class Egg { } private checkForPityTierOverrides(scene: BattleScene): void { - const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? 1 : 0; + const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0; scene.gameData.eggPity[EggTier.GREAT] += 1; scene.gameData.eggPity[EggTier.ULTRA] += 1; scene.gameData.eggPity[EggTier.MASTER] += 1 + tierValueOffset; // These numbers are roughly the 80% mark. That is, 80% of the time you'll get an egg before this gets triggered. - if (scene.gameData.eggPity[EggTier.MASTER] >= 412 && this._tier === EggTier.COMMON) { + if (scene.gameData.eggPity[EggTier.MASTER] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) { this._tier = EggTier.MASTER; - } else if (scene.gameData.eggPity[EggTier.ULTRA] >= 59 && this._tier === EggTier.COMMON) { + } else if (scene.gameData.eggPity[EggTier.ULTRA] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) { this._tier = EggTier.ULTRA; - } else if (scene.gameData.eggPity[EggTier.GREAT] >= 9 && this._tier === EggTier.COMMON) { + } else if (scene.gameData.eggPity[EggTier.GREAT] >= EGG_PITY_RARE_THRESHOLD && this._tier === EggTier.COMMON) { this._tier = EggTier.GREAT; } scene.gameData.eggPity[this._tier] = 0; @@ -544,7 +533,7 @@ export class Egg { } private getEggTierFromSpeciesStarterValue(): EggTier { - const speciesStartValue = speciesStarters[this.species]; + const speciesStartValue = speciesStarterCosts[this.species]; if (speciesStartValue >= 1 && speciesStartValue <= 3) { return EggTier.COMMON; } @@ -567,7 +556,7 @@ export class Egg { } export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timestamp: number): Species { - const legendarySpecies = Object.entries(speciesStarters) + const legendarySpecies = Object.entries(speciesStarterCosts) .filter(s => s[1] >= 8 && s[1] <= 9) .map(s => parseInt(s[0])) .filter(s => getPokemonSpecies(s).isObtainable()); @@ -594,7 +583,7 @@ export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timesta * @returns The egg tier of a given pokemon species */ export function getEggTierForSpecies(pokemonSpecies :PokemonSpecies): EggTier { - const speciesBaseValue = speciesStarters[pokemonSpecies.getRootSpeciesId()]; + const speciesBaseValue = speciesStarterCosts[pokemonSpecies.getRootSpeciesId()]; if (speciesBaseValue <= 3) { return EggTier.COMMON; } else if (speciesBaseValue <= 5) { diff --git a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts index 1de83374db2..e445a8f481d 100644 --- a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts +++ b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts @@ -8,7 +8,8 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; -import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { speciesStarterCosts } from "#app/data/balance/starters"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; @@ -71,7 +72,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = const pokemon = getHighestStatTotalPlayerPokemon(scene, true, true); const baseSpecies = pokemon.getSpeciesForm().getRootSpeciesId(); - const starterValue: number = speciesStarters[baseSpecies] ?? 1; + const starterValue: number = speciesStarterCosts[baseSpecies] ?? 1; const multiplier = Math.max(MONEY_MAXIMUM_MULTIPLIER / 10 * starterValue, MONEY_MINIMUM_MULTIPLIER); const price = scene.getWaveMoneyAmount(multiplier); diff --git a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts index 24e777d8c88..e4c0fdc2d98 100644 --- a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts @@ -10,7 +10,8 @@ import { Biome } from "#enums/biome"; import { TrainerType } from "#enums/trainer-type"; import i18next from "i18next"; import { Species } from "#enums/species"; -import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { speciesStarterCosts } from "#app/data/balance/starters"; import { Nature } from "#enums/nature"; import { Moves } from "#enums/moves"; import { PlayerPokemon } from "#app/field/pokemon"; @@ -454,8 +455,8 @@ function calculateEggRewardsForPokemon(pokemon: PlayerPokemon): [number, number] const rootSpecies = pokemon.species.getRootSpeciesId(); let pointsFromStarterTier = 0; // 2 points for every 1 below 7 that the pokemon's starter tier is (max 12, min 0) - if (speciesStarters.hasOwnProperty(rootSpecies)) { - const starterTier = speciesStarters[rootSpecies]; + if (speciesStarterCosts.hasOwnProperty(rootSpecies)) { + const starterTier = speciesStarterCosts[rootSpecies]; pointsFromStarterTier = Math.min(Math.max(Math.floor(7 - starterTier) * 2, 0), 12); } diff --git a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts index 4e199493c24..5486c130a28 100644 --- a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts @@ -5,7 +5,8 @@ import BattleScene from "#app/battle-scene"; import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { catchPokemon, getRandomSpeciesByStarterTier, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; -import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { speciesStarterCosts } from "#app/data/balance/starters"; import { Species } from "#enums/species"; import { PokeballType } from "#app/data/pokeball"; import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; @@ -88,7 +89,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = isPokemon: true }); - const starterTier = speciesStarters[species.speciesId]; + const starterTier = speciesStarterCosts[species.speciesId]; // Prices decrease by starter tier less than 5, but only reduces cost by half at max let priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER * (Math.max(starterTier, 2.5) / 5); if (pokemon.shiny) { diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index 107e73c2fb7..ff993f339cb 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -1,7 +1,7 @@ import { Ability, allAbilities } from "#app/data/ability"; import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { getNatureName, Nature } from "#app/data/nature"; -import { speciesStarters } from "#app/data/pokemon-species"; +import { speciesStarterCosts } from "#app/data/balance/starters"; import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; import { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { AbilityAttr } from "#app/system/game-data"; @@ -326,7 +326,7 @@ export const TrainingSessionEncounter: MysteryEncounter = // Only update the fusion's dex data if the Pokemon is already caught in dex (ignore rentals) const rootFusionSpecies = playerPokemon.fusionSpecies?.getRootSpeciesId(); if (!isNullOrUndefined(rootFusionSpecies) - && speciesStarters.hasOwnProperty(rootFusionSpecies) + && speciesStarterCosts.hasOwnProperty(rootFusionSpecies) && !!scene.gameData.dexData[rootFusionSpecies].caughtAttr) { scene.gameData.starterData[rootFusionSpecies].abilityAttr |= playerPokemon.fusionAbilityIndex !== 1 || playerPokemon.fusionSpecies?.ability2 ? 1 << playerPokemon.fusionAbilityIndex diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index 5fa2a7066a5..6069e1abcfb 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -5,12 +5,12 @@ import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { TimeOfDay } from "#enums/time-of-day"; -import { Nature } from "../nature"; -import { EvolutionItem, pokemonEvolutions } from "../pokemon-evolutions"; -import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeItemTrigger } from "../pokemon-forms"; -import { StatusEffect } from "../status-effect"; -import { Type } from "../type"; -import { WeatherType } from "../weather"; +import { Nature } from "#app/data/nature"; +import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; +import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; +import { StatusEffect } from "#app/data/status-effect"; +import { Type } from "#app/data/type"; +import { WeatherType } from "#app/data/weather"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { AttackTypeBoosterModifier } from "#app/modifier/modifier"; import { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; diff --git a/src/data/mystery-encounters/mystery-encounters.ts b/src/data/mystery-encounters/mystery-encounters.ts index 0ce4a5c2506..14fd5d7d6d1 100644 --- a/src/data/mystery-encounters/mystery-encounters.ts +++ b/src/data/mystery-encounters/mystery-encounters.ts @@ -32,7 +32,7 @@ import { FunAndGamesEncounter } from "#app/data/mystery-encounters/encounters/fu import { UncommonBreedEncounter } from "#app/data/mystery-encounters/encounters/uncommon-breed-encounter"; import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encounters/global-trade-system-encounter"; import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter"; -import { getBiomeName } from "#app/data/biomes"; +import { getBiomeName } from "#app/data/balance/biomes"; /** * Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * ) / MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index ca02c8bdfd6..18adaa4f6f4 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -1,5 +1,5 @@ import Battle, { BattlerIndex, BattleType } from "#app/battle"; -import { biomeLinks, BiomePoolTier } from "#app/data/biomes"; +import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes"; import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; import { AVERAGE_ENCOUNTERS_PER_RUN_TARGET, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index fb67305c78d..fc459c78e37 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -12,7 +12,8 @@ import { Mode } from "#app/ui/ui"; import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler"; import { Species } from "#enums/species"; import { Type } from "#app/data/type"; -import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species"; +import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species"; +import { speciesStarterCosts } from "#app/data/balance/starters"; import { getEncounterText, queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getPokemonNameWithAffix } from "#app/messages"; import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; @@ -206,8 +207,8 @@ export function getRandomSpeciesByStarterTier(starterTiers: number | [number, nu let min = Array.isArray(starterTiers) ? starterTiers[0] : starterTiers; let max = Array.isArray(starterTiers) ? starterTiers[1] : starterTiers; - let filteredSpecies: [PokemonSpecies, number][] = Object.keys(speciesStarters) - .map(s => [parseInt(s) as Species, speciesStarters[s] as number]) + let filteredSpecies: [PokemonSpecies, number][] = Object.keys(speciesStarterCosts) + .map(s => [parseInt(s) as Species, speciesStarterCosts[s] as number]) .filter(s => { const pokemonSpecies = getPokemonSpecies(s[0]); return pokemonSpecies && (!excludedSpecies || !excludedSpecies.includes(s[0])) diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index ae1be63e4a0..b710c40e1d5 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -4,18 +4,19 @@ import { PartyMemberStrength } from "#enums/party-member-strength"; import { Species } from "#enums/species"; import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities"; import i18next from "i18next"; -import BattleScene, { AnySound } from "../battle-scene"; -import { GameMode } from "../game-mode"; -import { StarterMoveset } from "../system/game-data"; -import * as Utils from "../utils"; -import { uncatchableSpecies } from "./biomes"; -import { speciesEggMoves } from "./egg-moves"; -import { GrowthRate } from "./exp"; -import { EvolutionLevel, SpeciesWildEvolutionDelay, pokemonEvolutions, pokemonPrevolutions } from "./pokemon-evolutions"; -import { Type } from "./type"; -import { LevelMoves, pokemonFormLevelMoves, pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves, pokemonSpeciesLevelMoves } from "./pokemon-level-moves"; +import BattleScene, { AnySound } from "#app/battle-scene"; +import { GameMode } from "#app/game-mode"; +import { StarterMoveset } from "#app/system/game-data"; +import * as Utils from "#app/utils"; +import { uncatchableSpecies } from "#app/data/balance/biomes"; +import { speciesEggMoves } from "#app/data/balance/egg-moves"; +import { GrowthRate } from "#app/data/exp"; +import { EvolutionLevel, SpeciesWildEvolutionDelay, pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; +import { Type } from "#app/data/type"; +import { LevelMoves, pokemonFormLevelMoves, pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; import { Stat } from "#enums/stat"; -import { Variant, VariantSet, variantColorCache, variantData } from "./variant"; +import { Variant, VariantSet, variantColorCache, variantData } from "#app/data/variant"; +import { speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { SpeciesFormKey } from "#enums/species-form-key"; export enum Region { @@ -173,7 +174,7 @@ export abstract class PokemonSpeciesForm { */ getRootSpeciesId(forStarter: boolean = false): Species { let ret = this.speciesId; - while (pokemonPrevolutions.hasOwnProperty(ret) && (!forStarter || !speciesStarters.hasOwnProperty(ret))) { + while (pokemonPrevolutions.hasOwnProperty(ret) && (!forStarter || !speciesStarterCosts.hasOwnProperty(ret))) { ret = pokemonPrevolutions[ret]; } return ret; @@ -929,6 +930,40 @@ export class PokemonForm extends PokemonSpeciesForm { } } +export const noStarterFormKeys: string[] = [ + SpeciesFormKey.MEGA, + SpeciesFormKey.MEGA_X, + SpeciesFormKey.MEGA_Y, + SpeciesFormKey.PRIMAL, + SpeciesFormKey.ORIGIN, + SpeciesFormKey.THERIAN, + SpeciesFormKey.GIGANTAMAX, + SpeciesFormKey.GIGANTAMAX_RAPID, + SpeciesFormKey.GIGANTAMAX_SINGLE, + SpeciesFormKey.ETERNAMAX +].map(k => k.toString()); + +/** +* Method to get the daily list of starters with Pokerus. +* @param scene {@linkcode BattleScene} used as part of RNG +* @returns A list of starters with Pokerus +*/ +export function getPokerusStarters(scene: BattleScene): PokemonSpecies[] { + const pokerusStarters: PokemonSpecies[] = []; + const date = new Date(); + date.setUTCHours(0, 0, 0, 0); + scene.executeWithSeedOffset(() => { + while (pokerusStarters.length < POKERUS_STARTER_COUNT) { + const randomSpeciesId = parseInt(Utils.randSeedItem(Object.keys(speciesStarterCosts)), 10); + const species = getPokemonSpecies(randomSpeciesId); + if (!pokerusStarters.includes(species)) { + pokerusStarters.push(species); + } + } + }, 0, date.getTime().toString()); + return pokerusStarters; +} + export const allSpecies: PokemonSpecies[] = []; export function initSpecies() { @@ -2714,1235 +2749,6 @@ export function initSpecies() { ); } -export const speciesStarters = { - [Species.BULBASAUR]: 3, - [Species.CHARMANDER]: 3, - [Species.SQUIRTLE]: 3, - [Species.CATERPIE]: 2, - [Species.WEEDLE]: 1, - [Species.PIDGEY]: 1, - [Species.RATTATA]: 1, - [Species.SPEAROW]: 1, - [Species.EKANS]: 2, - [Species.PIKACHU]: 3, - [Species.SANDSHREW]: 2, - [Species.NIDORAN_F]: 3, - [Species.NIDORAN_M]: 3, - [Species.CLEFAIRY]: 3, - [Species.VULPIX]: 3, - [Species.JIGGLYPUFF]: 2, - [Species.ZUBAT]: 3, - [Species.ODDISH]: 3, - [Species.PARAS]: 2, - [Species.VENONAT]: 2, - [Species.DIGLETT]: 2, - [Species.MEOWTH]: 3, - [Species.PSYDUCK]: 2, - [Species.MANKEY]: 4, - [Species.GROWLITHE]: 4, - [Species.POLIWAG]: 2, - [Species.ABRA]: 4, - [Species.MACHOP]: 3, - [Species.BELLSPROUT]: 2, - [Species.TENTACOOL]: 3, - [Species.GEODUDE]: 3, - [Species.PONYTA]: 2, - [Species.SLOWPOKE]: 3, - [Species.MAGNEMITE]: 4, - [Species.FARFETCHD]: 2, - [Species.DODUO]: 3, - [Species.SEEL]: 1, - [Species.GRIMER]: 2, - [Species.SHELLDER]: 5, - [Species.GASTLY]: 4, - [Species.ONIX]: 3, - [Species.DROWZEE]: 2, - [Species.KRABBY]: 3, - [Species.VOLTORB]: 2, - [Species.EXEGGCUTE]: 3, - [Species.CUBONE]: 3, - [Species.HITMONLEE]: 4, - [Species.HITMONCHAN]: 4, - [Species.LICKITUNG]: 3, - [Species.KOFFING]: 2, - [Species.RHYHORN]: 3, - [Species.CHANSEY]: 3, - [Species.TANGELA]: 3, - [Species.KANGASKHAN]: 4, - [Species.HORSEA]: 3, - [Species.GOLDEEN]: 2, - [Species.STARYU]: 3, - [Species.MR_MIME]: 3, - [Species.SCYTHER]: 5, - [Species.JYNX]: 4, - [Species.ELECTABUZZ]: 4, - [Species.MAGMAR]: 4, - [Species.PINSIR]: 4, - [Species.TAUROS]: 4, - [Species.MAGIKARP]: 4, - [Species.LAPRAS]: 4, - [Species.DITTO]: 2, - [Species.EEVEE]: 3, - [Species.PORYGON]: 4, - [Species.OMANYTE]: 3, - [Species.KABUTO]: 3, - [Species.AERODACTYL]: 5, - [Species.SNORLAX]: 5, - [Species.ARTICUNO]: 6, - [Species.ZAPDOS]: 6, - [Species.MOLTRES]: 6, - [Species.DRATINI]: 4, - [Species.MEWTWO]: 8, - [Species.MEW]: 6, - - [Species.CHIKORITA]: 2, - [Species.CYNDAQUIL]: 3, - [Species.TOTODILE]: 3, - [Species.SENTRET]: 1, - [Species.HOOTHOOT]: 2, - [Species.LEDYBA]: 1, - [Species.SPINARAK]: 1, - [Species.CHINCHOU]: 2, - [Species.PICHU]: 2, - [Species.CLEFFA]: 2, - [Species.IGGLYBUFF]: 1, - [Species.TOGEPI]: 3, - [Species.NATU]: 2, - [Species.MAREEP]: 2, - [Species.MARILL]: 4, - [Species.SUDOWOODO]: 3, - [Species.HOPPIP]: 2, - [Species.AIPOM]: 2, - [Species.SUNKERN]: 1, - [Species.YANMA]: 3, - [Species.WOOPER]: 2, - [Species.MURKROW]: 3, - [Species.MISDREAVUS]: 2, - [Species.UNOWN]: 1, - [Species.WOBBUFFET]: 2, - [Species.GIRAFARIG]: 3, - [Species.PINECO]: 2, - [Species.DUNSPARCE]: 3, - [Species.GLIGAR]: 3, - [Species.SNUBBULL]: 2, - [Species.QWILFISH]: 3, - [Species.SHUCKLE]: 3, - [Species.HERACROSS]: 5, - [Species.SNEASEL]: 4, - [Species.TEDDIURSA]: 4, - [Species.SLUGMA]: 2, - [Species.SWINUB]: 3, - [Species.CORSOLA]: 2, - [Species.REMORAID]: 2, - [Species.DELIBIRD]: 2, - [Species.MANTINE]: 3, - [Species.SKARMORY]: 4, - [Species.HOUNDOUR]: 3, - [Species.PHANPY]: 3, - [Species.STANTLER]: 3, - [Species.SMEARGLE]: 1, - [Species.TYROGUE]: 3, - [Species.SMOOCHUM]: 3, - [Species.ELEKID]: 3, - [Species.MAGBY]: 3, - [Species.MILTANK]: 4, - [Species.RAIKOU]: 6, - [Species.ENTEI]: 6, - [Species.SUICUNE]: 6, - [Species.LARVITAR]: 4, - [Species.LUGIA]: 8, - [Species.HO_OH]: 8, - [Species.CELEBI]: 6, - - [Species.TREECKO]: 3, - [Species.TORCHIC]: 4, - [Species.MUDKIP]: 3, - [Species.POOCHYENA]: 2, - [Species.ZIGZAGOON]: 2, - [Species.WURMPLE]: 1, - [Species.LOTAD]: 3, - [Species.SEEDOT]: 2, - [Species.TAILLOW]: 3, - [Species.WINGULL]: 2, - [Species.RALTS]: 3, - [Species.SURSKIT]: 2, - [Species.SHROOMISH]: 3, - [Species.SLAKOTH]: 4, - [Species.NINCADA]: 4, - [Species.WHISMUR]: 2, - [Species.MAKUHITA]: 3, - [Species.AZURILL]: 4, - [Species.NOSEPASS]: 2, - [Species.SKITTY]: 1, - [Species.SABLEYE]: 2, - [Species.MAWILE]: 3, - [Species.ARON]: 3, - [Species.MEDITITE]: 3, - [Species.ELECTRIKE]: 2, - [Species.PLUSLE]: 2, - [Species.MINUN]: 2, - [Species.VOLBEAT]: 2, - [Species.ILLUMISE]: 2, - [Species.ROSELIA]: 3, - [Species.GULPIN]: 1, - [Species.CARVANHA]: 3, - [Species.WAILMER]: 2, - [Species.NUMEL]: 2, - [Species.TORKOAL]: 3, - [Species.SPOINK]: 2, - [Species.SPINDA]: 1, - [Species.TRAPINCH]: 3, - [Species.CACNEA]: 2, - [Species.SWABLU]: 2, - [Species.ZANGOOSE]: 4, - [Species.SEVIPER]: 3, - [Species.LUNATONE]: 3, - [Species.SOLROCK]: 3, - [Species.BARBOACH]: 2, - [Species.CORPHISH]: 3, - [Species.BALTOY]: 2, - [Species.LILEEP]: 3, - [Species.ANORITH]: 3, - [Species.FEEBAS]: 4, - [Species.CASTFORM]: 1, - [Species.KECLEON]: 2, - [Species.SHUPPET]: 2, - [Species.DUSKULL]: 3, - [Species.TROPIUS]: 3, - [Species.CHIMECHO]: 3, - [Species.ABSOL]: 4, - [Species.WYNAUT]: 2, - [Species.SNORUNT]: 2, - [Species.SPHEAL]: 2, - [Species.CLAMPERL]: 3, - [Species.RELICANTH]: 3, - [Species.LUVDISC]: 1, - [Species.BAGON]: 4, - [Species.BELDUM]: 4, - [Species.REGIROCK]: 6, - [Species.REGICE]: 6, - [Species.REGISTEEL]: 6, - [Species.LATIAS]: 7, - [Species.LATIOS]: 7, - [Species.KYOGRE]: 9, - [Species.GROUDON]: 9, - [Species.RAYQUAZA]: 9, - [Species.JIRACHI]: 7, - [Species.DEOXYS]: 7, - - [Species.TURTWIG]: 3, - [Species.CHIMCHAR]: 3, - [Species.PIPLUP]: 3, - [Species.STARLY]: 3, - [Species.BIDOOF]: 2, - [Species.KRICKETOT]: 1, - [Species.SHINX]: 2, - [Species.BUDEW]: 3, - [Species.CRANIDOS]: 3, - [Species.SHIELDON]: 3, - [Species.BURMY]: 2, - [Species.COMBEE]: 2, - [Species.PACHIRISU]: 2, - [Species.BUIZEL]: 2, - [Species.CHERUBI]: 1, - [Species.SHELLOS]: 3, - [Species.DRIFLOON]: 2, - [Species.BUNEARY]: 2, - [Species.GLAMEOW]: 2, - [Species.CHINGLING]: 2, - [Species.STUNKY]: 2, - [Species.BRONZOR]: 3, - [Species.BONSLY]: 2, - [Species.MIME_JR]: 2, - [Species.HAPPINY]: 2, - [Species.CHATOT]: 2, - [Species.SPIRITOMB]: 4, - [Species.GIBLE]: 4, - [Species.MUNCHLAX]: 4, - [Species.RIOLU]: 3, - [Species.HIPPOPOTAS]: 3, - [Species.SKORUPI]: 3, - [Species.CROAGUNK]: 2, - [Species.CARNIVINE]: 2, - [Species.FINNEON]: 1, - [Species.MANTYKE]: 2, - [Species.SNOVER]: 2, - [Species.ROTOM]: 5, - [Species.UXIE]: 6, - [Species.MESPRIT]: 6, - [Species.AZELF]: 6, - [Species.DIALGA]: 8, - [Species.PALKIA]: 8, - [Species.HEATRAN]: 6, - [Species.REGIGIGAS]: 7, - [Species.GIRATINA]: 8, - [Species.CRESSELIA]: 6, - [Species.PHIONE]: 4, - [Species.MANAPHY]: 7, - [Species.DARKRAI]: 7, - [Species.SHAYMIN]: 6, - [Species.ARCEUS]: 9, - - [Species.VICTINI]: 7, - [Species.SNIVY]: 3, - [Species.TEPIG]: 3, - [Species.OSHAWOTT]: 3, - [Species.PATRAT]: 1, - [Species.LILLIPUP]: 3, - [Species.PURRLOIN]: 2, - [Species.PANSAGE]: 2, - [Species.PANSEAR]: 2, - [Species.PANPOUR]: 2, - [Species.MUNNA]: 2, - [Species.PIDOVE]: 1, - [Species.BLITZLE]: 2, - [Species.ROGGENROLA]: 3, - [Species.WOOBAT]: 3, - [Species.DRILBUR]: 4, - [Species.AUDINO]: 3, - [Species.TIMBURR]: 4, - [Species.TYMPOLE]: 3, - [Species.THROH]: 4, - [Species.SAWK]: 4, - [Species.SEWADDLE]: 2, - [Species.VENIPEDE]: 3, - [Species.COTTONEE]: 3, - [Species.PETILIL]: 3, - [Species.BASCULIN]: 4, - [Species.SANDILE]: 4, - [Species.DARUMAKA]: 4, - [Species.MARACTUS]: 2, - [Species.DWEBBLE]: 2, - [Species.SCRAGGY]: 3, - [Species.SIGILYPH]: 4, - [Species.YAMASK]: 3, - [Species.TIRTOUGA]: 3, - [Species.ARCHEN]: 3, - [Species.TRUBBISH]: 2, - [Species.ZORUA]: 3, - [Species.MINCCINO]: 3, - [Species.GOTHITA]: 3, - [Species.SOLOSIS]: 3, - [Species.DUCKLETT]: 2, - [Species.VANILLITE]: 3, - [Species.DEERLING]: 2, - [Species.EMOLGA]: 2, - [Species.KARRABLAST]: 3, - [Species.FOONGUS]: 2, - [Species.FRILLISH]: 3, - [Species.ALOMOMOLA]: 4, - [Species.JOLTIK]: 3, - [Species.FERROSEED]: 3, - [Species.KLINK]: 3, - [Species.TYNAMO]: 2, - [Species.ELGYEM]: 2, - [Species.LITWICK]: 3, - [Species.AXEW]: 4, - [Species.CUBCHOO]: 2, - [Species.CRYOGONAL]: 4, - [Species.SHELMET]: 2, - [Species.STUNFISK]: 3, - [Species.MIENFOO]: 3, - [Species.DRUDDIGON]: 4, - [Species.GOLETT]: 3, - [Species.PAWNIARD]: 4, - [Species.BOUFFALANT]: 4, - [Species.RUFFLET]: 3, - [Species.VULLABY]: 3, - [Species.HEATMOR]: 3, - [Species.DURANT]: 4, - [Species.DEINO]: 4, - [Species.LARVESTA]: 4, - [Species.COBALION]: 6, - [Species.TERRAKION]: 6, - [Species.VIRIZION]: 6, - [Species.TORNADUS]: 7, - [Species.THUNDURUS]: 7, - [Species.RESHIRAM]: 8, - [Species.ZEKROM]: 8, - [Species.LANDORUS]: 7, - [Species.KYUREM]: 8, - [Species.KELDEO]: 6, - [Species.MELOETTA]: 7, - [Species.GENESECT]: 6, - - [Species.CHESPIN]: 3, - [Species.FENNEKIN]: 3, - [Species.FROAKIE]: 4, - [Species.BUNNELBY]: 3, - [Species.FLETCHLING]: 3, - [Species.SCATTERBUG]: 2, - [Species.LITLEO]: 2, - [Species.FLABEBE]: 3, - [Species.SKIDDO]: 2, - [Species.PANCHAM]: 3, - [Species.FURFROU]: 3, - [Species.ESPURR]: 2, - [Species.HONEDGE]: 4, - [Species.SPRITZEE]: 2, - [Species.SWIRLIX]: 3, - [Species.INKAY]: 3, - [Species.BINACLE]: 3, - [Species.SKRELP]: 2, - [Species.CLAUNCHER]: 3, - [Species.HELIOPTILE]: 3, - [Species.TYRUNT]: 3, - [Species.AMAURA]: 3, - [Species.HAWLUCHA]: 4, - [Species.DEDENNE]: 2, - [Species.CARBINK]: 2, - [Species.GOOMY]: 4, - [Species.KLEFKI]: 3, - [Species.PHANTUMP]: 2, - [Species.PUMPKABOO]: 2, - [Species.BERGMITE]: 3, - [Species.NOIBAT]: 3, - [Species.XERNEAS]: 8, - [Species.YVELTAL]: 8, - [Species.ZYGARDE]: 8, - [Species.DIANCIE]: 7, - [Species.HOOPA]: 7, - [Species.VOLCANION]: 6, - [Species.ETERNAL_FLOETTE]: 4, - - [Species.ROWLET]: 3, - [Species.LITTEN]: 3, - [Species.POPPLIO]: 4, - [Species.PIKIPEK]: 2, - [Species.YUNGOOS]: 2, - [Species.GRUBBIN]: 3, - [Species.CRABRAWLER]: 3, - [Species.ORICORIO]: 3, - [Species.CUTIEFLY]: 3, - [Species.ROCKRUFF]: 3, - [Species.WISHIWASHI]: 2, - [Species.MAREANIE]: 2, - [Species.MUDBRAY]: 3, - [Species.DEWPIDER]: 3, - [Species.FOMANTIS]: 2, - [Species.MORELULL]: 2, - [Species.SALANDIT]: 3, - [Species.STUFFUL]: 3, - [Species.BOUNSWEET]: 3, - [Species.COMFEY]: 4, - [Species.ORANGURU]: 4, - [Species.PASSIMIAN]: 4, - [Species.WIMPOD]: 3, - [Species.SANDYGAST]: 3, - [Species.PYUKUMUKU]: 2, - [Species.TYPE_NULL]: 5, - [Species.MINIOR]: 4, - [Species.KOMALA]: 3, - [Species.TURTONATOR]: 4, - [Species.TOGEDEMARU]: 3, - [Species.MIMIKYU]: 4, - [Species.BRUXISH]: 4, - [Species.DRAMPA]: 4, - [Species.DHELMISE]: 4, - [Species.JANGMO_O]: 4, - [Species.TAPU_KOKO]: 6, - [Species.TAPU_LELE]: 6, - [Species.TAPU_BULU]: 6, - [Species.TAPU_FINI]: 6, - [Species.COSMOG]: 7, - [Species.NIHILEGO]: 6, - [Species.BUZZWOLE]: 6, - [Species.PHEROMOSA]: 7, - [Species.XURKITREE]: 6, - [Species.CELESTEELA]: 6, - [Species.KARTANA]: 7, - [Species.GUZZLORD]: 6, - [Species.NECROZMA]: 8, - [Species.MAGEARNA]: 7, - [Species.MARSHADOW]: 7, - [Species.POIPOLE]: 7, - [Species.STAKATAKA]: 6, - [Species.BLACEPHALON]: 7, - [Species.ZERAORA]: 6, - [Species.MELTAN]: 6, - [Species.ALOLA_RATTATA]: 1, - [Species.ALOLA_SANDSHREW]: 2, - [Species.ALOLA_VULPIX]: 3, - [Species.ALOLA_DIGLETT]: 2, - [Species.ALOLA_MEOWTH]: 3, - [Species.ALOLA_GEODUDE]: 3, - [Species.ALOLA_GRIMER]: 3, - - [Species.GROOKEY]: 3, - [Species.SCORBUNNY]: 4, - [Species.SOBBLE]: 3, - [Species.SKWOVET]: 2, - [Species.ROOKIDEE]: 3, - [Species.BLIPBUG]: 2, - [Species.NICKIT]: 1, - [Species.GOSSIFLEUR]: 2, - [Species.WOOLOO]: 2, - [Species.CHEWTLE]: 3, - [Species.YAMPER]: 2, - [Species.ROLYCOLY]: 3, - [Species.APPLIN]: 3, - [Species.SILICOBRA]: 3, - [Species.CRAMORANT]: 3, - [Species.ARROKUDA]: 3, - [Species.TOXEL]: 3, - [Species.SIZZLIPEDE]: 3, - [Species.CLOBBOPUS]: 2, - [Species.SINISTEA]: 3, - [Species.HATENNA]: 3, - [Species.IMPIDIMP]: 3, - [Species.MILCERY]: 3, - [Species.FALINKS]: 4, - [Species.PINCURCHIN]: 3, - [Species.SNOM]: 3, - [Species.STONJOURNER]: 3, - [Species.EISCUE]: 3, - [Species.INDEEDEE]: 4, - [Species.MORPEKO]: 3, - [Species.CUFANT]: 3, - [Species.DRACOZOLT]: 5, - [Species.ARCTOZOLT]: 4, - [Species.DRACOVISH]: 5, - [Species.ARCTOVISH]: 4, - [Species.DURALUDON]: 5, - [Species.DREEPY]: 4, - [Species.ZACIAN]: 9, - [Species.ZAMAZENTA]: 8, - [Species.ETERNATUS]: 10, - [Species.KUBFU]: 6, - [Species.ZARUDE]: 6, - [Species.REGIELEKI]: 6, - [Species.REGIDRAGO]: 6, - [Species.GLASTRIER]: 6, - [Species.SPECTRIER]: 7, - [Species.CALYREX]: 8, - [Species.GALAR_MEOWTH]: 3, - [Species.GALAR_PONYTA]: 2, - [Species.GALAR_SLOWPOKE]: 3, - [Species.GALAR_FARFETCHD]: 3, - [Species.GALAR_CORSOLA]: 3, - [Species.GALAR_ZIGZAGOON]: 3, - [Species.GALAR_DARUMAKA]: 4, - [Species.GALAR_YAMASK]: 3, - [Species.GALAR_STUNFISK]: 2, - [Species.GALAR_MR_MIME]: 3, - [Species.GALAR_ARTICUNO]: 6, - [Species.GALAR_ZAPDOS]: 6, - [Species.GALAR_MOLTRES]: 6, - [Species.HISUI_GROWLITHE]: 4, - [Species.HISUI_VOLTORB]: 3, - [Species.HISUI_QWILFISH]: 4, - [Species.HISUI_SNEASEL]: 5, - [Species.HISUI_ZORUA]: 3, - [Species.ENAMORUS]: 7, - - [Species.SPRIGATITO]: 4, - [Species.FUECOCO]: 4, - [Species.QUAXLY]: 4, - [Species.LECHONK]: 2, - [Species.TAROUNTULA]: 1, - [Species.NYMBLE]: 3, - [Species.PAWMI]: 3, - [Species.TANDEMAUS]: 4, - [Species.FIDOUGH]: 2, - [Species.SMOLIV]: 3, - [Species.SQUAWKABILLY]: 2, - [Species.NACLI]: 4, - [Species.CHARCADET]: 4, - [Species.TADBULB]: 3, - [Species.WATTREL]: 3, - [Species.MASCHIFF]: 3, - [Species.SHROODLE]: 2, - [Species.BRAMBLIN]: 3, - [Species.TOEDSCOOL]: 3, - [Species.KLAWF]: 3, - [Species.CAPSAKID]: 3, - [Species.RELLOR]: 2, - [Species.FLITTLE]: 3, - [Species.TINKATINK]: 4, - [Species.WIGLETT]: 2, - [Species.BOMBIRDIER]: 3, - [Species.FINIZEN]: 3, - [Species.VAROOM]: 4, - [Species.CYCLIZAR]: 4, - [Species.ORTHWORM]: 4, - [Species.GLIMMET]: 4, - [Species.GREAVARD]: 3, - [Species.FLAMIGO]: 4, - [Species.CETODDLE]: 3, - [Species.VELUZA]: 4, - [Species.DONDOZO]: 4, - [Species.TATSUGIRI]: 4, - [Species.GREAT_TUSK]: 6, - [Species.SCREAM_TAIL]: 6, - [Species.BRUTE_BONNET]: 6, - [Species.FLUTTER_MANE]: 7, - [Species.SLITHER_WING]: 6, - [Species.SANDY_SHOCKS]: 6, - [Species.IRON_TREADS]: 6, - [Species.IRON_BUNDLE]: 6, - [Species.IRON_HANDS]: 6, - [Species.IRON_JUGULIS]: 6, - [Species.IRON_MOTH]: 6, - [Species.IRON_THORNS]: 6, - [Species.FRIGIBAX]: 4, - [Species.GIMMIGHOUL]: 4, - [Species.WO_CHIEN]: 6, - [Species.CHIEN_PAO]: 7, - [Species.TING_LU]: 6, - [Species.CHI_YU]: 7, - [Species.ROARING_MOON]: 6, - [Species.IRON_VALIANT]: 6, - [Species.KORAIDON]: 9, - [Species.MIRAIDON]: 9, - [Species.WALKING_WAKE]: 6, - [Species.IRON_LEAVES]: 6, - [Species.POLTCHAGEIST]: 4, - [Species.OKIDOGI]: 6, - [Species.MUNKIDORI]: 6, - [Species.FEZANDIPITI]: 6, - [Species.OGERPON]: 7, - [Species.GOUGING_FIRE]: 7, - [Species.RAGING_BOLT]: 6, - [Species.IRON_BOULDER]: 7, - [Species.IRON_CROWN]: 6, - [Species.TERAPAGOS]: 8, - [Species.PECHARUNT]: 6, - [Species.PALDEA_TAUROS]: 5, - [Species.PALDEA_WOOPER]: 3, - [Species.BLOODMOON_URSALUNA]: 6, -}; - -export const noStarterFormKeys: string[] = [ - SpeciesFormKey.MEGA, - SpeciesFormKey.MEGA_X, - SpeciesFormKey.MEGA_Y, - SpeciesFormKey.PRIMAL, - SpeciesFormKey.ORIGIN, - SpeciesFormKey.THERIAN, - SpeciesFormKey.GIGANTAMAX, - SpeciesFormKey.GIGANTAMAX_RAPID, - SpeciesFormKey.GIGANTAMAX_SINGLE, - SpeciesFormKey.ETERNAMAX -].map(k => k.toString()); - -export function getStarterValueFriendshipCap(value: integer): integer { - switch (value) { - case 1: - return 20; - case 2: - return 40; - case 3: - return 60; - case 4: - return 100; - case 5: - return 140; - case 6: - return 200; - case 7: - return 280; - case 8: - case 9: - return 450; - default: - return 600; - } -} - -export const POKERUS_STARTER_COUNT = 5; //adjust here! -/** -* Method to get the daily list of starters with Pokerus. -* @param scene {@linkcode BattleScene} used as part of RNG -* @returns A list of starters with Pokerus -*/ -export function getPokerusStarters(scene: BattleScene): PokemonSpecies[] { - const pokerusStarters: PokemonSpecies[] = []; - const date = new Date(); - date.setUTCHours(0, 0, 0, 0); - scene.executeWithSeedOffset(() => { - while (pokerusStarters.length < POKERUS_STARTER_COUNT) { - const randomSpeciesId = parseInt(Utils.randSeedItem(Object.keys(speciesStarters)), 10); - const species = getPokemonSpecies(randomSpeciesId); - if (!pokerusStarters.includes(species)) { - pokerusStarters.push(species); - } - } - }, 0, date.getTime().toString()); - return pokerusStarters; -} - -export const starterPassiveAbilities = { - [Species.BULBASAUR]: Abilities.GRASSY_SURGE, - [Species.CHARMANDER]: Abilities.BEAST_BOOST, - [Species.SQUIRTLE]: Abilities.STURDY, - [Species.CATERPIE]: Abilities.MAGICIAN, - [Species.WEEDLE]: Abilities.TINTED_LENS, - [Species.PIDGEY]: Abilities.SHEER_FORCE, - [Species.RATTATA]: Abilities.STRONG_JAW, - [Species.SPEAROW]: Abilities.MOXIE, - [Species.EKANS]: Abilities.REGENERATOR, - [Species.SANDSHREW]: Abilities.TOUGH_CLAWS, - [Species.NIDORAN_F]: Abilities.FLARE_BOOST, - [Species.NIDORAN_M]: Abilities.GUTS, - [Species.VULPIX]: Abilities.FUR_COAT, - [Species.ZUBAT]: Abilities.INTIMIDATE, - [Species.ODDISH]: Abilities.TRIAGE, - [Species.PARAS]: Abilities.TRIAGE, - [Species.VENONAT]: Abilities.SIMPLE, - [Species.DIGLETT]: Abilities.STURDY, - [Species.MEOWTH]: Abilities.TOUGH_CLAWS, - [Species.PSYDUCK]: Abilities.SIMPLE, - [Species.MANKEY]: Abilities.IRON_FIST, - [Species.GROWLITHE]: Abilities.SPEED_BOOST, - [Species.POLIWAG]: Abilities.NO_GUARD, - [Species.ABRA]: Abilities.PSYCHIC_SURGE, - [Species.MACHOP]: Abilities.QUICK_FEET, - [Species.BELLSPROUT]: Abilities.FLOWER_GIFT, - [Species.TENTACOOL]: Abilities.TOXIC_CHAIN, - [Species.GEODUDE]: Abilities.DRY_SKIN, - [Species.PONYTA]: Abilities.MAGIC_GUARD, - [Species.SLOWPOKE]: Abilities.UNAWARE, - [Species.MAGNEMITE]: Abilities.LEVITATE, - [Species.FARFETCHD]: Abilities.SNIPER, - [Species.DODUO]: Abilities.PARENTAL_BOND, - [Species.SEEL]: Abilities.WATER_BUBBLE, - [Species.GRIMER]: Abilities.WATER_ABSORB, - [Species.SHELLDER]: Abilities.ICE_SCALES, - [Species.GASTLY]: Abilities.SHADOW_SHIELD, - [Species.ONIX]: Abilities.ROCKY_PAYLOAD, - [Species.DROWZEE]: Abilities.MAGICIAN, - [Species.KRABBY]: Abilities.THERMAL_EXCHANGE, - [Species.VOLTORB]: Abilities.TRANSISTOR, - [Species.EXEGGCUTE]: Abilities.RIPEN, - [Species.CUBONE]: Abilities.PARENTAL_BOND, - [Species.LICKITUNG]: Abilities.CHEEK_POUCH, - [Species.KOFFING]: Abilities.PARENTAL_BOND, - [Species.RHYHORN]: Abilities.FILTER, - [Species.TANGELA]: Abilities.SEED_SOWER, - [Species.KANGASKHAN]: Abilities.GUTS, - [Species.HORSEA]: Abilities.DRAGONS_MAW, - [Species.GOLDEEN]: Abilities.MULTISCALE, - [Species.STARYU]: Abilities.REGENERATOR, - [Species.SCYTHER]: Abilities.TINTED_LENS, - [Species.PINSIR]: Abilities.TINTED_LENS, - [Species.TAUROS]: Abilities.STAMINA, - [Species.MAGIKARP]: Abilities.MULTISCALE, - [Species.LAPRAS]: Abilities.LIGHTNING_ROD, - [Species.DITTO]: Abilities.ADAPTABILITY, - [Species.EEVEE]: Abilities.PICKUP, - [Species.PORYGON]: Abilities.PROTEAN, - [Species.OMANYTE]: Abilities.STURDY, - [Species.KABUTO]: Abilities.TOUGH_CLAWS, - [Species.AERODACTYL]: Abilities.ORICHALCUM_PULSE, - [Species.ARTICUNO]: Abilities.SNOW_WARNING, - [Species.ZAPDOS]: Abilities.DRIZZLE, - [Species.MOLTRES]: Abilities.DROUGHT, - [Species.DRATINI]: Abilities.AERILATE, - [Species.MEWTWO]: Abilities.NEUROFORCE, - [Species.MEW]: Abilities.PROTEAN, - [Species.CHIKORITA]: Abilities.THICK_FAT, - [Species.CYNDAQUIL]: Abilities.DROUGHT, - [Species.TOTODILE]: Abilities.TOUGH_CLAWS, - [Species.SENTRET]: Abilities.PICKUP, - [Species.HOOTHOOT]: Abilities.AERILATE, - [Species.LEDYBA]: Abilities.PRANKSTER, - [Species.SPINARAK]: Abilities.PRANKSTER, - [Species.CHINCHOU]: Abilities.WATER_BUBBLE, - [Species.PICHU]: Abilities.ELECTRIC_SURGE, - [Species.CLEFFA]: Abilities.ANALYTIC, - [Species.IGGLYBUFF]: Abilities.HUGE_POWER, - [Species.TOGEPI]: Abilities.PIXILATE, - [Species.NATU]: Abilities.TINTED_LENS, - [Species.MAREEP]: Abilities.ELECTROMORPHOSIS, - [Species.HOPPIP]: Abilities.FLUFFY, - [Species.AIPOM]: Abilities.SCRAPPY, - [Species.SUNKERN]: Abilities.DROUGHT, - [Species.YANMA]: Abilities.SHEER_FORCE, - [Species.WOOPER]: Abilities.COMATOSE, - [Species.MURKROW]: Abilities.DARK_AURA, - [Species.MISDREAVUS]: Abilities.BEADS_OF_RUIN, - [Species.UNOWN]: Abilities.PICKUP, - [Species.GIRAFARIG]: Abilities.PARENTAL_BOND, - [Species.PINECO]: Abilities.IRON_BARBS, - [Species.DUNSPARCE]: Abilities.UNAWARE, - [Species.GLIGAR]: Abilities.TOXIC_BOOST, - [Species.SNUBBULL]: Abilities.PIXILATE, - [Species.QWILFISH]: Abilities.TOXIC_DEBRIS, - [Species.SHUCKLE]: Abilities.HARVEST, - [Species.HERACROSS]: Abilities.TECHNICIAN, - [Species.SNEASEL]: Abilities.TOUGH_CLAWS, - [Species.TEDDIURSA]: Abilities.THICK_FAT, - [Species.SLUGMA]: Abilities.DESOLATE_LAND, - [Species.SWINUB]: Abilities.SLUSH_RUSH, - [Species.CORSOLA]: Abilities.STORM_DRAIN, - [Species.REMORAID]: Abilities.SIMPLE, - [Species.DELIBIRD]: Abilities.HUGE_POWER, - [Species.SKARMORY]: Abilities.LIGHTNING_ROD, - [Species.HOUNDOUR]: Abilities.DROUGHT, - [Species.PHANPY]: Abilities.SPEED_BOOST, - [Species.STANTLER]: Abilities.SPEED_BOOST, - [Species.SMEARGLE]: Abilities.PRANKSTER, - [Species.TYROGUE]: Abilities.MOXIE, - [Species.SMOOCHUM]: Abilities.PSYCHIC_SURGE, - [Species.ELEKID]: Abilities.SHEER_FORCE, - [Species.MAGBY]: Abilities.CONTRARY, - [Species.MILTANK]: Abilities.STAMINA, - [Species.RAIKOU]: Abilities.TRANSISTOR, - [Species.ENTEI]: Abilities.MOXIE, - [Species.SUICUNE]: Abilities.UNAWARE, - [Species.LARVITAR]: Abilities.SAND_RUSH, - [Species.LUGIA]: Abilities.DELTA_STREAM, - [Species.HO_OH]: Abilities.MAGIC_GUARD, - [Species.CELEBI]: Abilities.PSYCHIC_SURGE, - [Species.TREECKO]: Abilities.TINTED_LENS, - [Species.TORCHIC]: Abilities.RECKLESS, - [Species.MUDKIP]: Abilities.DRIZZLE, - [Species.POOCHYENA]: Abilities.TOUGH_CLAWS, - [Species.ZIGZAGOON]: Abilities.RUN_AWAY, - [Species.WURMPLE]: Abilities.SIMPLE, - [Species.LOTAD]: Abilities.DRIZZLE, - [Species.SEEDOT]: Abilities.SHARPNESS, - [Species.TAILLOW]: Abilities.AERILATE, - [Species.WINGULL]: Abilities.SWIFT_SWIM, - [Species.RALTS]: Abilities.PSYCHIC_SURGE, - [Species.SURSKIT]: Abilities.WATER_BUBBLE, - [Species.SHROOMISH]: Abilities.GUTS, - [Species.SLAKOTH]: Abilities.GUTS, - [Species.NINCADA]: Abilities.MAGIC_GUARD, - [Species.WHISMUR]: Abilities.PUNK_ROCK, - [Species.MAKUHITA]: Abilities.STAMINA, - [Species.AZURILL]: Abilities.MISTY_SURGE, - [Species.NOSEPASS]: Abilities.LEVITATE, - [Species.SKITTY]: Abilities.SCRAPPY, - [Species.SABLEYE]: Abilities.UNNERVE, - [Species.MAWILE]: Abilities.UNNERVE, - [Species.ARON]: Abilities.EARTH_EATER, - [Species.MEDITITE]: Abilities.MINDS_EYE, - [Species.ELECTRIKE]: Abilities.ELECTRIC_SURGE, - [Species.PLUSLE]: Abilities.POWER_SPOT, - [Species.MINUN]: Abilities.POWER_SPOT, - [Species.VOLBEAT]: Abilities.HONEY_GATHER, - [Species.ILLUMISE]: Abilities.HONEY_GATHER, - [Species.GULPIN]: Abilities.EARTH_EATER, - [Species.CARVANHA]: Abilities.SHEER_FORCE, - [Species.WAILMER]: Abilities.LEVITATE, - [Species.NUMEL]: Abilities.FUR_COAT, - [Species.TORKOAL]: Abilities.ANALYTIC, - [Species.SPOINK]: Abilities.PSYCHIC_SURGE, - [Species.SPINDA]: Abilities.SIMPLE, - [Species.TRAPINCH]: Abilities.ADAPTABILITY, - [Species.CACNEA]: Abilities.SAND_RUSH, - [Species.SWABLU]: Abilities.ADAPTABILITY, - [Species.ZANGOOSE]: Abilities.POISON_HEAL, - [Species.SEVIPER]: Abilities.MULTISCALE, - [Species.LUNATONE]: Abilities.SHADOW_SHIELD, - [Species.SOLROCK]: Abilities.DROUGHT, - [Species.BARBOACH]: Abilities.SIMPLE, - [Species.CORPHISH]: Abilities.TOUGH_CLAWS, - [Species.BALTOY]: Abilities.WELL_BAKED_BODY, - [Species.LILEEP]: Abilities.SEED_SOWER, - [Species.ANORITH]: Abilities.WATER_ABSORB, - [Species.FEEBAS]: Abilities.MAGIC_GUARD, - [Species.CASTFORM]: Abilities.ADAPTABILITY, - [Species.KECLEON]: Abilities.ADAPTABILITY, - [Species.SHUPPET]: Abilities.SHADOW_SHIELD, - [Species.DUSKULL]: Abilities.UNNERVE, - [Species.TROPIUS]: Abilities.RIPEN, - [Species.ABSOL]: Abilities.SHARPNESS, - [Species.WYNAUT]: Abilities.STURDY, - [Species.SNORUNT]: Abilities.SNOW_WARNING, - [Species.SPHEAL]: Abilities.UNAWARE, - [Species.CLAMPERL]: Abilities.DRIZZLE, - [Species.RELICANTH]: Abilities.PRIMORDIAL_SEA, - [Species.LUVDISC]: Abilities.MULTISCALE, - [Species.BAGON]: Abilities.DRAGONS_MAW, - [Species.BELDUM]: Abilities.LEVITATE, - [Species.REGIROCK]: Abilities.SAND_STREAM, - [Species.REGICE]: Abilities.SNOW_WARNING, - [Species.REGISTEEL]: Abilities.FILTER, - [Species.LATIAS]: Abilities.PRISM_ARMOR, - [Species.LATIOS]: Abilities.TINTED_LENS, - [Species.KYOGRE]: Abilities.MOLD_BREAKER, - [Species.GROUDON]: Abilities.TURBOBLAZE, - [Species.RAYQUAZA]: Abilities.UNNERVE, - [Species.JIRACHI]: Abilities.COMATOSE, - [Species.DEOXYS]: Abilities.PROTEAN, - [Species.TURTWIG]: Abilities.THICK_FAT, - [Species.CHIMCHAR]: Abilities.BEAST_BOOST, - [Species.PIPLUP]: Abilities.DRIZZLE, - [Species.STARLY]: Abilities.ROCK_HEAD, - [Species.BIDOOF]: Abilities.SAP_SIPPER, - [Species.KRICKETOT]: Abilities.SHARPNESS, - [Species.SHINX]: Abilities.SPEED_BOOST, - [Species.BUDEW]: Abilities.GRASSY_SURGE, - [Species.CRANIDOS]: Abilities.ROCK_HEAD, - [Species.SHIELDON]: Abilities.EARTH_EATER, - [Species.BURMY]: Abilities.STURDY, - [Species.COMBEE]: Abilities.INTIMIDATE, - [Species.PACHIRISU]: Abilities.HONEY_GATHER, - [Species.BUIZEL]: Abilities.MOXIE, - [Species.CHERUBI]: Abilities.ORICHALCUM_PULSE, - [Species.SHELLOS]: Abilities.REGENERATOR, - [Species.DRIFLOON]: Abilities.MAGIC_GUARD, - [Species.BUNEARY]: Abilities.ADAPTABILITY, - [Species.GLAMEOW]: Abilities.INTIMIDATE, - [Species.CHINGLING]: Abilities.PUNK_ROCK, - [Species.STUNKY]: Abilities.NEUTRALIZING_GAS, - [Species.BRONZOR]: Abilities.BULLETPROOF, - [Species.BONSLY]: Abilities.SAP_SIPPER, - [Species.MIME_JR]: Abilities.OPPORTUNIST, - [Species.HAPPINY]: Abilities.FUR_COAT, - [Species.CHATOT]: Abilities.PUNK_ROCK, - [Species.SPIRITOMB]: Abilities.VESSEL_OF_RUIN, - [Species.GIBLE]: Abilities.SAND_STREAM, - [Species.MUNCHLAX]: Abilities.RIPEN, - [Species.RIOLU]: Abilities.MINDS_EYE, - [Species.HIPPOPOTAS]: Abilities.UNAWARE, - [Species.SKORUPI]: Abilities.SUPER_LUCK, - [Species.CROAGUNK]: Abilities.MOXIE, - [Species.CARNIVINE]: Abilities.ARENA_TRAP, - [Species.FINNEON]: Abilities.WATER_BUBBLE, - [Species.MANTYKE]: Abilities.UNAWARE, - [Species.SNOVER]: Abilities.THICK_FAT, - [Species.ROTOM]: Abilities.HADRON_ENGINE, - [Species.UXIE]: Abilities.UNAWARE, - [Species.MESPRIT]: Abilities.MOODY, - [Species.AZELF]: Abilities.NEUROFORCE, - [Species.DIALGA]: Abilities.LEVITATE, - [Species.PALKIA]: Abilities.SPEED_BOOST, - [Species.HEATRAN]: Abilities.EARTH_EATER, - [Species.REGIGIGAS]: Abilities.SCRAPPY, - [Species.GIRATINA]: Abilities.SHADOW_SHIELD, - [Species.CRESSELIA]: Abilities.SHADOW_SHIELD, - [Species.PHIONE]: Abilities.SIMPLE, - [Species.MANAPHY]: Abilities.PRIMORDIAL_SEA, - [Species.DARKRAI]: Abilities.UNNERVE, - [Species.SHAYMIN]: Abilities.WIND_RIDER, - [Species.ARCEUS]: Abilities.ADAPTABILITY, - [Species.VICTINI]: Abilities.SHEER_FORCE, - [Species.SNIVY]: Abilities.MULTISCALE, - [Species.TEPIG]: Abilities.ROCK_HEAD, - [Species.OSHAWOTT]: Abilities.INTREPID_SWORD, - [Species.PATRAT]: Abilities.NO_GUARD, - [Species.LILLIPUP]: Abilities.FUR_COAT, - [Species.PURRLOIN]: Abilities.PICKUP, - [Species.PANSAGE]: Abilities.WELL_BAKED_BODY, - [Species.PANSEAR]: Abilities.WATER_ABSORB, - [Species.PANPOUR]: Abilities.SAP_SIPPER, - [Species.MUNNA]: Abilities.NEUTRALIZING_GAS, - [Species.PIDOVE]: Abilities.SNIPER, - [Species.BLITZLE]: Abilities.ELECTRIC_SURGE, - [Species.ROGGENROLA]: Abilities.SOLID_ROCK, - [Species.WOOBAT]: Abilities.OPPORTUNIST, - [Species.DRILBUR]: Abilities.SAND_STREAM, - [Species.AUDINO]: Abilities.FRIEND_GUARD, - [Species.TIMBURR]: Abilities.ROCKY_PAYLOAD, - [Species.TYMPOLE]: Abilities.POISON_HEAL, - [Species.THROH]: Abilities.STAMINA, - [Species.SAWK]: Abilities.SCRAPPY, - [Species.SEWADDLE]: Abilities.SHARPNESS, - [Species.VENIPEDE]: Abilities.STAMINA, - [Species.COTTONEE]: Abilities.FLUFFY, - [Species.PETILIL]: Abilities.SIMPLE, - [Species.BASCULIN]: Abilities.SUPREME_OVERLORD, - [Species.SANDILE]: Abilities.TOUGH_CLAWS, - [Species.DARUMAKA]: Abilities.GORILLA_TACTICS, - [Species.MARACTUS]: Abilities.WELL_BAKED_BODY, - [Species.DWEBBLE]: Abilities.ROCKY_PAYLOAD, - [Species.SCRAGGY]: Abilities.PROTEAN, - [Species.SIGILYPH]: Abilities.FLARE_BOOST, - [Species.YAMASK]: Abilities.PURIFYING_SALT, - [Species.TIRTOUGA]: Abilities.WATER_ABSORB, - [Species.ARCHEN]: Abilities.MULTISCALE, - [Species.TRUBBISH]: Abilities.NEUTRALIZING_GAS, - [Species.ZORUA]: Abilities.DARK_AURA, - [Species.MINCCINO]: Abilities.FUR_COAT, - [Species.GOTHITA]: Abilities.UNNERVE, - [Species.SOLOSIS]: Abilities.PSYCHIC_SURGE, - [Species.DUCKLETT]: Abilities.DRIZZLE, - [Species.VANILLITE]: Abilities.SLUSH_RUSH, - [Species.DEERLING]: Abilities.FUR_COAT, - [Species.EMOLGA]: Abilities.TRANSISTOR, - [Species.KARRABLAST]: Abilities.QUICK_DRAW, - [Species.FOONGUS]: Abilities.THICK_FAT, - [Species.FRILLISH]: Abilities.POISON_HEAL, - [Species.ALOMOMOLA]: Abilities.MULTISCALE, - [Species.JOLTIK]: Abilities.TRANSISTOR, - [Species.FERROSEED]: Abilities.ROUGH_SKIN, - [Species.KLINK]: Abilities.STEELY_SPIRIT, - [Species.TYNAMO]: Abilities.POISON_HEAL, - [Species.ELGYEM]: Abilities.PRISM_ARMOR, - [Species.LITWICK]: Abilities.SOUL_HEART, - [Species.AXEW]: Abilities.DRAGONS_MAW, - [Species.CUBCHOO]: Abilities.TOUGH_CLAWS, - [Species.CRYOGONAL]: Abilities.SNOW_WARNING, - [Species.SHELMET]: Abilities.PROTEAN, - [Species.STUNFISK]: Abilities.STORM_DRAIN, - [Species.MIENFOO]: Abilities.NO_GUARD, - [Species.DRUDDIGON]: Abilities.INTIMIDATE, - [Species.GOLETT]: Abilities.SHADOW_SHIELD, - [Species.PAWNIARD]: Abilities.SWORD_OF_RUIN, - [Species.BOUFFALANT]: Abilities.ROCK_HEAD, - [Species.RUFFLET]: Abilities.SPEED_BOOST, - [Species.VULLABY]: Abilities.THICK_FAT, - [Species.HEATMOR]: Abilities.CONTRARY, - [Species.DURANT]: Abilities.COMPOUND_EYES, - [Species.DEINO]: Abilities.PARENTAL_BOND, - [Species.LARVESTA]: Abilities.DROUGHT, - [Species.COBALION]: Abilities.INTREPID_SWORD, - [Species.TERRAKION]: Abilities.ROCKY_PAYLOAD, - [Species.VIRIZION]: Abilities.SHARPNESS, - [Species.TORNADUS]: Abilities.DRIZZLE, - [Species.THUNDURUS]: Abilities.DRIZZLE, - [Species.RESHIRAM]: Abilities.ORICHALCUM_PULSE, - [Species.ZEKROM]: Abilities.HADRON_ENGINE, - [Species.LANDORUS]: Abilities.STORM_DRAIN, - [Species.KYUREM]: Abilities.SNOW_WARNING, - [Species.KELDEO]: Abilities.GRIM_NEIGH, - [Species.MELOETTA]: Abilities.MINDS_EYE, - [Species.GENESECT]: Abilities.PROTEAN, - [Species.CHESPIN]: Abilities.DAUNTLESS_SHIELD, - [Species.FENNEKIN]: Abilities.PSYCHIC_SURGE, - [Species.FROAKIE]: Abilities.STAKEOUT, - [Species.BUNNELBY]: Abilities.GUTS, - [Species.FLETCHLING]: Abilities.MAGIC_GUARD, - [Species.SCATTERBUG]: Abilities.PRANKSTER, - [Species.LITLEO]: Abilities.BEAST_BOOST, - [Species.FLABEBE]: Abilities.GRASSY_SURGE, - [Species.SKIDDO]: Abilities.SEED_SOWER, - [Species.PANCHAM]: Abilities.FUR_COAT, - [Species.FURFROU]: Abilities.FLUFFY, - [Species.ESPURR]: Abilities.FUR_COAT, - [Species.HONEDGE]: Abilities.SHARPNESS, - [Species.SPRITZEE]: Abilities.FUR_COAT, - [Species.SWIRLIX]: Abilities.WELL_BAKED_BODY, - [Species.INKAY]: Abilities.UNNERVE, - [Species.BINACLE]: Abilities.SAP_SIPPER, - [Species.SKRELP]: Abilities.DRAGONS_MAW, - [Species.CLAUNCHER]: Abilities.SWIFT_SWIM, - [Species.HELIOPTILE]: Abilities.PROTEAN, - [Species.TYRUNT]: Abilities.RECKLESS, - [Species.AMAURA]: Abilities.ICE_SCALES, - [Species.HAWLUCHA]: Abilities.MOXIE, - [Species.DEDENNE]: Abilities.PIXILATE, - [Species.CARBINK]: Abilities.SOLID_ROCK, - [Species.GOOMY]: Abilities.REGENERATOR, - [Species.KLEFKI]: Abilities.LEVITATE, - [Species.PHANTUMP]: Abilities.SHADOW_TAG, - [Species.PUMPKABOO]: Abilities.WELL_BAKED_BODY, - [Species.BERGMITE]: Abilities.ICE_SCALES, - [Species.NOIBAT]: Abilities.PUNK_ROCK, - [Species.XERNEAS]: Abilities.HARVEST, - [Species.YVELTAL]: Abilities.SOUL_HEART, - [Species.ZYGARDE]: Abilities.HUGE_POWER, - [Species.DIANCIE]: Abilities.LEVITATE, - [Species.HOOPA]: Abilities.OPPORTUNIST, - [Species.VOLCANION]: Abilities.FILTER, - [Species.ROWLET]: Abilities.SNIPER, - [Species.LITTEN]: Abilities.OPPORTUNIST, - [Species.POPPLIO]: Abilities.PUNK_ROCK, - [Species.PIKIPEK]: Abilities.TECHNICIAN, - [Species.YUNGOOS]: Abilities.TOUGH_CLAWS, - [Species.GRUBBIN]: Abilities.SPEED_BOOST, - [Species.CRABRAWLER]: Abilities.WATER_BUBBLE, - [Species.ORICORIO]: Abilities.ADAPTABILITY, - [Species.CUTIEFLY]: Abilities.TINTED_LENS, - [Species.ROCKRUFF]: Abilities.ROCKY_PAYLOAD, - [Species.WISHIWASHI]: Abilities.REGENERATOR, - [Species.MAREANIE]: Abilities.TOXIC_DEBRIS, - [Species.MUDBRAY]: Abilities.CUD_CHEW, - [Species.DEWPIDER]: Abilities.TINTED_LENS, - [Species.FOMANTIS]: Abilities.SHARPNESS, - [Species.MORELULL]: Abilities.TRIAGE, - [Species.SALANDIT]: Abilities.DRAGONS_MAW, - [Species.STUFFUL]: Abilities.SCRAPPY, - [Species.BOUNSWEET]: Abilities.MOXIE, - [Species.COMFEY]: Abilities.FRIEND_GUARD, - [Species.ORANGURU]: Abilities.POWER_SPOT, - [Species.PASSIMIAN]: Abilities.LIBERO, - [Species.WIMPOD]: Abilities.REGENERATOR, - [Species.SANDYGAST]: Abilities.SAND_SPIT, - [Species.PYUKUMUKU]: Abilities.PURIFYING_SALT, - [Species.TYPE_NULL]: Abilities.ADAPTABILITY, - [Species.MINIOR]: Abilities.STURDY, - [Species.KOMALA]: Abilities.GUTS, - [Species.TURTONATOR]: Abilities.DAUNTLESS_SHIELD, - [Species.TOGEDEMARU]: Abilities.ROUGH_SKIN, - [Species.MIMIKYU]: Abilities.TOUGH_CLAWS, - [Species.BRUXISH]: Abilities.MULTISCALE, - [Species.DRAMPA]: Abilities.THICK_FAT, - [Species.DHELMISE]: Abilities.WATER_BUBBLE, - [Species.JANGMO_O]: Abilities.DAUNTLESS_SHIELD, - [Species.TAPU_KOKO]: Abilities.TRANSISTOR, - [Species.TAPU_LELE]: Abilities.SHEER_FORCE, - [Species.TAPU_BULU]: Abilities.TRIAGE, - [Species.TAPU_FINI]: Abilities.FAIRY_AURA, - [Species.COSMOG]: Abilities.BEAST_BOOST, - [Species.NIHILEGO]: Abilities.LEVITATE, - [Species.BUZZWOLE]: Abilities.MOXIE, - [Species.PHEROMOSA]: Abilities.TINTED_LENS, - [Species.XURKITREE]: Abilities.TRANSISTOR, - [Species.CELESTEELA]: Abilities.HEATPROOF, - [Species.KARTANA]: Abilities.SHARPNESS, - [Species.GUZZLORD]: Abilities.POISON_HEAL, - [Species.NECROZMA]: Abilities.BEAST_BOOST, - [Species.MAGEARNA]: Abilities.STEELY_SPIRIT, - [Species.MARSHADOW]: Abilities.IRON_FIST, - [Species.POIPOLE]: Abilities.SHEER_FORCE, - [Species.STAKATAKA]: Abilities.SOLID_ROCK, - [Species.BLACEPHALON]: Abilities.MAGIC_GUARD, - [Species.ZERAORA]: Abilities.TOUGH_CLAWS, - [Species.MELTAN]: Abilities.STEELY_SPIRIT, - [Species.GROOKEY]: Abilities.GRASS_PELT, - [Species.SCORBUNNY]: Abilities.NO_GUARD, - [Species.SOBBLE]: Abilities.SUPER_LUCK, - [Species.SKWOVET]: Abilities.HARVEST, - [Species.ROOKIDEE]: Abilities.IRON_BARBS, - [Species.BLIPBUG]: Abilities.PSYCHIC_SURGE, - [Species.NICKIT]: Abilities.MAGICIAN, - [Species.GOSSIFLEUR]: Abilities.GRASSY_SURGE, - [Species.WOOLOO]: Abilities.SIMPLE, - [Species.CHEWTLE]: Abilities.ROCKY_PAYLOAD, - [Species.YAMPER]: Abilities.SHEER_FORCE, - [Species.ROLYCOLY]: Abilities.SOLID_ROCK, - [Species.APPLIN]: Abilities.DRAGONS_MAW, - [Species.SILICOBRA]: Abilities.SAND_RUSH, - [Species.CRAMORANT]: Abilities.LIGHTNING_ROD, - [Species.ARROKUDA]: Abilities.INTIMIDATE, - [Species.TOXEL]: Abilities.ELECTRIC_SURGE, - [Species.SIZZLIPEDE]: Abilities.SPEED_BOOST, - [Species.CLOBBOPUS]: Abilities.WATER_BUBBLE, - [Species.SINISTEA]: Abilities.SHADOW_SHIELD, - [Species.HATENNA]: Abilities.FAIRY_AURA, - [Species.IMPIDIMP]: Abilities.FUR_COAT, - [Species.MILCERY]: Abilities.REGENERATOR, - [Species.FALINKS]: Abilities.PARENTAL_BOND, - [Species.PINCURCHIN]: Abilities.ELECTROMORPHOSIS, - [Species.SNOM]: Abilities.SNOW_WARNING, - [Species.STONJOURNER]: Abilities.STURDY, - [Species.EISCUE]: Abilities.ICE_SCALES, - [Species.INDEEDEE]: Abilities.FRIEND_GUARD, - [Species.MORPEKO]: Abilities.MOODY, - [Species.CUFANT]: Abilities.EARTH_EATER, - [Species.DRACOZOLT]: Abilities.NO_GUARD, - [Species.ARCTOZOLT]: Abilities.TRANSISTOR, - [Species.DRACOVISH]: Abilities.SWIFT_SWIM, - [Species.ARCTOVISH]: Abilities.STRONG_JAW, - [Species.DURALUDON]: Abilities.STEELWORKER, - [Species.DREEPY]: Abilities.PARENTAL_BOND, - [Species.ZACIAN]: Abilities.UNNERVE, - [Species.ZAMAZENTA]: Abilities.UNNERVE, - [Species.ETERNATUS]: Abilities.NEUTRALIZING_GAS, - [Species.KUBFU]: Abilities.IRON_FIST, - [Species.ZARUDE]: Abilities.TOUGH_CLAWS, - [Species.REGIELEKI]: Abilities.ELECTRIC_SURGE, - [Species.REGIDRAGO]: Abilities.MULTISCALE, - [Species.GLASTRIER]: Abilities.FILTER, - [Species.SPECTRIER]: Abilities.SHADOW_SHIELD, - [Species.CALYREX]: Abilities.HARVEST, - [Species.ENAMORUS]: Abilities.FAIRY_AURA, - [Species.SPRIGATITO]: Abilities.MAGICIAN, - [Species.FUECOCO]: Abilities.PUNK_ROCK, - [Species.QUAXLY]: Abilities.OPPORTUNIST, - [Species.LECHONK]: Abilities.SIMPLE, - [Species.TAROUNTULA]: Abilities.HONEY_GATHER, - [Species.NYMBLE]: Abilities.GUTS, - [Species.PAWMI]: Abilities.TRANSISTOR, - [Species.TANDEMAUS]: Abilities.SCRAPPY, - [Species.FIDOUGH]: Abilities.WATER_ABSORB, - [Species.SMOLIV]: Abilities.RIPEN, - [Species.SQUAWKABILLY]: Abilities.MOXIE, - [Species.NACLI]: Abilities.SOLID_ROCK, - [Species.CHARCADET]: Abilities.PRISM_ARMOR, - [Species.TADBULB]: Abilities.STAMINA, - [Species.WATTREL]: Abilities.SHEER_FORCE, - [Species.MASCHIFF]: Abilities.STRONG_JAW, - [Species.SHROODLE]: Abilities.CORROSION, - [Species.BRAMBLIN]: Abilities.SHADOW_SHIELD, - [Species.TOEDSCOOL]: Abilities.PRANKSTER, - [Species.KLAWF]: Abilities.WATER_ABSORB, - [Species.CAPSAKID]: Abilities.PARENTAL_BOND, - [Species.RELLOR]: Abilities.PRANKSTER, - [Species.FLITTLE]: Abilities.DAZZLING, - [Species.TINKATINK]: Abilities.STEELWORKER, - [Species.WIGLETT]: Abilities.STURDY, - [Species.BOMBIRDIER]: Abilities.UNBURDEN, - [Species.FINIZEN]: Abilities.IRON_FIST, - [Species.VAROOM]: Abilities.LEVITATE, - [Species.CYCLIZAR]: Abilities.PROTEAN, - [Species.ORTHWORM]: Abilities.REGENERATOR, - [Species.GLIMMET]: Abilities.LEVITATE, - [Species.GREAVARD]: Abilities.FUR_COAT, - [Species.FLAMIGO]: Abilities.MOXIE, - [Species.CETODDLE]: Abilities.ICE_SCALES, - [Species.VELUZA]: Abilities.SUPER_LUCK, - [Species.DONDOZO]: Abilities.PARENTAL_BOND, - [Species.TATSUGIRI]: Abilities.ADAPTABILITY, - [Species.GREAT_TUSK]: Abilities.INTIMIDATE, - [Species.SCREAM_TAIL]: Abilities.UNAWARE, - [Species.BRUTE_BONNET]: Abilities.CHLOROPHYLL, - [Species.FLUTTER_MANE]: Abilities.DAZZLING, - [Species.SLITHER_WING]: Abilities.SCRAPPY, - [Species.SANDY_SHOCKS]: Abilities.EARTH_EATER, - [Species.IRON_TREADS]: Abilities.STEELY_SPIRIT, - [Species.IRON_BUNDLE]: Abilities.SNOW_WARNING, - [Species.IRON_HANDS]: Abilities.IRON_FIST, - [Species.IRON_JUGULIS]: Abilities.LIGHTNING_ROD, - [Species.IRON_MOTH]: Abilities.LEVITATE, - [Species.IRON_THORNS]: Abilities.SAND_STREAM, - [Species.FRIGIBAX]: Abilities.SNOW_WARNING, - [Species.GIMMIGHOUL]: Abilities.HONEY_GATHER, - [Species.WO_CHIEN]: Abilities.VESSEL_OF_RUIN, - [Species.CHIEN_PAO]: Abilities.INTIMIDATE, - [Species.TING_LU]: Abilities.STAMINA, - [Species.CHI_YU]: Abilities.BERSERK, - [Species.ROARING_MOON]: Abilities.TOUGH_CLAWS, - [Species.IRON_VALIANT]: Abilities.ADAPTABILITY, - [Species.KORAIDON]: Abilities.OPPORTUNIST, - [Species.MIRAIDON]: Abilities.OPPORTUNIST, - [Species.WALKING_WAKE]: Abilities.BEAST_BOOST, - [Species.IRON_LEAVES]: Abilities.SHARPNESS, - [Species.POLTCHAGEIST]: Abilities.TRIAGE, - [Species.OKIDOGI]: Abilities.FUR_COAT, - [Species.MUNKIDORI]: Abilities.NEUROFORCE, - [Species.FEZANDIPITI]: Abilities.LEVITATE, - [Species.OGERPON]: Abilities.OPPORTUNIST, - [Species.GOUGING_FIRE]: Abilities.BEAST_BOOST, - [Species.RAGING_BOLT]: Abilities.BEAST_BOOST, - [Species.IRON_BOULDER]: Abilities.SHARPNESS, - [Species.IRON_CROWN]: Abilities.SHARPNESS, - [Species.TERAPAGOS]: Abilities.SOUL_HEART, - [Species.PECHARUNT]: Abilities.TOXIC_CHAIN, - [Species.ALOLA_RATTATA]: Abilities.ADAPTABILITY, - [Species.ALOLA_SANDSHREW]: Abilities.ICE_SCALES, - [Species.ALOLA_VULPIX]: Abilities.SHEER_FORCE, - [Species.ALOLA_DIGLETT]: Abilities.STURDY, - [Species.ALOLA_MEOWTH]: Abilities.DARK_AURA, - [Species.ALOLA_GEODUDE]: Abilities.DRY_SKIN, - [Species.ALOLA_GRIMER]: Abilities.TOXIC_DEBRIS, - [Species.ETERNAL_FLOETTE]: Abilities.MAGIC_GUARD, - [Species.GALAR_MEOWTH]: Abilities.STEELWORKER, - [Species.GALAR_PONYTA]: Abilities.MOXIE, - [Species.GALAR_SLOWPOKE]: Abilities.UNAWARE, - [Species.GALAR_FARFETCHD]: Abilities.INTREPID_SWORD, - [Species.GALAR_ARTICUNO]: Abilities.SERENE_GRACE, - [Species.GALAR_ZAPDOS]: Abilities.TOUGH_CLAWS, - [Species.GALAR_MOLTRES]: Abilities.DARK_AURA, - [Species.GALAR_CORSOLA]: Abilities.SHADOW_SHIELD, - [Species.GALAR_ZIGZAGOON]: Abilities.POISON_HEAL, - [Species.GALAR_DARUMAKA]: Abilities.FLASH_FIRE, - [Species.GALAR_YAMASK]: Abilities.TABLETS_OF_RUIN, - [Species.GALAR_STUNFISK]: Abilities.ARENA_TRAP, - [Species.HISUI_GROWLITHE]: Abilities.RECKLESS, - [Species.HISUI_VOLTORB]: Abilities.TRANSISTOR, - [Species.HISUI_QWILFISH]: Abilities.MERCILESS, - [Species.HISUI_SNEASEL]: Abilities.SCRAPPY, - [Species.HISUI_ZORUA]: Abilities.ADAPTABILITY, - [Species.PALDEA_TAUROS]: Abilities.ADAPTABILITY, - [Species.PALDEA_WOOPER]: Abilities.THICK_FAT, - [Species.BLOODMOON_URSALUNA]: Abilities.BERSERK -}; - // TODO: Remove { //setTimeout(() => { diff --git a/src/data/trainer-config.ts b/src/data/trainer-config.ts index 296e4e9b1b5..63ed596cd13 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -1,22 +1,22 @@ -import BattleScene, { startingWave } from "../battle-scene"; -import { ModifierTypeFunc, modifierTypes } from "../modifier/modifier-type"; -import { EnemyPokemon, PokemonMove } from "../field/pokemon"; -import * as Utils from "../utils"; -import { PokeballType } from "./pokeball"; -import { pokemonEvolutions, pokemonPrevolutions } from "./pokemon-evolutions"; -import PokemonSpecies, { getPokemonSpecies, PokemonSpeciesFilter } from "./pokemon-species"; -import { tmSpecies } from "./tms"; -import { Type } from "./type"; -import { doubleBattleDialogue } from "./dialogue"; -import { PersistentModifier } from "../modifier/modifier"; -import { TrainerVariant } from "../field/trainer"; +import BattleScene, { startingWave } from "#app/battle-scene"; +import { ModifierTypeFunc, modifierTypes } from "#app/modifier/modifier-type"; +import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; +import * as Utils from "#app/utils"; +import { PokeballType } from "#app/data/pokeball"; +import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; +import PokemonSpecies, { getPokemonSpecies, PokemonSpeciesFilter } from "#app/data/pokemon-species"; +import { tmSpecies } from "#app/data/balance/tms"; +import { Type } from "#app/data/type"; +import { doubleBattleDialogue } from "#app/data/dialogue"; +import { PersistentModifier } from "#app/modifier/modifier"; +import { TrainerVariant } from "#app/field/trainer"; import { getIsInitialized, initI18n } from "#app/plugins/i18n"; import i18next from "i18next"; -import {Moves} from "#enums/moves"; -import {PartyMemberStrength} from "#enums/party-member-strength"; -import {Species} from "#enums/species"; -import {TrainerType} from "#enums/trainer-type"; -import {Gender} from "./gender"; +import { Moves } from "#enums/moves"; +import { PartyMemberStrength } from "#enums/party-member-strength"; +import { Species } from "#enums/species"; +import { TrainerType } from "#enums/trainer-type"; +import { Gender } from "#app/data/gender"; /** Minimum BST for Pokemon generated onto the Elite Four's teams */ const ELITE_FOUR_MINIMUM_BST = 460; diff --git a/src/field/arena.ts b/src/field/arena.ts index dc9ad84f09d..9d5f1eb0a4e 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -1,26 +1,26 @@ -import BattleScene from "../battle-scene"; -import { biomePokemonPools, BiomePoolTier, BiomeTierTrainerPools, biomeTrainerPools, PokemonPools } from "../data/biomes"; +import BattleScene from "#app/battle-scene"; +import { biomePokemonPools, BiomePoolTier, BiomeTierTrainerPools, biomeTrainerPools, PokemonPools } from "#app/data/balance/biomes"; import { Constructor } from "#app/utils"; -import * as Utils from "../utils"; -import PokemonSpecies, { getPokemonSpecies } from "../data/pokemon-species"; -import { getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage, Weather, WeatherType } from "../data/weather"; -import { CommonAnim } from "../data/battle-anims"; -import { Type } from "../data/type"; -import Move from "../data/move"; -import { ArenaTag, ArenaTagSide, ArenaTrapTag, getArenaTag } from "../data/arena-tag"; -import { BattlerIndex } from "../battle"; -import { Terrain, TerrainType } from "../data/terrain"; -import { applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs, PostTerrainChangeAbAttr, PostWeatherChangeAbAttr } from "../data/ability"; -import Pokemon from "./pokemon"; +import * as Utils from "#app/utils"; +import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage, Weather, WeatherType } from "#app/data/weather"; +import { CommonAnim } from "#app/data/battle-anims"; +import { Type } from "#app/data/type"; +import Move from "#app/data/move"; +import { ArenaTag, ArenaTagSide, ArenaTrapTag, getArenaTag } from "#app/data/arena-tag"; +import { BattlerIndex } from "#app/battle"; +import { Terrain, TerrainType } from "#app/data/terrain"; +import { applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs, PostTerrainChangeAbAttr, PostWeatherChangeAbAttr } from "#app/data/ability"; +import Pokemon from "#app/field/pokemon"; import Overrides from "#app/overrides"; -import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "../events/arena"; +import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena"; import { ArenaTagType } from "#enums/arena-tag-type"; import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { TimeOfDay } from "#enums/time-of-day"; import { TrainerType } from "#enums/trainer-type"; -import { Abilities } from "#app/enums/abilities"; +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"; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 22df5d3a6c3..d5411496223 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1,43 +1,45 @@ import Phaser from "phaser"; -import BattleScene, { AnySound } from "../battle-scene"; +import BattleScene, { AnySound } from "#app/battle-scene"; import { Variant, VariantSet, variantColorCache } from "#app/data/variant"; import { variantData } from "#app/data/variant"; -import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info"; -import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget } from "../data/move"; -import { default as PokemonSpecies, PokemonSpeciesForm, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species"; +import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "#app/ui/battle-info"; +import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget } from "#app/data/move"; +import { default as PokemonSpecies, PokemonSpeciesForm, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; +import { starterPassiveAbilities } from "#app/data/balance/passives"; import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils"; -import * as Utils from "../utils"; -import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type"; -import { getLevelTotalExp } from "../data/exp"; +import * as Utils from "#app/utils"; +import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "#app/data/type"; +import { getLevelTotalExp } from "#app/data/exp"; import { Stat, type PermanentStat, type BattleStat, type EffectiveStat, PERMANENT_STATS, BATTLE_STATS, EFFECTIVE_STATS } from "#enums/stat"; -import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, BaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempStatStageBoosterModifier, TempCritBoosterModifier, StatBoosterModifier, CritBoosterModifier, TerastallizeModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonIncrementingStatModifier, EvoTrackerModifier } from "../modifier/modifier"; -import { PokeballType } from "../data/pokeball"; -import { Gender } from "../data/gender"; -import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims"; -import { Status, StatusEffect, getRandomStatus } from "../data/status-effect"; -import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions"; -import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms"; +import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, BaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempStatStageBoosterModifier, TempCritBoosterModifier, StatBoosterModifier, CritBoosterModifier, TerastallizeModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonIncrementingStatModifier, EvoTrackerModifier } from "#app/modifier/modifier"; +import { PokeballType } from "#app/data/pokeball"; +import { Gender } from "#app/data/gender"; +import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; +import { Status, StatusEffect, getRandomStatus } from "#app/data/status-effect"; +import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; +import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms"; import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag } from "../data/battler-tags"; -import { WeatherType } from "../data/weather"; -import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag"; -import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs } from "../data/ability"; -import PokemonData from "../system/pokemon-data"; -import { BattlerIndex } from "../battle"; -import { Mode } from "../ui/ui"; -import PartyUiHandler, { PartyOption, PartyUiMode } from "../ui/party-ui-handler"; +import { WeatherType } from "#app/data/weather"; +import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; +import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs } from "#app/data/ability"; +import PokemonData from "#app/system/pokemon-data"; +import { BattlerIndex } from "#app/battle"; +import { Mode } from "#app/ui/ui"; +import PartyUiHandler, { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; -import { LevelMoves } from "../data/pokemon-level-moves"; -import { DamageAchv, achvs } from "../system/achv"; -import { DexAttr, StarterDataEntry, StarterMoveset } from "../system/game-data"; +import { LevelMoves } from "#app/data/balance/pokemon-level-moves"; +import { DamageAchv, achvs } from "#app/system/achv"; +import { DexAttr, StarterDataEntry, StarterMoveset } from "#app/system/game-data"; import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities"; -import { Nature, getNatureStatMultiplier } from "../data/nature"; -import { SpeciesFormChange, SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangeStatusEffectTrigger } from "../data/pokemon-forms"; -import { TerrainType } from "../data/terrain"; -import { TrainerSlot } from "../data/trainer-config"; +import { Nature, getNatureStatMultiplier } from "#app/data/nature"; +import { SpeciesFormChange, SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms"; +import { TerrainType } from "#app/data/terrain"; +import { TrainerSlot } from "#app/data/trainer-config"; import Overrides from "#app/overrides"; import i18next from "i18next"; -import { speciesEggMoves } from "../data/egg-moves"; -import { ModifierTier } from "../modifier/modifier-tier"; +import { speciesEggMoves } from "#app/data/balance/egg-moves"; +import { ModifierTier } from "#app/modifier/modifier-tier"; import { applyChallenges, ChallengeType } from "#app/data/challenge"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; @@ -58,17 +60,12 @@ import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase"; import { Challenges } from "#enums/challenges"; -import { PokemonAnimType } from "#app/enums/pokemon-anim-type"; +import { PokemonAnimType } from "#enums/pokemon-anim-type"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; import { SwitchType } from "#enums/switch-type"; import { SpeciesFormKey } from "#enums/species-form-key"; - -/** `64/65536 -> 1/1024` */ -const BASE_SHINY_CHANCE = 64; - -/** `1/256` */ -const BASE_HIDDEN_ABILITY_CHANCE = 256; +import { BASE_HIDDEN_ABILITY_CHANCE, BASE_SHINY_CHANCE, SHINY_EPIC_CHANCE, SHINY_VARIANT_CHANCE } from "#app/data/balance/rates"; export enum FieldPosition { CENTER, @@ -1930,9 +1927,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.scene.executeWithSeedOffset(() => { rand.value = Utils.randSeedInt(10); }, this.id, this.scene.waveSeed); - if (rand.value >= 4) { + if (rand.value >= SHINY_VARIANT_CHANCE) { return 0; // 6/10 - } else if (rand.value >= 1) { + } else if (rand.value >= SHINY_EPIC_CHANCE) { return 1; // 3/10 } else { return 2; // 1/10 @@ -4057,7 +4054,7 @@ export class PlayerPokemon extends Pokemon { starterData.forEach((sd: StarterDataEntry, i: integer) => { const speciesId = !i ? starterSpeciesId : fusionStarterSpeciesId as Species; sd.friendship = (sd.friendship || 0) + starterAmount.value; - if (sd.friendship >= getStarterValueFriendshipCap(speciesStarters[speciesId])) { + if (sd.friendship >= getStarterValueFriendshipCap(speciesStarterCosts[speciesId])) { this.scene.gameData.addStarterCandy(getPokemonSpecies(speciesId), 1); sd.friendship = 0; } diff --git a/src/field/trainer.ts b/src/field/trainer.ts index b1d0263f604..06548e2b020 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -1,6 +1,6 @@ -import BattleScene from "../battle-scene"; -import {pokemonPrevolutions} from "../data/pokemon-evolutions"; -import PokemonSpecies, {getPokemonSpecies} from "../data/pokemon-species"; +import BattleScene from "#app/battle-scene"; +import {pokemonPrevolutions} from "#app/data/balance/pokemon-evolutions"; +import PokemonSpecies, {getPokemonSpecies} from "#app/data/pokemon-species"; import { TrainerConfig, TrainerPartyCompoundTemplate, @@ -10,13 +10,13 @@ import { trainerConfigs, trainerPartyTemplates, signatureSpecies -} from "../data/trainer-config"; -import {EnemyPokemon} from "./pokemon"; -import * as Utils from "../utils"; -import {PersistentModifier} from "../modifier/modifier"; -import {trainerNamePools} from "../data/trainer-names"; -import {ArenaTagSide, ArenaTrapTag} from "#app/data/arena-tag"; -import {getIsInitialized, initI18n} from "#app/plugins/i18n"; +} from "#app/data/trainer-config"; +import {EnemyPokemon} from "#app/field/pokemon"; +import * as Utils from "#app/utils"; +import { PersistentModifier } from "#app/modifier/modifier"; +import { trainerNamePools } from "#app/data/trainer-names"; +import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; +import { getIsInitialized, initI18n } from "#app/plugins/i18n"; import i18next from "i18next"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { Species } from "#enums/species"; diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 6e7a6735157..e71082ca8f5 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -1,23 +1,23 @@ -import { GachaType } from "./enums/gacha-types"; -import { getBiomeHasProps } from "./field/arena"; -import CacheBustedLoaderPlugin from "./plugins/cache-busted-loader-plugin"; -import { SceneBase } from "./scene-base"; -import { WindowVariant, getWindowVariantSuffix } from "./ui/ui-theme"; -import { isMobile } from "./touch-controls"; -import * as Utils from "./utils"; -import { initPokemonPrevolutions } from "#app/data/pokemon-evolutions"; -import { initBiomes } from "#app/data/biomes"; -import { initEggMoves } from "#app/data/egg-moves"; +import { GachaType } from "#enums/gacha-types"; +import { getBiomeHasProps } from "#app/field/arena"; +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 { initPokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; +import { initBiomes } from "#app/data/balance/biomes"; +import { initEggMoves } from "#app/data/balance/egg-moves"; import { initPokemonForms } from "#app/data/pokemon-forms"; import { initSpecies } from "#app/data/pokemon-species"; import { initMoves } from "#app/data/move"; import { initAbilities } from "#app/data/ability"; import { initAchievements } from "#app/system/achv"; import { initTrainerTypeDialogue } from "#app/data/dialogue"; -import { initChallenges } from "./data/challenge"; +import { initChallenges } from "#app/data/challenge"; import i18next from "i18next"; -import { initStatsKeys } from "./ui/game-stats-ui-handler"; -import { initVouchers } from "./system/voucher"; +import { initStatsKeys } from "#app/ui/game-stats-ui-handler"; +import { initVouchers } from "#app/system/voucher"; import { Biome } from "#enums/biome"; import {initMysteryEncounters} from "#app/data/mystery-encounters/mystery-encounters"; diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 7e4ef402ead..238d2f0debf 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1,20 +1,20 @@ -import * as Modifiers from "./modifier"; -import { MoneyMultiplierModifier } from "./modifier"; -import { allMoves, AttackMove, selfStatLowerMoves } from "../data/move"; -import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS, PokeballType } from "../data/pokeball"; -import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "../field/pokemon"; -import { EvolutionItem, pokemonEvolutions } from "../data/pokemon-evolutions"; -import { tmPoolTiers, tmSpecies } from "../data/tms"; -import { Type } from "../data/type"; -import PartyUiHandler, { PokemonMoveSelectFilter, PokemonSelectFilter } from "../ui/party-ui-handler"; -import * as Utils from "../utils"; -import { getBerryEffectDescription, getBerryName } from "../data/berry"; -import { Unlockables } from "../system/unlockables"; -import { getStatusEffectDescriptor, StatusEffect } from "../data/status-effect"; -import BattleScene from "../battle-scene"; -import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "../system/voucher"; -import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeCondition, SpeciesFormChangeItemTrigger } from "../data/pokemon-forms"; -import { ModifierTier } from "./modifier-tier"; +import * as Modifiers from "#app/modifier/modifier"; +import { MoneyMultiplierModifier } from "#app/modifier/modifier"; +import { allMoves, AttackMove, selfStatLowerMoves } from "#app/data/move"; +import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS, PokeballType } from "#app/data/pokeball"; +import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; +import { tmPoolTiers, tmSpecies } from "#app/data/balance/tms"; +import { Type } from "#app/data/type"; +import PartyUiHandler, { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party-ui-handler"; +import * as Utils from "#app/utils"; +import { getBerryEffectDescription, getBerryName } from "#app/data/berry"; +import { Unlockables } from "#app/system/unlockables"; +import { getStatusEffectDescriptor, StatusEffect } from "#app/data/status-effect"; +import BattleScene from "#app/battle-scene"; +import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "#app/system/voucher"; +import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeCondition, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; +import { ModifierTier } from "#app/modifier/modifier-tier"; import { getNatureName, getNatureStatMultiplier, Nature } from "#app/data/nature"; import i18next from "i18next"; import { getModifierTierTextTint } from "#app/ui/text"; @@ -25,7 +25,7 @@ import { BerryType } from "#enums/berry-type"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { getPokemonNameWithAffix } from "#app/messages"; -import { PermanentStat, TEMP_BATTLE_STATS, TempBattleStat, Stat, getStatKey } from "#app/enums/stat"; +import { PermanentStat, TEMP_BATTLE_STATS, TempBattleStat, Stat, getStatKey } from "#enums/stat"; import { SpeciesFormKey } from "#enums/species-form-key"; const outputModifierData = false; diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 537e2327a21..4c20b454081 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1,31 +1,30 @@ -import * as ModifierTypes from "./modifier-type"; -import { getModifierType, ModifierType, modifierTypes } from "./modifier-type"; -import BattleScene from "../battle-scene"; -import { getLevelTotalExp } from "../data/exp"; -import { MAX_PER_TYPE_POKEBALLS, PokeballType } from "../data/pokeball"; -import Pokemon, { PlayerPokemon } from "../field/pokemon"; -import { addTextObject, TextStyle } from "../ui/text"; -import { Type } from "../data/type"; -import { EvolutionPhase } from "../phases/evolution-phase"; -import { FusionSpeciesFormEvolution, pokemonEvolutions, pokemonPrevolutions } from "../data/pokemon-evolutions"; -import { getPokemonNameWithAffix } from "../messages"; -import * as Utils from "../utils"; -import { getBerryEffectFunc, getBerryPredicate } from "../data/berry"; +import * as ModifierTypes from "#app/modifier/modifier-type"; +import { getModifierType, ModifierType, modifierTypes } from "#app/modifier/modifier-type"; +import BattleScene from "#app/battle-scene"; +import { getLevelTotalExp } from "#app/data/exp"; +import { MAX_PER_TYPE_POKEBALLS, PokeballType } from "#app/data/pokeball"; +import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; +import { addTextObject, TextStyle } from "#app/ui/text"; +import { Type } from "#app/data/type"; +import { EvolutionPhase } from "#app/phases/evolution-phase"; +import { FusionSpeciesFormEvolution, pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; +import { getPokemonNameWithAffix } from "#app/messages"; +import * as Utils from "#app/utils"; +import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; import { getStatusEffectHealText, StatusEffect } from "#app/data/status-effect"; -import { achvs } from "../system/achv"; -import { VoucherType } from "../system/voucher"; -import { FormChangeItem, SpeciesFormChangeItemTrigger, SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeTeraTrigger } from "../data/pokemon-forms"; +import { achvs } from "#app/system/achv"; +import { VoucherType } from "#app/system/voucher"; +import { FormChangeItem, SpeciesFormChangeItemTrigger, SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms"; import { Nature } from "#app/data/nature"; import Overrides from "#app/overrides"; import { Command } from "#app/ui/command-ui-handler"; import { Species } from "#enums/species"; -import { BATTLE_STATS, type PermanentStat, Stat, TEMP_BATTLE_STATS, type TempBattleStat } from "#app/enums/stat"; +import { BATTLE_STATS, type PermanentStat, Stat, TEMP_BATTLE_STATS, type TempBattleStat } from "#enums/stat"; import i18next from "i18next"; - import { allMoves } from "#app/data/move"; -import { Abilities } from "#app/enums/abilities"; +import { Abilities } from "#enums/abilities"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { LevelUpPhase } from "#app/phases/level-up-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index a374f885d3f..5e5031c1d06 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -2,7 +2,7 @@ import BattleScene from "#app/battle-scene"; import { TurnCommand, BattleType } from "#app/battle"; import { TrappedTag, EncoreTag } from "#app/data/battler-tags"; import { MoveTargetSet, getMoveTargets } from "#app/data/move"; -import { speciesStarters } from "#app/data/pokemon-species"; +import { speciesStarterCosts } from "#app/data/balance/starters"; import { Abilities } from "#app/enums/abilities"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Biome } from "#app/enums/biome"; @@ -126,7 +126,7 @@ export class CommandPhase extends FieldPhase { } break; case Command.BALL: - const notInDex = (this.scene.getEnemyField().filter(p => p.isActive(true)).some(p => !p.scene.gameData.dexData[p.species.speciesId].caughtAttr) && this.scene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarters).length - 1); + const notInDex = (this.scene.getEnemyField().filter(p => p.isActive(true)).some(p => !p.scene.gameData.dexData[p.species.speciesId].caughtAttr) && this.scene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarterCosts).length - 1); if (this.scene.arena.biomeType === Biome.END && (!this.scene.gameMode.isClassic || this.scene.gameMode.isFreshStartChallenge() || notInDex )) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.MESSAGE); diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index 0c3c2fa5bcf..59b73fe9e11 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -1,17 +1,17 @@ import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; -import { Phase } from "../phase"; -import BattleScene from "../battle-scene"; -import { SpeciesFormEvolution } from "../data/pokemon-evolutions"; -import EvolutionSceneHandler from "../ui/evolution-scene-handler"; -import * as Utils from "../utils"; -import { Mode } from "../ui/ui"; -import { cos, sin } from "../field/anims"; -import { PlayerPokemon } from "../field/pokemon"; -import { getTypeRgb } from "../data/type"; +import { Phase } from "#app/phase"; +import BattleScene from "#app/battle-scene"; +import { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; +import EvolutionSceneHandler from "#app/ui/evolution-scene-handler"; +import * as Utils from "#app/utils"; +import { Mode } from "#app/ui/ui"; +import { cos, sin } from "#app/field/anims"; +import { PlayerPokemon } from "#app/field/pokemon"; +import { getTypeRgb } from "#app/data/type"; import i18next from "i18next"; -import { getPokemonNameWithAffix } from "../messages"; -import { LearnMovePhase } from "./learn-move-phase"; -import { EndEvolutionPhase } from "./end-evolution-phase"; +import { getPokemonNameWithAffix } from "#app/messages"; +import { LearnMovePhase } from "#app/phases/learn-move-phase"; +import { EndEvolutionPhase } from "#app/phases/end-evolution-phase"; export class EvolutionPhase extends Phase { protected pokemon: PlayerPokemon; diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts index 1db47e9e289..9c444fc40f0 100644 --- a/src/phases/game-over-phase.ts +++ b/src/phases/game-over-phase.ts @@ -2,7 +2,7 @@ import { clientSessionId } from "#app/account"; import { BattleType } from "#app/battle"; import BattleScene from "#app/battle-scene"; import { getCharVariantFromDialogue } from "#app/data/dialogue"; -import { pokemonEvolutions } from "#app/data/pokemon-evolutions"; +import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species"; import { trainerConfigs } from "#app/data/trainer-config"; import Pokemon from "#app/field/pokemon"; diff --git a/src/phases/select-biome-phase.ts b/src/phases/select-biome-phase.ts index fe9b5b3996b..49c6c3ac3c0 100644 --- a/src/phases/select-biome-phase.ts +++ b/src/phases/select-biome-phase.ts @@ -1,5 +1,5 @@ import BattleScene from "#app/battle-scene"; -import { biomeLinks, getBiomeName } from "#app/data/biomes"; +import { biomeLinks, getBiomeName } from "#app/data/balance/biomes"; import { Biome } from "#app/enums/biome"; import { MoneyInterestModifier, MapModifier } from "#app/modifier/modifier"; import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; diff --git a/src/system/achv.ts b/src/system/achv.ts index 09ec74de50c..a355a027093 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -1,7 +1,7 @@ import { Modifier } from "typescript"; import BattleScene from "../battle-scene"; import { TurnHeldItemTransferModifier } from "../modifier/modifier"; -import { pokemonEvolutions } from "#app/data/pokemon-evolutions"; +import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import i18next from "i18next"; import * as Utils from "../utils"; import { PlayerGender } from "#enums/player-gender"; diff --git a/src/system/game-data.ts b/src/system/game-data.ts index af5e717c17b..b597a1b9aad 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -1,52 +1,53 @@ import i18next from "i18next"; -import BattleScene, { bypassLogin, PokeballCounts } from "../battle-scene"; -import Pokemon, { EnemyPokemon, PlayerPokemon } from "../field/pokemon"; -import { pokemonPrevolutions } from "../data/pokemon-evolutions"; -import PokemonSpecies, { allSpecies, getPokemonSpecies, noStarterFormKeys, speciesStarters } from "../data/pokemon-species"; -import * as Utils from "../utils"; +import BattleScene, { bypassLogin, PokeballCounts } from "#app/battle-scene"; +import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; +import PokemonSpecies, { allSpecies, getPokemonSpecies, noStarterFormKeys } from "#app/data/pokemon-species"; +import { speciesStarterCosts } from "#app/data/balance/starters"; +import * as Utils from "#app/utils"; import Overrides from "#app/overrides"; -import PokemonData from "./pokemon-data"; -import PersistentModifierData from "./modifier-data"; -import ArenaData from "./arena-data"; -import { Unlockables } from "./unlockables"; -import { GameModes, getGameMode } from "../game-mode"; -import { BattleType } from "../battle"; -import TrainerData from "./trainer-data"; -import { trainerConfigs } from "../data/trainer-config"; -import { resetSettings, setSetting, SettingKeys } from "./settings/settings"; -import { achvs } from "./achv"; -import EggData from "./egg-data"; -import { Egg } from "../data/egg"; -import { vouchers, VoucherType } from "./voucher"; +import PokemonData from "#app/system/pokemon-data"; +import PersistentModifierData from "#app/system/modifier-data"; +import ArenaData from "#app/system/arena-data"; +import { Unlockables } from "#app/system/unlockables"; +import { GameModes, getGameMode } from "#app/game-mode"; +import { BattleType } from "#app/battle"; +import TrainerData from "#app/system/trainer-data"; +import { trainerConfigs } from "#app/data/trainer-config"; +import { resetSettings, setSetting, SettingKeys } from "#app/system/settings/settings"; +import { achvs } from "#app/system/achv"; +import EggData from "#app/system/egg-data"; +import { Egg } from "#app/data/egg"; +import { vouchers, VoucherType } from "#app/system/voucher"; import { AES, enc } from "crypto-js"; -import { Mode } from "../ui/ui"; -import { clientSessionId, loggedInUser, updateUserInfo } from "../account"; -import { Nature } from "../data/nature"; -import { GameStats } from "./game-stats"; -import { Tutorial } from "../tutorial"; -import { speciesEggMoves } from "../data/egg-moves"; -import { allMoves } from "../data/move"; -import { TrainerVariant } from "../field/trainer"; +import { Mode } from "#app/ui/ui"; +import { clientSessionId, loggedInUser, updateUserInfo } from "#app/account"; +import { Nature } from "#app/data/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/move"; +import { TrainerVariant } from "#app/field/trainer"; import { Variant } from "#app/data/variant"; -import { setSettingGamepad, SettingGamepad, settingGamepadDefaults } from "./settings/settings-gamepad"; +import { setSettingGamepad, SettingGamepad, settingGamepadDefaults } from "#app/system/settings/settings-gamepad"; import { setSettingKeyboard, SettingKeyboard } from "#app/system/settings/settings-keyboard"; import { TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena"; -import * as Modifier from "../modifier/modifier"; +import * as Modifier from "#app/modifier/modifier"; import { StatusEffect } from "#app/data/status-effect"; -import ChallengeData from "./challenge-data"; +import ChallengeData from "#app/system/challenge-data"; import { Device } from "#enums/devices"; import { GameDataType } from "#enums/game-data-type"; import { Moves } from "#enums/moves"; import { PlayerGender } from "#enums/player-gender"; import { Species } from "#enums/species"; import { applyChallenges, ChallengeType } from "#app/data/challenge"; -import { WeatherType } from "#app/enums/weather-type"; +import { WeatherType } from "#enums/weather-type"; import { TerrainType } from "#app/data/terrain"; import { OutdatedPhase } from "#app/phases/outdated-phase"; import { ReloadSessionPhase } from "#app/phases/reload-session-phase"; import { RUN_HISTORY_LIMIT } from "#app/ui/run-history-ui-handler"; -import { applySessionDataPatches, applySettingsDataPatches, applySystemDataPatches } from "./version-converter"; -import { MysteryEncounterSaveData } from "../data/mystery-encounters/mystery-encounter-save-data"; +import { applySessionDataPatches, applySettingsDataPatches, applySystemDataPatches } from "#app/system/version-converter"; +import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PokerogueApiClearSessionData } from "#app/@types/pokerogue-api"; @@ -1537,7 +1538,7 @@ export class GameData { private initStarterData(): void { const starterData: StarterData = {}; - const starterSpeciesIds = Object.keys(speciesStarters).map(k => parseInt(k) as Species); + const starterSpeciesIds = Object.keys(speciesStarterCosts).map(k => parseInt(k) as Species); for (const speciesId of starterSpeciesIds) { starterData[speciesId] = { @@ -1617,7 +1618,7 @@ export class GameData { dexEntry.caughtAttr |= dexAttr; // Unlock ability - if (speciesStarters.hasOwnProperty(species.speciesId)) { + if (speciesStarterCosts.hasOwnProperty(species.speciesId)) { this.starterData[species.speciesId].abilityAttr |= pokemon.abilityIndex !== 1 || pokemon.species.ability2 ? 1 << pokemon.abilityIndex : AbilityAttr.ABILITY_HIDDEN; @@ -1673,7 +1674,7 @@ export class GameData { } }; - if (newCatch && speciesStarters.hasOwnProperty(species.speciesId)) { + if (newCatch && speciesStarterCosts.hasOwnProperty(species.speciesId)) { if (!showMessage) { resolve(true); return; @@ -1801,7 +1802,7 @@ export class GameData { } getStarterCount(dexEntryPredicate: (entry: DexEntry) => boolean): integer { - const starterKeys = Object.keys(speciesStarters); + const starterKeys = Object.keys(speciesStarterCosts); let starterCount = 0; for (const s of starterKeys) { const starterDexEntry = this.dexData[s]; @@ -1875,7 +1876,7 @@ export class GameData { } getSpeciesStarterValue(speciesId: Species): number { - const baseValue = speciesStarters[speciesId]; + const baseValue = speciesStarterCosts[speciesId]; let value = baseValue; const decrementValue = (value: number) => { diff --git a/src/test/evolution.test.ts b/src/test/evolution.test.ts index 07865d7e64a..1a5a2be996f 100644 --- a/src/test/evolution.test.ts +++ b/src/test/evolution.test.ts @@ -1,4 +1,4 @@ -import { pokemonEvolutions, SpeciesFormEvolution, SpeciesWildEvolutionDelay } from "#app/data/pokemon-evolutions"; +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/src/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts b/src/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts index fe3106ac114..fa3c97b56ce 100644 --- a/src/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts @@ -17,7 +17,7 @@ import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { CommandPhase } from "#app/phases/command-phase"; import { UncommonBreedEncounter } from "#app/data/mystery-encounters/encounters/uncommon-breed-encounter"; import { MovePhase } from "#app/phases/move-phase"; -import { speciesEggMoves } from "#app/data/egg-moves"; +import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { BerryType } from "#enums/berry-type"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; diff --git a/src/test/mystery-encounter/mystery-encounter-utils.test.ts b/src/test/mystery-encounter/mystery-encounter-utils.test.ts index 7958294737d..7541379be07 100644 --- a/src/test/mystery-encounter/mystery-encounter-utils.test.ts +++ b/src/test/mystery-encounter/mystery-encounter-utils.test.ts @@ -6,7 +6,8 @@ import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; import { StatusEffect } from "#app/data/status-effect"; import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; -import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { speciesStarterCosts } from "#app/data/balance/starters"; import { Type } from "#app/data/type"; import { getHighestLevelPlayerPokemon, getLowestLevelPlayerPokemon, getRandomPlayerPokemon, getRandomSpeciesByStarterTier, koPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getEncounterText, queueEncounterMessage, showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; @@ -209,7 +210,7 @@ describe("Mystery Encounter Utils", () => { const pokeSpecies = getPokemonSpecies(result); expect(pokeSpecies.speciesId).toBe(result); - expect(speciesStarters[result]).toBe(5); + expect(speciesStarterCosts[result]).toBe(5); }); it("gets species for a starter tier range", () => { @@ -217,8 +218,8 @@ describe("Mystery Encounter Utils", () => { const pokeSpecies = getPokemonSpecies(result); expect(pokeSpecies.speciesId).toBe(result); - expect(speciesStarters[result]).toBeGreaterThanOrEqual(5); - expect(speciesStarters[result]).toBeLessThanOrEqual(8); + expect(speciesStarterCosts[result]).toBeGreaterThanOrEqual(5); + expect(speciesStarterCosts[result]).toBeLessThanOrEqual(8); }); it("excludes species from search", () => { diff --git a/src/test/vitest.setup.ts b/src/test/vitest.setup.ts index 9778204aa7b..948180ca261 100644 --- a/src/test/vitest.setup.ts +++ b/src/test/vitest.setup.ts @@ -2,11 +2,11 @@ import "vitest-canvas-mock"; import { initLoggedInUser } from "#app/account"; import { initAbilities } from "#app/data/ability"; -import { initBiomes } from "#app/data/biomes"; -import { initEggMoves } from "#app/data/egg-moves"; +import { initBiomes } from "#app/data/balance/biomes"; +import { initEggMoves } from "#app/data/balance/egg-moves"; import { initMoves } from "#app/data/move"; import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters"; -import { initPokemonPrevolutions } from "#app/data/pokemon-evolutions"; +import { initPokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { initPokemonForms } from "#app/data/pokemon-forms"; import { initSpecies } from "#app/data/pokemon-species"; import { initAchievements } from "#app/system/achv"; diff --git a/src/ui/game-stats-ui-handler.ts b/src/ui/game-stats-ui-handler.ts index 967b3ae0dc4..69abdf85e9e 100644 --- a/src/ui/game-stats-ui-handler.ts +++ b/src/ui/game-stats-ui-handler.ts @@ -1,15 +1,15 @@ import Phaser from "phaser"; -import BattleScene from "../battle-scene"; -import { TextStyle, addTextObject } from "./text"; -import { Mode } from "./ui"; -import UiHandler from "./ui-handler"; -import { addWindow } from "./ui-theme"; -import * as Utils from "../utils"; -import { DexAttr, GameData } from "../system/game-data"; -import { speciesStarters } from "../data/pokemon-species"; -import {Button} from "#enums/buttons"; +import BattleScene from "#app/battle-scene"; +import { TextStyle, addTextObject } from "#app/ui/text"; +import { 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 { DexAttr, GameData } from "#app/system/game-data"; +import { speciesStarterCosts } from "#app/data/balance/starters"; +import { Button } from "#enums/buttons"; import i18next from "i18next"; -import { UiTheme } from "#app/enums/ui-theme"; +import { UiTheme } from "#enums/ui-theme"; interface DisplayStat { label_key?: string; @@ -34,14 +34,14 @@ const displayStats: DisplayStats = { label_key: "starters", sourceFunc: gameData => { const starterCount = gameData.getStarterCount(d => !!d.caughtAttr); - return `${starterCount} (${Math.floor((starterCount / Object.keys(speciesStarters).length) * 1000) / 10}%)`; + return `${starterCount} (${Math.floor((starterCount / Object.keys(speciesStarterCosts).length) * 1000) / 10}%)`; } }, shinyStartersUnlocked: { label_key: "shinyStarters", sourceFunc: gameData => { const starterCount = gameData.getStarterCount(d => !!(d.caughtAttr & DexAttr.SHINY)); - return `${starterCount} (${Math.floor((starterCount / Object.keys(speciesStarters).length) * 1000) / 10}%)`; + return `${starterCount} (${Math.floor((starterCount / Object.keys(speciesStarterCosts).length) * 1000) / 10}%)`; } }, dexSeen: { diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 6b6ce2aa789..72b34731842 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -1,22 +1,22 @@ -import BattleScene from "../battle-scene"; -import Pokemon, { MoveResult, PlayerPokemon, PokemonMove } from "../field/pokemon"; -import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "./text"; -import { Command } from "./command-ui-handler"; -import MessageUiHandler from "./message-ui-handler"; -import { Mode } from "./ui"; -import * as Utils from "../utils"; -import { PokemonFormChangeItemModifier, PokemonHeldItemModifier, SwitchEffectTransferModifier } from "../modifier/modifier"; -import { allMoves, ForceSwitchOutAttr } from "../data/move"; -import { getGenderColor, getGenderSymbol } from "../data/gender"; -import { StatusEffect } from "../data/status-effect"; -import PokemonIconAnimHandler, { PokemonIconAnimMode } from "./pokemon-icon-anim-handler"; -import { pokemonEvolutions } from "../data/pokemon-evolutions"; -import { addWindow } from "./ui-theme"; -import { SpeciesFormChangeItemTrigger, FormChangeItem } from "../data/pokemon-forms"; +import BattleScene from "#app/battle-scene"; +import Pokemon, { MoveResult, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "#app/ui/text"; +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 { PokemonFormChangeItemModifier, PokemonHeldItemModifier, SwitchEffectTransferModifier } from "#app/modifier/modifier"; +import { allMoves, ForceSwitchOutAttr } from "#app/data/move"; +import { getGenderColor, getGenderSymbol } from "#app/data/gender"; +import { StatusEffect } from "#app/data/status-effect"; +import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler"; +import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; +import { addWindow } from "#app/ui/ui-theme"; +import { SpeciesFormChangeItemTrigger, FormChangeItem } from "#app/data/pokemon-forms"; import { getVariantTint } from "#app/data/variant"; import {Button} from "#enums/buttons"; import { applyChallenges, ChallengeType } from "#app/data/challenge"; -import MoveInfoOverlay from "./move-info-overlay"; +import MoveInfoOverlay from "#app/ui/move-info-overlay"; import i18next from "i18next"; import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { Moves } from "#enums/moves"; diff --git a/src/ui/pokemon-hatch-info-container.ts b/src/ui/pokemon-hatch-info-container.ts index f8a9adced36..2c3f5110f53 100644 --- a/src/ui/pokemon-hatch-info-container.ts +++ b/src/ui/pokemon-hatch-info-container.ts @@ -1,14 +1,14 @@ -import PokemonInfoContainer from "./pokemon-info-container"; -import BattleScene from "../battle-scene"; -import { Gender } from "../data/gender"; -import { Type } from "../data/type"; -import * as Utils from "../utils"; -import { TextStyle, addTextObject } from "./text"; -import { speciesEggMoves } from "#app/data/egg-moves"; +import PokemonInfoContainer from "#app/ui/pokemon-info-container"; +import BattleScene from "#app/battle-scene"; +import { Gender } from "#app/data/gender"; +import { Type } from "#app/data/type"; +import * as Utils from "#app/utils"; +import { TextStyle, addTextObject } from "#app/ui/text"; +import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { allMoves } from "#app/data/move"; -import { Species } from "#app/enums/species"; +import { Species } from "#enums/species"; import { getEggTierForSpecies } from "#app/data/egg"; -import { starterColors } from "../battle-scene"; +import { starterColors } from "#app/battle-scene"; import { argbFromRgba } from "@material/material-color-utilities"; import { EggHatchData } from "#app/data/egg-hatch-data"; import { PlayerPokemon } from "#app/field/pokemon"; diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index cc1ba5ada07..38445f79e05 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1,51 +1,54 @@ -import { BattleSceneEventType, CandyUpgradeNotificationChangedEvent } from "../events/battle-scene"; -import { pokemonPrevolutions } from "#app/data/pokemon-evolutions"; +import { BattleSceneEventType, CandyUpgradeNotificationChangedEvent } from "#app/events/battle-scene"; +import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { Variant, getVariantTint, getVariantIcon } from "#app/data/variant"; import { argbFromRgba } from "@material/material-color-utilities"; import i18next from "i18next"; import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; -import BattleScene, { starterColors } from "../battle-scene"; -import { allAbilities } from "../data/ability"; -import { speciesEggMoves } from "../data/egg-moves"; -import { GrowthRate, getGrowthRateColor } from "../data/exp"; -import { Gender, getGenderColor, getGenderSymbol } from "../data/gender"; -import { allMoves } from "../data/move"; -import { Nature, getNatureName } from "../data/nature"; -import { pokemonFormChanges } from "../data/pokemon-forms"; -import { LevelMoves, pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "../data/pokemon-level-moves"; -import PokemonSpecies, { allSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities, POKERUS_STARTER_COUNT, getPokerusStarters } from "../data/pokemon-species"; -import { Type } from "../data/type"; -import { GameModes } from "../game-mode"; -import { AbilityAttr, DexAttr, DexAttrProps, DexEntry, StarterMoveset, StarterAttributes, StarterPreferences, StarterPrefs } from "../system/game-data"; -import { Tutorial, handleTutorial } from "../tutorial"; -import * as Utils from "../utils"; -import { OptionSelectItem } from "./abstact-option-select-ui-handler"; -import MessageUiHandler from "./message-ui-handler"; -import PokemonIconAnimHandler, { PokemonIconAnimMode } from "./pokemon-icon-anim-handler"; -import { StatsContainer } from "./stats-container"; -import { TextStyle, addBBCodeTextObject, addTextObject } from "./text"; -import { Mode } from "./ui"; -import { addWindow } from "./ui-theme"; +import BattleScene, { starterColors } from "#app/battle-scene"; +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/move"; +import { Nature, getNatureName } from "#app/data/nature"; +import { pokemonFormChanges } from "#app/data/pokemon-forms"; +import { LevelMoves, pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; +import PokemonSpecies, { allSpecies, getPokemonSpeciesForm, getPokerusStarters } from "#app/data/pokemon-species"; +import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; +import { starterPassiveAbilities } from "#app/data/balance/passives"; +import { Type } from "#app/data/type"; +import { GameModes } from "#app/game-mode"; +import { AbilityAttr, DexAttr, DexAttrProps, DexEntry, StarterMoveset, StarterAttributes, StarterPreferences, StarterPrefs } from "#app/system/game-data"; +import { Tutorial, handleTutorial } from "#app/tutorial"; +import * as Utils from "#app/utils"; +import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; +import MessageUiHandler from "#app/ui/message-ui-handler"; +import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler"; +import { StatsContainer } from "#app/ui/stats-container"; +import { TextStyle, addBBCodeTextObject, addTextObject } from "#app/ui/text"; +import { Mode } from "#app/ui/ui"; +import { addWindow } from "#app/ui/ui-theme"; 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 "../data/challenge"; -import MoveInfoOverlay from "./move-info-overlay"; +import * as Challenge from "#app/data/challenge"; +import MoveInfoOverlay from "#app/ui/move-info-overlay"; import { getEggTierForSpecies } from "#app/data/egg"; import { Device } from "#enums/devices"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { Button } from "#enums/buttons"; -import { EggSourceType } from "#app/enums/egg-source-types"; -import AwaitableUiHandler from "./awaitable-ui-handler"; -import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "./dropdown"; -import { StarterContainer } from "./starter-container"; -import { DropDownColumn, FilterBar } from "./filter-bar"; -import { ScrollBar } from "./scroll-bar"; +import { EggSourceType } from "#enums/egg-source-types"; +import AwaitableUiHandler from "#app/ui/awaitable-ui-handler"; +import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#app/ui/dropdown"; +import { StarterContainer } from "#app/ui/starter-container"; +import { DropDownColumn, FilterBar } from "#app/ui/filter-bar"; +import { ScrollBar } from "#app/ui/scroll-bar"; import { SelectChallengePhase } from "#app/phases/select-challenge-phase"; import { TitlePhase } from "#app/phases/title-phase"; -import { Abilities } from "#app/enums/abilities"; +import { Abilities } from "#enums/abilities"; +import { getPassiveCandyCount, getValueReductionCandyCounts, getSameSpeciesEggCandyCounts } from "#app/data/balance/starters"; export type StarterSelectCallback = (starters: Starter[]) => void; @@ -119,19 +122,6 @@ const languageSettings: { [key: string]: LanguageSetting } = { }, }; -const starterCandyCosts: { passive: integer, costReduction: [integer, integer], egg: integer }[] = [ - { passive: 40, costReduction: [25, 60], egg: 30 }, // 1 Cost - { passive: 40, costReduction: [25, 60], egg: 30 }, // 2 Cost - { passive: 35, costReduction: [20, 50], egg: 25 }, // 3 Cost - { passive: 30, costReduction: [15, 40], egg: 20 }, // 4 Cost - { passive: 25, costReduction: [12, 35], egg: 18 }, // 5 Cost - { passive: 20, costReduction: [10, 30], egg: 15 }, // 6 Cost - { passive: 15, costReduction: [8, 20], egg: 12 }, // 7 Cost - { passive: 10, costReduction: [5, 15], egg: 10 }, // 8 Cost - { passive: 10, costReduction: [5, 15], egg: 10 }, // 9 Cost - { passive: 10, costReduction: [5, 15], egg: 10 }, // 10 Cost -]; - const valueReductionMax = 2; // Position of UI elements @@ -142,18 +132,6 @@ const teamWindowY = 18; const teamWindowWidth = 34; const teamWindowHeight = 132; -function getPassiveCandyCount(baseValue: integer): integer { - return starterCandyCosts[baseValue - 1].passive; -} - -function getValueReductionCandyCounts(baseValue: integer): [integer, integer] { - return starterCandyCosts[baseValue - 1].costReduction; -} - -function getSameSpeciesEggCandyCounts(baseValue: integer): integer { - return starterCandyCosts[baseValue - 1].egg; -} - /** * Calculates the starter position for a Pokemon of a given UI index * @param index UI index to calculate the starter position of @@ -658,7 +636,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { starterBoxContainer.add(this.cursorObj); for (const species of allSpecies) { - if (!speciesStarters.hasOwnProperty(species.speciesId) || !species.isObtainable()) { + if (!speciesStarterCosts.hasOwnProperty(species.speciesId) || !species.isObtainable()) { continue; } @@ -1112,7 +1090,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { // Get this species ID's starter data const starterData = this.scene.gameData.starterData[speciesId]; - return starterData.candyCount >= getPassiveCandyCount(speciesStarters[speciesId]) + return starterData.candyCount >= getPassiveCandyCount(speciesStarterCosts[speciesId]) && !(starterData.passiveAttr & PassiveAttr.UNLOCKED); } @@ -1125,7 +1103,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { // Get this species ID's starter data const starterData = this.scene.gameData.starterData[speciesId]; - return starterData.candyCount >= getValueReductionCandyCounts(speciesStarters[speciesId])[starterData.valueReduction] + return starterData.candyCount >= getValueReductionCandyCounts(speciesStarterCosts[speciesId])[starterData.valueReduction] && starterData.valueReduction < valueReductionMax; } @@ -1138,7 +1116,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { // Get this species ID's starter data const starterData = this.scene.gameData.starterData[speciesId]; - return starterData.candyCount >= getSameSpeciesEggCandyCounts(speciesStarters[speciesId]); + return starterData.candyCount >= getSameSpeciesEggCandyCounts(speciesStarterCosts[speciesId]); } /** @@ -1732,7 +1710,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { // Unlock passive option if (!(passiveAttr & PassiveAttr.UNLOCKED)) { - const passiveCost = getPassiveCandyCount(speciesStarters[this.lastSpecies.speciesId]); + const passiveCost = getPassiveCandyCount(speciesStarterCosts[this.lastSpecies.speciesId]); options.push({ label: `x${passiveCost} ${i18next.t("starterSelectUiHandler:unlockPassive")} (${allAbilities[starterPassiveAbilities[this.lastSpecies.speciesId]].name})`, handler: () => { @@ -1768,7 +1746,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { // Reduce cost option const valueReduction = starterData.valueReduction; if (valueReduction < valueReductionMax) { - const reductionCost = getValueReductionCandyCounts(speciesStarters[this.lastSpecies.speciesId])[valueReduction]; + const reductionCost = getValueReductionCandyCounts(speciesStarterCosts[this.lastSpecies.speciesId])[valueReduction]; options.push({ label: `x${reductionCost} ${i18next.t("starterSelectUiHandler:reduceCost")}`, handler: () => { @@ -1802,7 +1780,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } // Same species egg menu option. - const sameSpeciesEggCost = getSameSpeciesEggCandyCounts(speciesStarters[this.lastSpecies.speciesId]); + const sameSpeciesEggCost = getSameSpeciesEggCandyCounts(speciesStarterCosts[this.lastSpecies.speciesId]); options.push({ label: `x${sameSpeciesEggCost} ${i18next.t("starterSelectUiHandler:sameSpeciesEgg")}`, handler: () => { @@ -2723,7 +2701,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { currentFriendship = 0; } - const friendshipCap = getStarterValueFriendshipCap(speciesStarters[speciesId]); + const friendshipCap = getStarterValueFriendshipCap(speciesStarterCosts[speciesId]); return { currentFriendship, friendshipCap }; } @@ -3356,7 +3334,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { updateStarterValueLabel(starter: StarterContainer): void { const speciesId = starter.species.speciesId; - const baseStarterValue = speciesStarters[speciesId]; + const baseStarterValue = speciesStarterCosts[speciesId]; const starterValue = this.scene.gameData.getSpeciesStarterValue(speciesId); starter.cost = starterValue; let valueStr = starterValue.toString(); diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index e93fa0713c0..28c3ccdb2e4 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -1,28 +1,28 @@ -import BattleScene, { starterColors } from "../battle-scene"; -import { Mode } from "./ui"; -import UiHandler from "./ui-handler"; -import * as Utils from "../utils"; -import { PlayerPokemon, PokemonMove } from "../field/pokemon"; -import { getStarterValueFriendshipCap, speciesStarters } from "../data/pokemon-species"; +import BattleScene, { starterColors } from "#app/battle-scene"; +import { Mode } from "#app/ui/ui"; +import UiHandler from "#app/ui/ui-handler"; +import * as Utils from "#app/utils"; +import { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { argbFromRgba } from "@material/material-color-utilities"; -import { Type, getTypeRgb } from "../data/type"; -import { TextStyle, addBBCodeTextObject, addTextObject, getBBCodeFrag } from "./text"; -import Move, { MoveCategory } from "../data/move"; -import { getPokeballAtlasKey } from "../data/pokeball"; -import { getGenderColor, getGenderSymbol } from "../data/gender"; -import { getLevelRelExp, getLevelTotalExp } from "../data/exp"; -import { PokemonHeldItemModifier } from "../modifier/modifier"; -import { StatusEffect } from "../data/status-effect"; -import { getBiomeName } from "../data/biomes"; -import { Nature, getNatureName, getNatureStatMultiplier } from "../data/nature"; -import { loggedInUser } from "../account"; +import { Type, getTypeRgb } from "#app/data/type"; +import { TextStyle, addBBCodeTextObject, addTextObject, getBBCodeFrag } from "#app/ui/text"; +import Move, { MoveCategory } from "#app/data/move"; +import { getPokeballAtlasKey } from "#app/data/pokeball"; +import { getGenderColor, getGenderSymbol } from "#app/data/gender"; +import { getLevelRelExp, getLevelTotalExp } from "#app/data/exp"; +import { PokemonHeldItemModifier } from "#app/modifier/modifier"; +import { StatusEffect } from "#app/data/status-effect"; +import { getBiomeName } from "#app/data/balance/biomes"; +import { Nature, getNatureName, getNatureStatMultiplier } from "#app/data/nature"; +import { loggedInUser } from "#app/account"; import { Variant, getVariantTint } from "#app/data/variant"; -import {Button} from "#enums/buttons"; -import { Ability } from "../data/ability"; +import { Button } from "#enums/buttons"; +import { Ability } from "#app/data/ability"; import i18next from "i18next"; -import {modifierSortFunc} from "../modifier/modifier"; +import { modifierSortFunc } from "#app/modifier/modifier"; import { PlayerGender } from "#enums/player-gender"; -import { Stat, PERMANENT_STATS, getStatKey } from "#app/enums/stat"; +import { Stat, PERMANENT_STATS, getStatKey } from "#enums/stat"; enum Page { PROFILE, @@ -333,7 +333,7 @@ export default class SummaryUiHandler extends UiHandler { currentFriendship = 0; } - const friendshipCap = getStarterValueFriendshipCap(speciesStarters[this.pokemon.species.getRootSpeciesId()]); + const friendshipCap = getStarterValueFriendshipCap(speciesStarterCosts[this.pokemon.species.getRootSpeciesId()]); const candyCropY = 16 - (16 * (currentFriendship / friendshipCap)); if (this.candyShadow.visible) { From ea261a039dbff14401d298d3ab3d7831cfd7211c Mon Sep 17 00:00:00 2001 From: "Adrian T." <68144167+torranx@users.noreply.github.com> Date: Thu, 3 Oct 2024 01:08:30 +0800 Subject: [PATCH 05/16] [Refactor] Cleanup test boilerplate script (#4547) --- create-test-boilerplate.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/create-test-boilerplate.js b/create-test-boilerplate.js index 6d9cde966d5..5f4bbc41198 100644 --- a/create-test-boilerplate.js +++ b/create-test-boilerplate.js @@ -1,9 +1,7 @@ /** - * This script creates a test boilerplate file for a move or ability. - * @param {string} type - The type of test to create. Either "move", "ability", - * or "item". - * @param {string} fileName - The name of the file to create. - * @example npm run create-test move tackle + * This script creates a test boilerplate file in the appropriate + * directory based on the type selected. + * @example npm run create-test */ import fs from "fs"; @@ -34,7 +32,7 @@ async function promptTestType() { console.log("Exiting..."); return process.exit(); } else if (!typeChoices.includes(typeAnswer.selectedOption)) { - console.error('Please provide a valid type ("move", "ability", or "item")!'); + console.error(`Please provide a valid type (${typeChoices.join(", ")})!`); return await promptTestType(); } @@ -102,7 +100,7 @@ async function runInteractive() { description = `Mystery Encounter - ${formattedName}`; break; default: - console.error('Invalid type. Please use "move", "ability", or "item".'); + console.error(`Invalid type. Please use one of the following: ${typeChoices.join(", ")}.`); process.exit(1); } From f634b7c0449c5eb29d5c6d3f70d15064f0af9df1 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:13:16 -0700 Subject: [PATCH 06/16] [Misc] Disable some leftover debug code (#4546) * Disable i18n debug in `.env` files * Disable leftover ME debug code --- .env.beta | 2 +- .env.development | 2 +- src/data/mystery-encounters/mystery-encounters.ts | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.env.beta b/.env.beta index 38888ec3d97..c4a7c154ffe 100644 --- a/.env.beta +++ b/.env.beta @@ -3,4 +3,4 @@ VITE_BYPASS_TUTORIAL=0 VITE_SERVER_URL=https://apibeta.pokerogue.net VITE_DISCORD_CLIENT_ID=1248062921129459756 VITE_GOOGLE_CLIENT_ID=955345393540-2k6lfftf0fdnb0krqmpthjnqavfvvf73.apps.googleusercontent.com -VITE_I18N_DEBUG=1 +VITE_I18N_DEBUG=0 diff --git a/.env.development b/.env.development index 6c92036270f..e4e5053016f 100644 --- a/.env.development +++ b/.env.development @@ -3,5 +3,5 @@ VITE_BYPASS_TUTORIAL=0 VITE_SERVER_URL=http://localhost:8001 VITE_DISCORD_CLIENT_ID=1234567890 VITE_GOOGLE_CLIENT_ID=1234567890 -VITE_I18N_DEBUG=1 +VITE_I18N_DEBUG=0 VITE_PORT=8000 diff --git a/src/data/mystery-encounters/mystery-encounters.ts b/src/data/mystery-encounters/mystery-encounters.ts index 14fd5d7d6d1..8fde06f3b14 100644 --- a/src/data/mystery-encounters/mystery-encounters.ts +++ b/src/data/mystery-encounters/mystery-encounters.ts @@ -363,6 +363,7 @@ export function initMysteryEncounters() { }); // Add ANY biome encounters to biome map + // eslint-disable-next-line let encounterBiomeTableLog = ""; mysteryEncountersByBiome.forEach((biomeEncounters, biome) => { anyBiomeEncounters.forEach(encounter => { @@ -374,5 +375,5 @@ export function initMysteryEncounters() { encounterBiomeTableLog += `${getBiomeName(biome).toUpperCase()}: [${biomeEncounters.map(type => MysteryEncounterType[type].toString().toLowerCase()).sort().join(", ")}]\n`; }); - console.debug("All Mystery Encounters by Biome:\n" + encounterBiomeTableLog); + //console.debug("All Mystery Encounters by Biome:\n" + encounterBiomeTableLog); } From 831efeb6bff8a8851d77768140e50e6ffa4dccdc Mon Sep 17 00:00:00 2001 From: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:33:12 +0200 Subject: [PATCH 07/16] =?UTF-8?q?[P2]=C2=A0Make=20weather=20damage=20round?= =?UTF-8?q?=20down=20for=20consistency=20(#4559)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fmake weather damage consistent with the rest of the game * [test] add some sandstorm and hail tests --- src/phases/weather-effect-phase.ts | 2 +- src/test/arena/weather_hail.test.ts | 18 ++++++++++-- src/test/arena/weather_sandstorm.test.ts | 37 ++++++++++++++++++++++-- 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/phases/weather-effect-phase.ts b/src/phases/weather-effect-phase.ts index 73de44389d0..b48ee342780 100644 --- a/src/phases/weather-effect-phase.ts +++ b/src/phases/weather-effect-phase.ts @@ -44,7 +44,7 @@ export class WeatherEffectPhase extends CommonAnimPhase { return; } - const damage = Math.ceil(pokemon.getMaxHp() / 16); + const damage = Utils.toDmgValue(pokemon.getMaxHp() / 16); this.scene.queueMessage(getWeatherDamageMessage(this.weather?.weatherType!, pokemon)!); // TODO: are those bangs correct? pokemon.damageAndUpdate(damage, HitResult.EFFECTIVE, false, false, true); diff --git a/src/test/arena/weather_hail.test.ts b/src/test/arena/weather_hail.test.ts index 31d20be2ded..2070e40dbcc 100644 --- a/src/test/arena/weather_hail.test.ts +++ b/src/test/arena/weather_hail.test.ts @@ -39,7 +39,7 @@ describe("Weather - Hail", () => { await game.phaseInterceptor.to("TurnEndPhase"); game.scene.getField(true).forEach(pokemon => { - expect(pokemon.hp).toBeLessThan(pokemon.getMaxHp() - Math.floor(pokemon.getMaxHp() / 16)); + expect(pokemon.hp).toBe(pokemon.getMaxHp() - Math.max(Math.floor(pokemon.getMaxHp() / 16), 1)); }); }); @@ -56,6 +56,20 @@ describe("Weather - Hail", () => { const enemyPokemon = game.scene.getEnemyPokemon()!; expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp()); - expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp() - Math.floor(enemyPokemon.getMaxHp() / 16)); + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp() - Math.max(Math.floor(enemyPokemon.getMaxHp() / 16), 1)); + }); + + it("does not inflict damage to Ice type Pokemon", async () => { + await game.classicMode.startBattle([Species.CLOYSTER]); + + game.move.select(Moves.SPLASH); + + await game.phaseInterceptor.to("TurnEndPhase"); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp()); + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp() - Math.max(Math.floor(enemyPokemon.getMaxHp() / 16), 1)); }); }); diff --git a/src/test/arena/weather_sandstorm.test.ts b/src/test/arena/weather_sandstorm.test.ts index 91188de6985..2419ca11b70 100644 --- a/src/test/arena/weather_sandstorm.test.ts +++ b/src/test/arena/weather_sandstorm.test.ts @@ -1,4 +1,6 @@ import { WeatherType } from "#app/data/weather"; +import { Abilities } from "#app/enums/abilities"; +import { Stat } from "#app/enums/stat"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; @@ -37,7 +39,7 @@ describe("Weather - Sandstorm", () => { await game.phaseInterceptor.to("TurnEndPhase"); game.scene.getField(true).forEach(pokemon => { - expect(pokemon.hp).toBeLessThan(pokemon.getMaxHp() - Math.floor(pokemon.getMaxHp() / 16)); + expect(pokemon.hp).toBe(pokemon.getMaxHp() - Math.max(Math.floor(pokemon.getMaxHp() / 16), 1)); }); }); @@ -53,6 +55,37 @@ describe("Weather - Sandstorm", () => { const enemyPokemon = game.scene.getEnemyPokemon()!; expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp()); - expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp() - Math.floor(enemyPokemon.getMaxHp() / 16)); + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp() - Math.max(Math.floor(enemyPokemon.getMaxHp() / 16), 1)); + }); + + it("does not inflict damage to Rock, Ground and Steel type Pokemon", async () => { + game.override + .battleType("double") + .enemySpecies(Species.SANDSHREW) + .ability(Abilities.BALL_FETCH) + .enemyAbility(Abilities.BALL_FETCH); + + await game.classicMode.startBattle([Species.ROCKRUFF, Species.KLINK]); + + game.move.select(Moves.SPLASH, 0); + game.move.select(Moves.SPLASH, 1); + + await game.phaseInterceptor.to("TurnEndPhase"); + + game.scene.getField(true).forEach(pokemon => { + expect(pokemon.hp).toBe(pokemon.getMaxHp()); + }); + }); + + it("increases Rock type Pokemon Sp.Def by 50%", async () => { + await game.classicMode.startBattle([Species.ROCKRUFF]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const playerSpdef = playerPokemon.getStat(Stat.SPDEF); + expect(playerPokemon.getEffectiveStat(Stat.SPDEF)).toBe(Math.floor(playerSpdef * 1.5)); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + const enemySpdef = enemyPokemon.getStat(Stat.SPDEF); + expect(enemyPokemon.getEffectiveStat(Stat.SPDEF)).toBe(enemySpdef); }); }); From 5f700590be857c960e8d90d56c7984b2b0896ed5 Mon Sep 17 00:00:00 2001 From: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:34:29 +0200 Subject: [PATCH 08/16] [QOL] Have Friendship Value be shown in summary (#4490) * Have Friendship Value be shown in summary * Fix how "fill" of icon is calced * Update src/locales/de/pokemon-summary.json * Actually add the images * Add correct image files * Update src/ui/summary-ui-handler.ts Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> * Update src/ui/summary-ui-handler.ts Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> * Update src/ui/summary-ui-handler.ts * Update src/ui/summary-ui-handler.ts Co-authored-by: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> * Made changed suggested in code review * Update src/locales/en/pokemon-summary.json --------- Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> Co-authored-by: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> --- public/images/ui/friendship.png | Bin 0 -> 174 bytes public/images/ui/friendship_overlay.png | Bin 0 -> 182 bytes public/images/ui/legacy/friendship.png | Bin 0 -> 174 bytes .../images/ui/legacy/friendship_overlay.png | Bin 0 -> 182 bytes src/loading-scene.ts | 2 ++ src/ui/summary-ui-handler.ts | 33 ++++++++++++++++++ 6 files changed, 35 insertions(+) create mode 100644 public/images/ui/friendship.png create mode 100644 public/images/ui/friendship_overlay.png create mode 100644 public/images/ui/legacy/friendship.png create mode 100644 public/images/ui/legacy/friendship_overlay.png diff --git a/public/images/ui/friendship.png b/public/images/ui/friendship.png new file mode 100644 index 0000000000000000000000000000000000000000..073adcadc7654da274704444d5cbc773c4a925a2 GIT binary patch literal 174 zcmeAS@N?(olHy`uVBq!ia0vp^{6Ngd!3-pihitnGq!^2X+?^QKos)S9VIoSmBi2x?XbGy$vQ#9~stsVD#Pi SG4lt|ECx?kKbLh*2~7YE>pBVm literal 0 HcmV?d00001 diff --git a/public/images/ui/friendship_overlay.png b/public/images/ui/friendship_overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..4a4724fbdc9f357caa342f3eab247bb0c8d9a876 GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^{6Ngd!3-pihitnGq!^2X+?^QKos)S9k`3?)aRt&o zf}(3>R6iTp|G#-T?dz^HKq1zWAirRS|3DyfK@DV*i>HfY2*=FO-jj?B3LJ;IcKkmx z&AFw{Ymo*+;sw?~gy?!Mb@LJuD>e>{GIF!lGYH Y8)6xQ9wz_32Q-ht)78&qol`;+0AP_n(*OVf literal 0 HcmV?d00001 diff --git a/public/images/ui/legacy/friendship.png b/public/images/ui/legacy/friendship.png new file mode 100644 index 0000000000000000000000000000000000000000..073adcadc7654da274704444d5cbc773c4a925a2 GIT binary patch literal 174 zcmeAS@N?(olHy`uVBq!ia0vp^{6Ngd!3-pihitnGq!^2X+?^QKos)S9VIoSmBi2x?XbGy$vQ#9~stsVD#Pi SG4lt|ECx?kKbLh*2~7YE>pBVm literal 0 HcmV?d00001 diff --git a/public/images/ui/legacy/friendship_overlay.png b/public/images/ui/legacy/friendship_overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..4a4724fbdc9f357caa342f3eab247bb0c8d9a876 GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^{6Ngd!3-pihitnGq!^2X+?^QKos)S9k`3?)aRt&o zf}(3>R6iTp|G#-T?dz^HKq1zWAirRS|3DyfK@DV*i>HfY2*=FO-jj?B3LJ;IcKkmx z&AFw{Ymo*+;sw?~gy?!Mb@LJuD>e>{GIF!lGYH Y8)6xQ9wz_32Q-ht)78&qol`;+0AP_n(*OVf literal 0 HcmV?d00001 diff --git a/src/loading-scene.ts b/src/loading-scene.ts index e71082ca8f5..3d4f4d165e6 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -44,6 +44,8 @@ export class LoadingScene extends SceneBase { this.loadAtlas("prompt", "ui"); this.loadImage("candy", "ui"); this.loadImage("candy_overlay", "ui"); + this.loadImage("friendship", "ui"); + this.loadImage("friendship_overlay", "ui"); this.loadImage("cursor", "ui"); this.loadImage("cursor_reverse", "ui"); for (const wv of Utils.getEnumValues(WindowVariant)) { diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 28c3ccdb2e4..d3985b225ff 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -87,6 +87,10 @@ export default class SummaryUiHandler extends UiHandler { private moveAccuracyText: Phaser.GameObjects.Text; private moveCategoryIcon: Phaser.GameObjects.Sprite; private summaryPageTransitionContainer: Phaser.GameObjects.Container; + private friendshipShadow: Phaser.GameObjects.Sprite; + private friendshipText: Phaser.GameObjects.Text; + private friendshipIcon: Phaser.GameObjects.Sprite; + private friendshipOverlay: Phaser.GameObjects.Sprite; private descriptionScrollTween: Phaser.Tweens.Tween | null; private moveCursorBlinkTimer: Phaser.Time.TimerEvent | null; @@ -187,6 +191,25 @@ export default class SummaryUiHandler extends UiHandler { this.candyCountText.setOrigin(0, 0); this.summaryContainer.add(this.candyCountText); + this.friendshipIcon = this.scene.add.sprite(13, -60, "friendship"); + this.friendshipIcon.setScale(0.8); + this.summaryContainer.add(this.friendshipIcon); + + this.friendshipOverlay = this.scene.add.sprite(13, -60, "friendship_overlay"); + this.friendshipOverlay.setScale(0.8); + this.summaryContainer.add(this.friendshipOverlay); + + this.friendshipShadow = this.scene.add.sprite(13, -60, "friendship"); + this.friendshipShadow.setTint(0x000000); + this.friendshipShadow.setAlpha(0.50); + this.friendshipShadow.setScale(0.8); + this.friendshipShadow.setInteractive(new Phaser.Geom.Rectangle(0, 0, 16, 16), Phaser.Geom.Rectangle.Contains); + this.summaryContainer.add(this.friendshipShadow); + + this.friendshipText = addTextObject(this.scene, 20, -66, "x0", TextStyle.WINDOW_ALT, { fontSize: "76px" }); + this.friendshipText.setOrigin(0, 0); + this.summaryContainer.add(this.friendshipText); + this.championRibbon = this.scene.add.image(88, -146, "champion_ribbon"); this.championRibbon.setOrigin(0, 0); //this.championRibbon.setScale(0.8); @@ -292,6 +315,7 @@ export default class SummaryUiHandler extends UiHandler { this.candyIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(colorScheme[0]))); this.candyOverlay.setTint(argbFromRgba(Utils.rgbHexToRgba(colorScheme[1]))); + this.numberText.setText(Utils.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)); @@ -345,6 +369,15 @@ export default class SummaryUiHandler extends UiHandler { this.candyShadow.setCrop(0, 0, 16, candyCropY); + if (this.friendshipShadow.visible) { + this.friendshipShadow.on("pointerover", () => this.scene.ui.showTooltip("", `${i18next.t("pokemonSummary:friendship")}`, true)); + this.friendshipShadow.on("pointerout", () => this.scene.ui.hideTooltip()); + } + + this.friendshipText.setText(`${this.pokemon?.friendship || "0"} / 255`); + + 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; From 8fc0d9a4292a6eadc676adc62e317e91390b9529 Mon Sep 17 00:00:00 2001 From: Lneacx Date: Thu, 3 Oct 2024 07:35:03 -0700 Subject: [PATCH 09/16] [P2] Fix Toxic to bypass semi-invulnerability when used by a Poison-type. (#4445) * Fix Toxic to bypass semi-invulnerability when used by a Poison-type. * Apply suggestion to refactor BerryPhase in Toxic move tests. Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Corrected missing quotes on a BerryPhase in Toxic.test. --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/phases/move-effect-phase.ts | 8 +++- src/test/moves/toxic.test.ts | 76 +++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 src/test/moves/toxic.test.ts diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index ca1fb87654f..33f2394cd1e 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -4,7 +4,7 @@ import { applyPreAttackAbAttrs, AddSecondStrikeAbAttr, IgnoreMoveEffectsAbAttr, import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag"; import { MoveAnim } from "#app/data/battle-anims"; import { BattlerTagLapseType, DamageProtectedTag, ProtectedTag, SemiInvulnerableTag, SubstituteTag } from "#app/data/battler-tags"; -import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr } from "#app/data/move"; +import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr, ToxicAccuracyAttr } from "#app/data/move"; import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Moves } from "#app/enums/moves"; @@ -14,6 +14,7 @@ import { PokemonMultiHitModifier, FlinchChanceModifier, EnemyAttackStatusEffectC import i18next from "i18next"; import * as Utils from "#app/utils"; import { PokemonPhase } from "./pokemon-phase"; +import { Type } from "#app/data/type"; export class MoveEffectPhase extends PokemonPhase { public move: PokemonMove; @@ -404,7 +405,10 @@ export class MoveEffectPhase extends PokemonPhase { } const semiInvulnerableTag = target.getTag(SemiInvulnerableTag); - if (semiInvulnerableTag && !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType)) { + if (semiInvulnerableTag + && !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType) + && !(this.move.getMove().getAttrs(ToxicAccuracyAttr) && user.isOfType(Type.POISON)) + ) { return false; } diff --git a/src/test/moves/toxic.test.ts b/src/test/moves/toxic.test.ts new file mode 100644 index 00000000000..2d023c201c1 --- /dev/null +++ b/src/test/moves/toxic.test.ts @@ -0,0 +1,76 @@ +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { StatusEffect } from "#enums/status-effect"; +import { BattlerIndex } from "#app/battle"; +import { allMoves } from "#app/data/move"; + +describe("Moves - Toxic", () => { + 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") + .moveset(Moves.TOXIC) + .enemySpecies(Species.MAGIKARP) + .enemyMoveset(Moves.SPLASH); + }); + + it("should be guaranteed to hit if user is Poison-type", async () => { + vi.spyOn(allMoves[Moves.TOXIC], "accuracy", "get").mockReturnValue(0); + await game.classicMode.startBattle([Species.TOXAPEX]); + + game.move.select(Moves.TOXIC); + await game.phaseInterceptor.to("BerryPhase", false); + + expect(game.scene.getEnemyPokemon()!.status?.effect).toBe(StatusEffect.TOXIC); + }); + + it("may miss if user is not Poison-type", async () => { + vi.spyOn(allMoves[Moves.TOXIC], "accuracy", "get").mockReturnValue(0); + await game.classicMode.startBattle([Species.UMBREON]); + + game.move.select(Moves.TOXIC); + await game.phaseInterceptor.to("BerryPhase", false); + + expect(game.scene.getEnemyPokemon()!.status).toBeUndefined(); + }); + + it("should hit semi-invulnerable targets if user is Poison-type", async () => { + vi.spyOn(allMoves[Moves.TOXIC], "accuracy", "get").mockReturnValue(0); + game.override.enemyMoveset(Moves.FLY); + await game.classicMode.startBattle([Species.TOXAPEX]); + + game.move.select(Moves.TOXIC); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.phaseInterceptor.to("BerryPhase", false); + + expect(game.scene.getEnemyPokemon()!.status?.effect).toBe(StatusEffect.TOXIC); + }); + + it("should miss semi-invulnerable targets if user is not Poison-type", async () => { + vi.spyOn(allMoves[Moves.TOXIC], "accuracy", "get").mockReturnValue(-1); + game.override.enemyMoveset(Moves.FLY); + await game.classicMode.startBattle([Species.UMBREON]); + + game.move.select(Moves.TOXIC); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.phaseInterceptor.to("BerryPhase", false); + + expect(game.scene.getEnemyPokemon()!.status).toBeUndefined(); + }); +}); From ea9e0c7909ad2bd64ee4cb832c24185580658649 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:17:51 -0400 Subject: [PATCH 10/16] [Move] Implement Scale Shot (#4551) * Scale Shot * Docstrings for StatStageChangeAttr * Add test for scale shot --- src/data/move.ts | 33 +++++++++++--- src/test/moves/scale_shot.test.ts | 74 +++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 src/test/moves/scale_shot.test.ts diff --git a/src/data/move.ts b/src/data/move.ts index 748f81cdd8b..13ab84e8898 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -2668,20 +2668,42 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr { } } +/** + * Attribute used for moves that change stat stages + * @param stats {@linkcode BattleStat} array of stats to be changed + * @param stages stages by which to change the stats, from -6 to 6 + * @param selfTarget whether the changes are applied to the user (true) or the target (false) + * @param condition {@linkcode MoveConditionFunc} optional condition to trigger the stat change + * @param firstHitOnly whether the stat change only applies on the first hit of a multi hit move + * @param moveEffectTrigger {@linkcode MoveEffectTrigger} the trigger for the effect to take place + * @param firstTargetOnly whether, if this is a multi target move, to only apply the effect after the first target is hit, rather than once for each target + * @param lastHitOnly whether the effect should only apply after the last hit of a multi hit move + * + * @extends MoveEffectAttr + * @see {@linkcode apply} + */ export class StatStageChangeAttr extends MoveEffectAttr { public stats: BattleStat[]; public stages: integer; private condition: MoveConditionFunc | null; private showMessage: boolean; - constructor(stats: BattleStat[], stages: integer, selfTarget?: boolean, condition?: MoveConditionFunc | null, showMessage: boolean = true, firstHitOnly: boolean = false, moveEffectTrigger: MoveEffectTrigger = MoveEffectTrigger.HIT, firstTargetOnly: boolean = false) { - super(selfTarget, moveEffectTrigger, firstHitOnly, false, firstTargetOnly); + constructor(stats: BattleStat[], stages: integer, selfTarget?: boolean, condition?: MoveConditionFunc | null, showMessage: boolean = true, firstHitOnly: boolean = false, moveEffectTrigger: MoveEffectTrigger = MoveEffectTrigger.HIT, firstTargetOnly: boolean = false, lastHitOnly: boolean = false) { + super(selfTarget, moveEffectTrigger, firstHitOnly, lastHitOnly, firstTargetOnly); this.stats = stats; this.stages = stages; this.condition = condition!; // TODO: is this bang correct? this.showMessage = showMessage; } + /** + * Attempts to change stats of the user or target (depending on value of selfTarget) if conditions are met + * @param user {@linkcode Pokemon} the user of the move + * @param target {@linkcode Pokemon} the target of the move + * @param move {@linkcode Move} the move + * @param args unused + * @returns whether stat stages were changed + */ apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean | Promise { if (!super.apply(user, target, move, args) || (this.condition && !this.condition(user, target, move))) { return false; @@ -9154,11 +9176,10 @@ export function initMoves() { .attr(ClearTerrainAttr) .condition((user, target, move) => !!user.scene.arena.terrain), new AttackMove(Moves.SCALE_SHOT, Type.DRAGON, MoveCategory.PHYSICAL, 25, 90, 20, -1, 0, 8) - //.attr(StatStageChangeAttr, Stat.SPD, 1, true) // TODO: Have boosts only apply at end of move, not after every hit - //.attr(StatStageChangeAttr, Stat.DEF, -1, true) + .attr(StatStageChangeAttr, [Stat.SPD], 1, true, null, true, false, MoveEffectTrigger.HIT, false, true) + .attr(StatStageChangeAttr, [Stat.DEF], -1, true, null, true, false, MoveEffectTrigger.HIT, false, true) .attr(MultiHitAttr) - .makesContact(false) - .partial(), + .makesContact(false), new AttackMove(Moves.METEOR_BEAM, Type.ROCK, MoveCategory.SPECIAL, 120, 90, 10, 100, 0, 8) .attr(ChargeAttr, ChargeAnim.METEOR_BEAM_CHARGING, i18next.t("moveTriggers:isOverflowingWithSpacePower", {pokemonName: "{USER}"}), null, true) .attr(StatStageChangeAttr, [ Stat.SPATK ], 1, true) diff --git a/src/test/moves/scale_shot.test.ts b/src/test/moves/scale_shot.test.ts new file mode 100644 index 00000000000..412ce6687c6 --- /dev/null +++ b/src/test/moves/scale_shot.test.ts @@ -0,0 +1,74 @@ +import { DamagePhase } from "#app/phases/damage-phase"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase"; +import { MoveEndPhase } from "#app/phases/move-end-phase"; +import { TurnEndPhase } from "#app/phases/turn-end-phase"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { Stat } from "#enums/stat"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest"; + +describe("Moves - Scale Shot", () => { + 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.SCALE_SHOT]) + .battleType("single") + .disableCrits() + .starterSpecies(Species.MINCCINO) + .ability(Abilities.NO_GUARD) + .passiveAbility(Abilities.SKILL_LINK) + .enemyAbility(Abilities.SHEER_FORCE) + .enemyPassiveAbility(Abilities.STALL) + .enemyMoveset(Moves.SKILL_SWAP) + .enemyLevel(5); + }); + + it("applies stat changes after last hit", async () => { + await game.classicMode.startBattle([Species.FORRETRESS]); + const minccino = game.scene.getPlayerPokemon()!; + game.move.select(Moves.SCALE_SHOT); + await game.phaseInterceptor.to(MoveEffectPhase); + await game.phaseInterceptor.to(DamagePhase); + await game.phaseInterceptor.to(MoveEffectPhase); + expect (minccino?.getStatStage(Stat.DEF)).toBe(0); + expect (minccino?.getStatStage(Stat.SPD)).toBe(0); + await game.phaseInterceptor.to(MoveEndPhase); + expect (minccino.getStatStage(Stat.DEF)).toBe(-1); + expect (minccino.getStatStage(Stat.SPD)).toBe(1); + }); + + it("unaffected by sheer force", async () => { + await game.classicMode.startBattle([Species.WOBBUFFET]); + const minccino = game.scene.getPlayerPokemon()!; + const wobbuffet = game.scene.getEnemyPokemon()!; + wobbuffet.setStat(Stat.HP, 100, true); + wobbuffet.hp = 100; + game.move.select(Moves.SCALE_SHOT); + await game.phaseInterceptor.to(TurnEndPhase); + const hpafter1 = wobbuffet.hp; + //effect not nullified by sheer force + expect (minccino.getStatStage(Stat.DEF)).toBe(-1); + expect (minccino.getStatStage(Stat.SPD)).toBe(1); + game.move.select(Moves.SCALE_SHOT); + await game.phaseInterceptor.to(MoveEndPhase); + const hpafter2 = wobbuffet.hp; + //check damage not boosted- make damage before sheer force a little lower than theoretical boosted sheer force damage + expect (100 - hpafter1).toBe(hpafter1 - hpafter2); + }); +}); From 54efd444975d9b42e52148eb2b32eef9a054b047 Mon Sep 17 00:00:00 2001 From: flx-sta <50131232+flx-sta@users.noreply.github.com> Date: Thu, 3 Oct 2024 08:38:17 -0700 Subject: [PATCH 11/16] [Refactor] Modifiers type inference v2 (#4294) * refactor: apply Modifiers type inference (pattern) Mirror from #1747 Co-authored-by: Dmitriy * fix: PokemonBaseStatTotalModifier.apply having a `[1]` left * fix: HeldItemTransferModifier.apply missing `...args: unknown[]` * Replace relative imports with absolute imports in `modifier.ts` * chore: fix TS1016* error [*] A required parameter cannot follow an optional parameter. * chore: fix namings, types and docs suggested by @torranx * replace: `IntegerHolder` with `NumberHolder` & `integer` with `number` * chore: apply review suggestions by @torranx * chore: address review feedback from @torranx * update: imports in `modifier-types` * update `lapse` calls in modifier.ts * fix lapse call in `battle-end-phase` * minor adjustments in `modifier.ts` * fix `EnemyEndureChanceModifier.apply` types * fix `EnemyAttackStatusEffectChanceModifier.apply` types * fix `EnemyTurnHealModifier.apply` types * fix `EnemyStatusEffectHealChanceModifier.apply` types --------- Co-authored-by: Dmitriy Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/battle-scene.ts | 90 +- src/modifier/modifier-type.ts | 311 ++++--- src/modifier/modifier.ts | 1542 ++++++++++++++++++++------------ src/phases/battle-end-phase.ts | 2 +- src/phases/berry-phase.ts | 4 +- 5 files changed, 1200 insertions(+), 749 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index df852126bc2..1bb0d6bfc4b 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2445,7 +2445,10 @@ export default class BattleScene extends SceneBase { } if ((modifier as PersistentModifier).add(this.modifiers, !!virtual, this)) { if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) { - success = modifier.apply([ this.getPokemonById(modifier.pokemonId), true ]); + const pokemon = this.getPokemonById(modifier.pokemonId); + if (pokemon) { + success = modifier.apply(pokemon, true); + } } if (playSound && !this.sound.get(soundName)) { this.playSound(soundName); @@ -2472,7 +2475,7 @@ export default class BattleScene extends SceneBase { for (const p in this.party) { const pokemon = this.party[p]; - const args: any[] = [ pokemon ]; + const args: unknown[] = []; if (modifier instanceof PokemonHpRestoreModifier) { if (!(modifier as PokemonHpRestoreModifier).fainted) { const hpRestoreMultiplier = new Utils.IntegerHolder(1); @@ -2485,8 +2488,8 @@ export default class BattleScene extends SceneBase { args.push(this.getPokemonById(modifier.fusePokemonId) as PlayerPokemon); } - if (modifier.shouldApply(args)) { - const result = modifier.apply(args); + if (modifier.shouldApply(pokemon, ...args)) { + const result = modifier.apply(pokemon, ...args); if (result instanceof Promise) { modifierPromises.push(result.then(s => success ||= s)); } else { @@ -2498,8 +2501,8 @@ export default class BattleScene extends SceneBase { return Promise.allSettled([this.party.map(p => p.updateInfo(instant)), ...modifierPromises]).then(() => resolve(success)); } else { const args = [ this ]; - if (modifier.shouldApply(args)) { - const result = modifier.apply(args); + if (modifier.shouldApply(...args)) { + const result = modifier.apply(...args); if (result instanceof Promise) { return result.then(success => resolve(success)); } else { @@ -2521,7 +2524,10 @@ export default class BattleScene extends SceneBase { } if ((modifier as PersistentModifier).add(this.enemyModifiers, false, this)) { if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) { - modifier.apply([ this.getPokemonById(modifier.pokemonId), true ]); + const pokemon = this.getPokemonById(modifier.pokemonId); + if (pokemon) { + modifier.apply(pokemon, true); + } } for (const rm of modifiersToRemove) { this.removeModifier(rm, true); @@ -2755,7 +2761,10 @@ export default class BattleScene extends SceneBase { if (modifierIndex > -1) { modifiers.splice(modifierIndex, 1); if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) { - modifier.apply([ this.getPokemonById(modifier.pokemonId), false ]); + const pokemon = this.getPokemonById(modifier.pokemonId); + if (pokemon) { + modifier.apply(pokemon, false); + } } return true; } @@ -2773,16 +2782,36 @@ export default class BattleScene extends SceneBase { return (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType); } - findModifiers(modifierFilter: ModifierPredicate, player: boolean = true): PersistentModifier[] { - return (player ? this.modifiers : this.enemyModifiers).filter(m => (modifierFilter as ModifierPredicate)(m)); + /** + * Get all of the modifiers that pass the `modifierFilter` function + * @param modifierFilter The function used to filter a target's modifiers + * @param isPlayer Whether to search the player (`true`) or the enemy (`false`); Defaults to `true` + * @returns the list of all modifiers that passed the `modifierFilter` function + */ + findModifiers(modifierFilter: ModifierPredicate, isPlayer: boolean = true): PersistentModifier[] { + return (isPlayer ? this.modifiers : this.enemyModifiers).filter(modifierFilter); } + /** + * Find the first modifier that pass the `modifierFilter` function + * @param modifierFilter The function used to filter a target's modifiers + * @param player Whether to search the player (`true`) or the enemy (`false`); Defaults to `true` + * @returns the first modifier that passed the `modifierFilter` function; `undefined` if none passed + */ findModifier(modifierFilter: ModifierPredicate, player: boolean = true): PersistentModifier | undefined { - return (player ? this.modifiers : this.enemyModifiers).find(m => (modifierFilter as ModifierPredicate)(m)); + return (player ? this.modifiers : this.enemyModifiers).find(modifierFilter); } - applyShuffledModifiers(scene: BattleScene, modifierType: Constructor, player: boolean = true, ...args: any[]): PersistentModifier[] { - let modifiers = (player ? this.modifiers : this.enemyModifiers).filter(m => m instanceof modifierType && m.shouldApply(args)); + /** + * Apply all modifiers that match `modifierType` in a random order + * @param scene {@linkcode BattleScene} used to randomize the order of modifiers + * @param modifierType The type of modifier to apply; must extend {@linkcode PersistentModifier} + * @param player Whether to search the player (`true`) or the enemy (`false`); Defaults to `true` + * @param ...args The list of arguments needed to invoke `modifierType.apply` + * @returns the list of all modifiers that matched `modifierType` and were applied. + */ + applyShuffledModifiers(scene: BattleScene, modifierType: Constructor, player: boolean = true, ...args: Parameters): T[] { + let modifiers = (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType && m.shouldApply(...args)); scene.executeWithSeedOffset(() => { const shuffleModifiers = mods => { if (mods.length < 1) { @@ -2796,15 +2825,23 @@ export default class BattleScene extends SceneBase { return this.applyModifiersInternal(modifiers, player, args); } - applyModifiers(modifierType: Constructor, player: boolean = true, ...args: any[]): PersistentModifier[] { - const modifiers = (player ? this.modifiers : this.enemyModifiers).filter(m => m instanceof modifierType && m.shouldApply(args)); + /** + * Apply all modifiers that match `modifierType` + * @param modifierType The type of modifier to apply; must extend {@linkcode PersistentModifier} + * @param player Whether to search the player (`true`) or the enemy (`false`); Defaults to `true` + * @param ...args The list of arguments needed to invoke `modifierType.apply` + * @returns the list of all modifiers that matched `modifierType` and were applied. + */ + applyModifiers(modifierType: Constructor, player: boolean = true, ...args: Parameters): T[] { + const modifiers = (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType && m.shouldApply(...args)); return this.applyModifiersInternal(modifiers, player, args); } - applyModifiersInternal(modifiers: PersistentModifier[], player: boolean, args: any[]): PersistentModifier[] { - const appliedModifiers: PersistentModifier[] = []; + /** Helper function to apply all passed modifiers */ + applyModifiersInternal(modifiers: T[], player: boolean, args: Parameters): T[] { + const appliedModifiers: T[] = []; for (const modifier of modifiers) { - if (modifier.apply(args)) { + if (modifier.apply(...args)) { console.log("Applied", modifier.type.name, !player ? "(enemy)" : ""); appliedModifiers.push(modifier); } @@ -2813,10 +2850,17 @@ export default class BattleScene extends SceneBase { return appliedModifiers; } - applyModifier(modifierType: Constructor, player: boolean = true, ...args: any[]): PersistentModifier | null { - const modifiers = (player ? this.modifiers : this.enemyModifiers).filter(m => m instanceof modifierType && m.shouldApply(args)); + /** + * Apply the first modifier that matches `modifierType` + * @param modifierType The type of modifier to apply; must extend {@linkcode PersistentModifier} + * @param player Whether to search the player (`true`) or the enemy (`false`); Defaults to `true` + * @param ...args The list of arguments needed to invoke `modifierType.apply` + * @returns the first modifier that matches `modifierType` and was applied; return `null` if none matched + */ + applyModifier(modifierType: Constructor, player: boolean = true, ...args: Parameters): T | null { + const modifiers = (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType && m.shouldApply(...args)); for (const modifier of modifiers) { - if (modifier.apply(args)) { + if (modifier.apply(...args)) { console.log("Applied", modifier.type.name, !player ? "(enemy)" : ""); return modifier; } @@ -2882,7 +2926,7 @@ export default class BattleScene extends SceneBase { } } - validateAchv(achv: Achv, args?: any[]): boolean { + validateAchv(achv: Achv, args?: unknown[]): boolean { if (!this.gameData.achvUnlocks.hasOwnProperty(achv.id) && achv.validate(this, args)) { this.gameData.achvUnlocks[achv.id] = new Date().getTime(); this.ui.achvBar.showAchv(achv); @@ -2895,7 +2939,7 @@ export default class BattleScene extends SceneBase { return false; } - validateVoucher(voucher: Voucher, args?: any[]): boolean { + validateVoucher(voucher: Voucher, args?: unknown[]): boolean { if (!this.gameData.voucherUnlocks.hasOwnProperty(voucher.id) && voucher.validate(this, args)) { this.gameData.voucherUnlocks[voucher.id] = new Date().getTime(); this.ui.achvBar.showAchv(voucher); diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 238d2f0debf..aac6a0ed572 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1,38 +1,35 @@ -import * as Modifiers from "#app/modifier/modifier"; -import { MoneyMultiplierModifier } from "#app/modifier/modifier"; -import { allMoves, AttackMove, selfStatLowerMoves } from "#app/data/move"; -import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS, PokeballType } from "#app/data/pokeball"; -import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import BattleScene from "#app/battle-scene"; import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { tmPoolTiers, tmSpecies } from "#app/data/balance/tms"; -import { Type } from "#app/data/type"; -import PartyUiHandler, { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party-ui-handler"; -import * as Utils from "#app/utils"; import { getBerryEffectDescription, getBerryName } from "#app/data/berry"; -import { Unlockables } from "#app/system/unlockables"; -import { getStatusEffectDescriptor, StatusEffect } from "#app/data/status-effect"; -import BattleScene from "#app/battle-scene"; -import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "#app/system/voucher"; -import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeCondition, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; -import { ModifierTier } from "#app/modifier/modifier-tier"; +import { allMoves, AttackMove, selfStatLowerMoves } from "#app/data/move"; import { getNatureName, getNatureStatMultiplier, Nature } from "#app/data/nature"; -import i18next from "i18next"; -import { getModifierTierTextTint } from "#app/ui/text"; +import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS, PokeballType } from "#app/data/pokeball"; +import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeCondition, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; +import { getStatusEffectDescriptor, StatusEffect } from "#app/data/status-effect"; +import { Type } from "#app/data/type"; +import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import { getPokemonNameWithAffix } from "#app/messages"; +import { AddPokeballModifier, AddVoucherModifier, AttackTypeBoosterModifier, BaseStatModifier, BerryModifier, BoostBugSpawnModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, CritBoosterModifier, DamageMoneyRewardModifier, DoubleBattleChanceBoosterModifier, EnemyAttackStatusEffectChanceModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, EvolutionItemModifier, EvolutionStatBoosterModifier, EvoTrackerModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, FusePokemonModifier, GigantamaxAccessModifier, HealingBoosterModifier, HealShopCostModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, IvScannerModifier, LevelIncrementBoosterModifier, LockModifierTiersModifier, MapModifier, MegaEvolutionAccessModifier, MoneyInterestModifier, MoneyMultiplierModifier, MoneyRewardModifier, MultipleParticipantExpBonusModifier, PokemonAllMovePpRestoreModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, PokemonInstantReviveModifier, PokemonLevelIncrementModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PokemonNatureChangeModifier, PokemonNatureWeightModifier, PokemonPpRestoreModifier, PokemonPpUpModifier, PokemonStatusHealModifier, PreserveBerryModifier, RememberMoveModifier, ResetNegativeStatStageModifier, ShinyRateBoosterModifier, SpeciesCritBoosterModifier, SpeciesStatBoosterModifier, SurviveDamageModifier, SwitchEffectTransferModifier, TempCritBoosterModifier, TempStatStageBoosterModifier, TerastallizeAccessModifier, TerastallizeModifier, TmModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier, type EnemyPersistentModifier, type Modifier, type PersistentModifier } from "#app/modifier/modifier"; +import { ModifierTier } from "#app/modifier/modifier-tier"; import Overrides from "#app/overrides"; +import { Unlockables } from "#app/system/unlockables"; +import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "#app/system/voucher"; +import PartyUiHandler, { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party-ui-handler"; +import { getModifierTierTextTint } from "#app/ui/text"; +import { formatMoney, getEnumKeys, getEnumValues, IntegerHolder, NumberHolder, padInt, randSeedInt, randSeedItem } from "#app/utils"; import { Abilities } from "#enums/abilities"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { getPokemonNameWithAffix } from "#app/messages"; -import { PermanentStat, TEMP_BATTLE_STATS, TempBattleStat, Stat, getStatKey } from "#enums/stat"; import { SpeciesFormKey } from "#enums/species-form-key"; +import { getStatKey, PermanentStat, Stat, TEMP_BATTLE_STATS, TempBattleStat } from "#enums/stat"; +import i18next from "i18next"; const outputModifierData = false; const useMaxWeightForOutput = false; -type Modifier = Modifiers.Modifier; - export enum ModifierPoolType { PLAYER, WILD, @@ -97,7 +94,7 @@ export class ModifierType { // Try multiple pool types in case of stolen items for (const type of poolTypes) { const pool = getModifierPoolForType(type); - for (const tier of Utils.getEnumValues(ModifierTier)) { + for (const tier of getEnumValues(ModifierTier)) { if (!pool.hasOwnProperty(tier)) { continue; } @@ -171,7 +168,7 @@ class AddPokeballModifierType extends ModifierType { private count: integer; constructor(iconImage: string, pokeballType: PokeballType, count: integer) { - super("", iconImage, (_type, _args) => new Modifiers.AddPokeballModifier(this, pokeballType, count), "pb", "se/pb_bounce_1"); + super("", iconImage, (_type, _args) => new AddPokeballModifier(this, pokeballType, count), "pb", "se/pb_bounce_1"); this.pokeballType = pokeballType; this.count = count; } @@ -198,7 +195,7 @@ class AddVoucherModifierType extends ModifierType { private count: integer; constructor(voucherType: VoucherType, count: integer) { - super("", getVoucherTypeIcon(voucherType), (_type, _args) => new Modifiers.AddVoucherModifier(this, voucherType, count), "voucher"); + super("", getVoucherTypeIcon(voucherType), (_type, _args) => new AddVoucherModifier(this, voucherType, count), "voucher"); this.count = count; this.voucherType = voucherType; } @@ -232,7 +229,7 @@ export class PokemonHeldItemModifierType extends PokemonModifierType { constructor(localeKey: string, iconImage: string, newModifierFunc: NewModifierFunc, group?: string, soundName?: string) { super(localeKey, iconImage, newModifierFunc, (pokemon: PlayerPokemon) => { const dummyModifier = this.newModifier(pokemon); - const matchingModifier = pokemon.scene.findModifier(m => m instanceof Modifiers.PokemonHeldItemModifier && m.pokemonId === pokemon.id && m.matchType(dummyModifier)) as Modifiers.PokemonHeldItemModifier; + const matchingModifier = pokemon.scene.findModifier(m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id && m.matchType(dummyModifier)) as PokemonHeldItemModifier; const maxStackCount = dummyModifier.getMaxStackCount(pokemon.scene); if (!maxStackCount) { return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.inoperable", { "pokemonName": getPokemonNameWithAffix(pokemon) }); @@ -244,8 +241,8 @@ export class PokemonHeldItemModifierType extends PokemonModifierType { }, group, soundName); } - newModifier(...args: any[]): Modifiers.PokemonHeldItemModifier { - return super.newModifier(...args) as Modifiers.PokemonHeldItemModifier; + newModifier(...args: any[]): PokemonHeldItemModifier { + return super.newModifier(...args) as PokemonHeldItemModifier; } } @@ -255,7 +252,7 @@ export class PokemonHpRestoreModifierType extends PokemonModifierType { protected healStatus: boolean; constructor(localeKey: string, iconImage: string, restorePoints: integer, restorePercent: integer, healStatus: boolean = false, newModifierFunc?: NewModifierFunc, selectFilter?: PokemonSelectFilter, group?: string) { - super(localeKey, iconImage, newModifierFunc || ((_type, args) => new Modifiers.PokemonHpRestoreModifier(this, (args[0] as PlayerPokemon).id, this.restorePoints, this.restorePercent, this.healStatus, false)), + super(localeKey, iconImage, newModifierFunc || ((_type, args) => new PokemonHpRestoreModifier(this, (args[0] as PlayerPokemon).id, this.restorePoints, this.restorePercent, this.healStatus, false)), selectFilter || ((pokemon: PlayerPokemon) => { if (!pokemon.hp || (pokemon.isFullHp() && (!this.healStatus || (!pokemon.status && !pokemon.getTag(BattlerTagType.CONFUSED))))) { return PartyUiHandler.NoEffectMessage; @@ -282,7 +279,7 @@ export class PokemonHpRestoreModifierType extends PokemonModifierType { export class PokemonReviveModifierType extends PokemonHpRestoreModifierType { constructor(localeKey: string, iconImage: string, restorePercent: integer) { - super(localeKey, iconImage, 0, restorePercent, false, (_type, args) => new Modifiers.PokemonHpRestoreModifier(this, (args[0] as PlayerPokemon).id, 0, this.restorePercent, false, true), + super(localeKey, iconImage, 0, restorePercent, false, (_type, args) => new PokemonHpRestoreModifier(this, (args[0] as PlayerPokemon).id, 0, this.restorePercent, false, true), ((pokemon: PlayerPokemon) => { if (!pokemon.isFainted()) { return PartyUiHandler.NoEffectMessage; @@ -305,7 +302,7 @@ export class PokemonReviveModifierType extends PokemonHpRestoreModifierType { export class PokemonStatusHealModifierType extends PokemonModifierType { constructor(localeKey: string, iconImage: string) { - super(localeKey, iconImage, ((_type, args) => new Modifiers.PokemonStatusHealModifier(this, (args[0] as PlayerPokemon).id)), + super(localeKey, iconImage, ((_type, args) => new PokemonStatusHealModifier(this, (args[0] as PlayerPokemon).id)), ((pokemon: PlayerPokemon) => { if (!pokemon.hp || (!pokemon.status && !pokemon.getTag(BattlerTagType.CONFUSED))) { return PartyUiHandler.NoEffectMessage; @@ -333,7 +330,7 @@ export class PokemonPpRestoreModifierType extends PokemonMoveModifierType { protected restorePoints: integer; constructor(localeKey: string, iconImage: string, restorePoints: integer) { - super(localeKey, iconImage, (_type, args) => new Modifiers.PokemonPpRestoreModifier(this, (args[0] as PlayerPokemon).id, (args[1] as integer), this.restorePoints), + super(localeKey, iconImage, (_type, args) => new PokemonPpRestoreModifier(this, (args[0] as PlayerPokemon).id, (args[1] as integer), this.restorePoints), (_pokemon: PlayerPokemon) => { return null; }, (pokemonMove: PokemonMove) => { @@ -358,7 +355,7 @@ export class PokemonAllMovePpRestoreModifierType extends PokemonModifierType { protected restorePoints: integer; constructor(localeKey: string, iconImage: string, restorePoints: integer) { - super(localeKey, iconImage, (_type, args) => new Modifiers.PokemonAllMovePpRestoreModifier(this, (args[0] as PlayerPokemon).id, this.restorePoints), + super(localeKey, iconImage, (_type, args) => new PokemonAllMovePpRestoreModifier(this, (args[0] as PlayerPokemon).id, this.restorePoints), (pokemon: PlayerPokemon) => { if (!pokemon.getMoveset().filter(m => m?.ppUsed).length) { return PartyUiHandler.NoEffectMessage; @@ -381,7 +378,7 @@ export class PokemonPpUpModifierType extends PokemonMoveModifierType { protected upPoints: integer; constructor(localeKey: string, iconImage: string, upPoints: integer) { - super(localeKey, iconImage, (_type, args) => new Modifiers.PokemonPpUpModifier(this, (args[0] as PlayerPokemon).id, (args[1] as integer), this.upPoints), + super(localeKey, iconImage, (_type, args) => new PokemonPpUpModifier(this, (args[0] as PlayerPokemon).id, (args[1] as integer), this.upPoints), (_pokemon: PlayerPokemon) => { return null; }, (pokemonMove: PokemonMove) => { @@ -403,7 +400,7 @@ export class PokemonNatureChangeModifierType extends PokemonModifierType { protected nature: Nature; constructor(nature: Nature) { - super("", `mint_${Utils.getEnumKeys(Stat).find(s => getNatureStatMultiplier(nature, Stat[s]) > 1)?.toLowerCase() || "neutral" }`, ((_type, args) => new Modifiers.PokemonNatureChangeModifier(this, (args[0] as PlayerPokemon).id, this.nature)), + super("", `mint_${getEnumKeys(Stat).find(s => getNatureStatMultiplier(nature, Stat[s]) > 1)?.toLowerCase() || "neutral" }`, ((_type, args) => new PokemonNatureChangeModifier(this, (args[0] as PlayerPokemon).id, this.nature)), ((pokemon: PlayerPokemon) => { if (pokemon.getNature() === this.nature) { return PartyUiHandler.NoEffectMessage; @@ -425,7 +422,7 @@ export class PokemonNatureChangeModifierType extends PokemonModifierType { export class RememberMoveModifierType extends PokemonModifierType { constructor(localeKey: string, iconImage: string, group?: string) { - super(localeKey, iconImage, (type, args) => new Modifiers.RememberMoveModifier(type, (args[0] as PlayerPokemon).id, (args[1] as integer)), + super(localeKey, iconImage, (type, args) => new RememberMoveModifier(type, (args[0] as PlayerPokemon).id, (args[1] as integer)), (pokemon: PlayerPokemon) => { if (!pokemon.getLearnableLevelMoves().length) { return PartyUiHandler.NoEffectMessage; @@ -439,7 +436,7 @@ export class DoubleBattleChanceBoosterModifierType extends ModifierType { private maxBattles: number; constructor(localeKey: string, iconImage: string, maxBattles: number) { - super(localeKey, iconImage, (_type, _args) => new Modifiers.DoubleBattleChanceBoosterModifier(this, maxBattles), "lure"); + super(localeKey, iconImage, (_type, _args) => new DoubleBattleChanceBoosterModifier(this, maxBattles), "lure"); this.maxBattles = maxBattles; } @@ -458,7 +455,7 @@ export class TempStatStageBoosterModifierType extends ModifierType implements Ge constructor(stat: TempBattleStat) { const nameKey = TempStatStageBoosterModifierTypeGenerator.items[stat]; - super("", nameKey, (_type, _args) => new Modifiers.TempStatStageBoosterModifier(this, this.stat, 5)); + super("", nameKey, (_type, _args) => new TempStatStageBoosterModifier(this, this.stat, 5)); this.stat = stat; this.nameKey = nameKey; @@ -485,7 +482,7 @@ export class BerryModifierType extends PokemonHeldItemModifierType implements Ge private berryType: BerryType; constructor(berryType: BerryType) { - super("", `${BerryType[berryType].toLowerCase()}_berry`, (type, args) => new Modifiers.BerryModifier(type, (args[0] as Pokemon).id, berryType), "berry"); + super("", `${BerryType[berryType].toLowerCase()}_berry`, (type, args) => new BerryModifier(type, (args[0] as Pokemon).id, berryType), "berry"); this.berryType = berryType; } @@ -550,7 +547,7 @@ export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType i constructor(moveType: Type, boostPercent: integer) { super("", `${getAttackTypeBoosterItemName(moveType)?.replace(/[ \-]/g, "_").toLowerCase()}`, - (_type, args) => new Modifiers.AttackTypeBoosterModifier(this, (args[0] as Pokemon).id, moveType, boostPercent)); + (_type, args) => new AttackTypeBoosterModifier(this, (args[0] as Pokemon).id, moveType, boostPercent)); this.moveType = moveType; this.boostPercent = boostPercent; @@ -573,7 +570,7 @@ export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType i export type SpeciesStatBoosterItem = keyof typeof SpeciesStatBoosterModifierTypeGenerator.items; /** - * Modifier type for {@linkcode Modifiers.SpeciesStatBoosterModifier} + * Modifier type for {@linkcode SpeciesStatBoosterModifier} * @extends PokemonHeldItemModifierType * @implements GeneratedPersistentModifierType */ @@ -582,7 +579,7 @@ export class SpeciesStatBoosterModifierType extends PokemonHeldItemModifierType constructor(key: SpeciesStatBoosterItem) { const item = SpeciesStatBoosterModifierTypeGenerator.items[key]; - super(`modifierType:SpeciesBoosterItem.${key}`, key.toLowerCase(), (type, args) => new Modifiers.SpeciesStatBoosterModifier(type, (args[0] as Pokemon).id, item.stats, item.multiplier, item.species)); + super(`modifierType:SpeciesBoosterItem.${key}`, key.toLowerCase(), (type, args) => new SpeciesStatBoosterModifier(type, (args[0] as Pokemon).id, item.stats, item.multiplier, item.species)); this.key = key; } @@ -594,12 +591,12 @@ export class SpeciesStatBoosterModifierType extends PokemonHeldItemModifierType export class PokemonLevelIncrementModifierType extends PokemonModifierType { constructor(localeKey: string, iconImage: string) { - super(localeKey, iconImage, (_type, args) => new Modifiers.PokemonLevelIncrementModifier(this, (args[0] as PlayerPokemon).id), (_pokemon: PlayerPokemon) => null); + super(localeKey, iconImage, (_type, args) => new PokemonLevelIncrementModifier(this, (args[0] as PlayerPokemon).id), (_pokemon: PlayerPokemon) => null); } getDescription(scene: BattleScene): string { let levels = 1; - const hasCandyJar = scene.modifiers.find(modifier => modifier instanceof Modifiers.LevelIncrementBoosterModifier); + const hasCandyJar = scene.modifiers.find(modifier => modifier instanceof LevelIncrementBoosterModifier); if (hasCandyJar) { levels += hasCandyJar.stackCount; } @@ -609,12 +606,12 @@ export class PokemonLevelIncrementModifierType extends PokemonModifierType { export class AllPokemonLevelIncrementModifierType extends ModifierType { constructor(localeKey: string, iconImage: string) { - super(localeKey, iconImage, (_type, _args) => new Modifiers.PokemonLevelIncrementModifier(this, -1)); + super(localeKey, iconImage, (_type, _args) => new PokemonLevelIncrementModifier(this, -1)); } getDescription(scene: BattleScene): string { let levels = 1; - const hasCandyJar = scene.modifiers.find(modifier => modifier instanceof Modifiers.LevelIncrementBoosterModifier); + const hasCandyJar = scene.modifiers.find(modifier => modifier instanceof LevelIncrementBoosterModifier); if (hasCandyJar) { levels += hasCandyJar.stackCount; } @@ -628,7 +625,7 @@ export class BaseStatBoosterModifierType extends PokemonHeldItemModifierType imp constructor(stat: PermanentStat) { const key = BaseStatBoosterModifierTypeGenerator.items[stat]; - super("", key, (_type, args) => new Modifiers.BaseStatModifier(this, (args[0] as Pokemon).id, this.stat)); + super("", key, (_type, args) => new BaseStatModifier(this, (args[0] as Pokemon).id, this.stat)); this.stat = stat; this.key = key; @@ -654,7 +651,7 @@ export class PokemonBaseStatTotalModifierType extends PokemonHeldItemModifierTyp private readonly statModifier: integer; constructor(statModifier: integer) { - super("modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE", "berry_juice", (_type, args) => new Modifiers.PokemonBaseStatTotalModifier(this, (args[0] as Pokemon).id, this.statModifier)); + super("modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE", "berry_juice", (_type, args) => new PokemonBaseStatTotalModifier(this, (args[0] as Pokemon).id, this.statModifier)); this.statModifier = statModifier; } @@ -679,7 +676,7 @@ export class PokemonBaseStatFlatModifierType extends PokemonHeldItemModifierType private readonly stats: Stat[]; constructor(statModifier: integer, stats: Stat[]) { - super("modifierType:ModifierType.MYSTERY_ENCOUNTER_OLD_GATEAU", "old_gateau", (_type, args) => new Modifiers.PokemonBaseStatFlatModifier(this, (args[0] as Pokemon).id, this.statModifier, this.stats)); + super("modifierType:ModifierType.MYSTERY_ENCOUNTER_OLD_GATEAU", "old_gateau", (_type, args) => new PokemonBaseStatFlatModifier(this, (args[0] as Pokemon).id, this.statModifier, this.stats)); this.statModifier = statModifier; this.stats = stats; } @@ -700,7 +697,7 @@ class AllPokemonFullHpRestoreModifierType extends ModifierType { private descriptionKey: string; constructor(localeKey: string, iconImage: string, descriptionKey?: string, newModifierFunc?: NewModifierFunc) { - super(localeKey, iconImage, newModifierFunc || ((_type, _args) => new Modifiers.PokemonHpRestoreModifier(this, -1, 0, 100, false))); + super(localeKey, iconImage, newModifierFunc || ((_type, _args) => new PokemonHpRestoreModifier(this, -1, 0, 100, false))); this.descriptionKey = descriptionKey!; // TODO: is this bang correct? } @@ -712,7 +709,7 @@ class AllPokemonFullHpRestoreModifierType extends ModifierType { class AllPokemonFullReviveModifierType extends AllPokemonFullHpRestoreModifierType { constructor(localeKey: string, iconImage: string) { - super(localeKey, iconImage, "modifierType:ModifierType.AllPokemonFullReviveModifierType", (_type, _args) => new Modifiers.PokemonHpRestoreModifier(this, -1, 0, 100, false, true)); + super(localeKey, iconImage, "modifierType:ModifierType.AllPokemonFullReviveModifierType", (_type, _args) => new PokemonHpRestoreModifier(this, -1, 0, 100, false, true)); } } @@ -721,16 +718,16 @@ export class MoneyRewardModifierType extends ModifierType { private moneyMultiplierDescriptorKey: string; constructor(localeKey: string, iconImage: string, moneyMultiplier: number, moneyMultiplierDescriptorKey: string) { - super(localeKey, iconImage, (_type, _args) => new Modifiers.MoneyRewardModifier(this, moneyMultiplier), "money", "se/buy"); + super(localeKey, iconImage, (_type, _args) => new MoneyRewardModifier(this, moneyMultiplier), "money", "se/buy"); this.moneyMultiplier = moneyMultiplier; this.moneyMultiplierDescriptorKey = moneyMultiplierDescriptorKey; } getDescription(scene: BattleScene): string { - const moneyAmount = new Utils.IntegerHolder(scene.getWaveMoneyAmount(this.moneyMultiplier)); + const moneyAmount = new IntegerHolder(scene.getWaveMoneyAmount(this.moneyMultiplier)); scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); - const formattedMoney = Utils.formatMoney(scene.moneyFormat, moneyAmount.value); + const formattedMoney = formatMoney(scene.moneyFormat, moneyAmount.value); return i18next.t("modifierType:ModifierType.MoneyRewardModifierType.description", { moneyMultiplier: i18next.t(this.moneyMultiplierDescriptorKey as any), @@ -743,7 +740,7 @@ export class ExpBoosterModifierType extends ModifierType { private boostPercent: integer; constructor(localeKey: string, iconImage: string, boostPercent: integer) { - super(localeKey, iconImage, () => new Modifiers.ExpBoosterModifier(this, boostPercent)); + super(localeKey, iconImage, () => new ExpBoosterModifier(this, boostPercent)); this.boostPercent = boostPercent; } @@ -757,7 +754,7 @@ export class PokemonExpBoosterModifierType extends PokemonHeldItemModifierType { private boostPercent: integer; constructor(localeKey: string, iconImage: string, boostPercent: integer) { - super(localeKey, iconImage, (_type, args) => new Modifiers.PokemonExpBoosterModifier(this, (args[0] as Pokemon).id, boostPercent)); + super(localeKey, iconImage, (_type, args) => new PokemonExpBoosterModifier(this, (args[0] as Pokemon).id, boostPercent)); this.boostPercent = boostPercent; } @@ -769,7 +766,7 @@ export class PokemonExpBoosterModifierType extends PokemonHeldItemModifierType { export class PokemonFriendshipBoosterModifierType extends PokemonHeldItemModifierType { constructor(localeKey: string, iconImage: string) { - super(localeKey, iconImage, (_type, args) => new Modifiers.PokemonFriendshipBoosterModifier(this, (args[0] as Pokemon).id)); + super(localeKey, iconImage, (_type, args) => new PokemonFriendshipBoosterModifier(this, (args[0] as Pokemon).id)); } getDescription(scene: BattleScene): string { @@ -781,7 +778,7 @@ export class PokemonMoveAccuracyBoosterModifierType extends PokemonHeldItemModif private amount: integer; constructor(localeKey: string, iconImage: string, amount: integer, group?: string, soundName?: string) { - super(localeKey, iconImage, (_type, args) => new Modifiers.PokemonMoveAccuracyBoosterModifier(this, (args[0] as Pokemon).id, amount), group, soundName); + super(localeKey, iconImage, (_type, args) => new PokemonMoveAccuracyBoosterModifier(this, (args[0] as Pokemon).id, amount), group, soundName); this.amount = amount; } @@ -793,7 +790,7 @@ export class PokemonMoveAccuracyBoosterModifierType extends PokemonHeldItemModif export class PokemonMultiHitModifierType extends PokemonHeldItemModifierType { constructor(localeKey: string, iconImage: string) { - super(localeKey, iconImage, (type, args) => new Modifiers.PokemonMultiHitModifier(type as PokemonMultiHitModifierType, (args[0] as Pokemon).id)); + super(localeKey, iconImage, (type, args) => new PokemonMultiHitModifier(type as PokemonMultiHitModifierType, (args[0] as Pokemon).id)); } getDescription(scene: BattleScene): string { @@ -805,7 +802,7 @@ export class TmModifierType extends PokemonModifierType { public moveId: Moves; constructor(moveId: Moves) { - super("", `tm_${Type[allMoves[moveId].type].toLowerCase()}`, (_type, args) => new Modifiers.TmModifier(this, (args[0] as PlayerPokemon).id), + super("", `tm_${Type[allMoves[moveId].type].toLowerCase()}`, (_type, args) => new TmModifier(this, (args[0] as PlayerPokemon).id), (pokemon: PlayerPokemon) => { if (pokemon.compatibleTms.indexOf(moveId) === -1 || pokemon.getMoveset().filter(m => m?.moveId === moveId).length) { return PartyUiHandler.NoEffectMessage; @@ -818,7 +815,7 @@ export class TmModifierType extends PokemonModifierType { get name(): string { return i18next.t("modifierType:ModifierType.TmModifierType.name", { - moveId: Utils.padInt(Object.keys(tmSpecies).indexOf(this.moveId.toString()) + 1, 3), + moveId: padInt(Object.keys(tmSpecies).indexOf(this.moveId.toString()) + 1, 3), moveName: allMoves[this.moveId].name, }); } @@ -832,7 +829,7 @@ export class EvolutionItemModifierType extends PokemonModifierType implements Ge public evolutionItem: EvolutionItem; constructor(evolutionItem: EvolutionItem) { - super("", EvolutionItem[evolutionItem].toLowerCase(), (_type, args) => new Modifiers.EvolutionItemModifier(this, (args[0] as PlayerPokemon).id), + super("", EvolutionItem[evolutionItem].toLowerCase(), (_type, args) => new EvolutionItemModifier(this, (args[0] as PlayerPokemon).id), (pokemon: PlayerPokemon) => { if (pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) && pokemonEvolutions[pokemon.species.speciesId].filter(e => e.item === this.evolutionItem && (!e.condition || e.condition.predicate(pokemon)) && (e.preFormKey === null || e.preFormKey === pokemon.getFormKey())).length && (pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX)) { @@ -868,7 +865,7 @@ export class FormChangeItemModifierType extends PokemonModifierType implements G public formChangeItem: FormChangeItem; constructor(formChangeItem: FormChangeItem) { - super("", FormChangeItem[formChangeItem].toLowerCase(), (_type, args) => new Modifiers.PokemonFormChangeItemModifier(this, (args[0] as PlayerPokemon).id, formChangeItem, true), + super("", FormChangeItem[formChangeItem].toLowerCase(), (_type, args) => new PokemonFormChangeItemModifier(this, (args[0] as PlayerPokemon).id, formChangeItem, true), (pokemon: PlayerPokemon) => { // Make sure the Pokemon has alternate forms if (pokemonFormChanges.hasOwnProperty(pokemon.species.speciesId) @@ -902,7 +899,7 @@ export class FormChangeItemModifierType extends PokemonModifierType implements G export class FusePokemonModifierType extends PokemonModifierType { constructor(localeKey: string, iconImage: string) { - super(localeKey, iconImage, (_type, args) => new Modifiers.FusePokemonModifier(this, (args[0] as PlayerPokemon).id, (args[1] as PlayerPokemon).id), + super(localeKey, iconImage, (_type, args) => new FusePokemonModifier(this, (args[0] as PlayerPokemon).id, (args[1] as PlayerPokemon).id), (pokemon: PlayerPokemon) => { if (pokemon.isFusion()) { return PartyUiHandler.NoEffectMessage; @@ -949,7 +946,7 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator { let type: Type; - const randInt = Utils.randSeedInt(totalWeight); + const randInt = randSeedInt(totalWeight); let weight = 0; for (const t of attackMoveTypeWeights.keys()) { @@ -981,7 +978,7 @@ class BaseStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { if (pregenArgs) { return new BaseStatBoosterModifierType(pregenArgs[0]); } - const randStat: PermanentStat = Utils.randSeedInt(Stat.SPD + 1); + const randStat: PermanentStat = randSeedInt(Stat.SPD + 1); return new BaseStatBoosterModifierType(randStat); }); } @@ -1002,7 +999,7 @@ class TempStatStageBoosterModifierTypeGenerator extends ModifierTypeGenerator { if (pregenArgs && (pregenArgs.length === 1) && TEMP_BATTLE_STATS.includes(pregenArgs[0])) { return new TempStatStageBoosterModifierType(pregenArgs[0]); } - const randStat: TempBattleStat = Utils.randSeedInt(Stat.ACC, Stat.ATK); + const randStat: TempBattleStat = randSeedInt(Stat.ACC, Stat.ATK); return new TempStatStageBoosterModifierType(randStat); }); } @@ -1044,8 +1041,8 @@ class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { const checkedStats = values[i].stats; // If party member already has the item being weighted currently, skip to the next item - const hasItem = p.getHeldItems().some(m => m instanceof Modifiers.SpeciesStatBoosterModifier - && (m as Modifiers.SpeciesStatBoosterModifier).contains(checkedSpecies[0], checkedStats[0])); + const hasItem = p.getHeldItems().some(m => m instanceof SpeciesStatBoosterModifier + && (m as SpeciesStatBoosterModifier).contains(checkedSpecies[0], checkedStats[0])); if (!hasItem) { if (checkedSpecies.includes(speciesId) || (!!fusionSpeciesId && checkedSpecies.includes(fusionSpeciesId))) { @@ -1065,7 +1062,7 @@ class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { } if (totalWeight !== 0) { - const randInt = Utils.randSeedInt(totalWeight, 1); + const randInt = randSeedInt(totalWeight, 1); let weight = 0; for (const i in weights) { @@ -1095,7 +1092,7 @@ class TmModifierTypeGenerator extends ModifierTypeGenerator { if (!tierUniqueCompatibleTms.length) { return null; } - const randTmIndex = Utils.randSeedInt(tierUniqueCompatibleTms.length); + const randTmIndex = randSeedInt(tierUniqueCompatibleTms.length); return new TmModifierType(tierUniqueCompatibleTms[randTmIndex]); }); } @@ -1123,7 +1120,7 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator { return null; } - return new EvolutionItemModifierType(evolutionItemPool[Utils.randSeedInt(evolutionItemPool.length)]!); // TODO: is the bang correct? + return new EvolutionItemModifierType(evolutionItemPool[randSeedInt(evolutionItemPool.length)]!); // TODO: is the bang correct? }); } } @@ -1137,12 +1134,12 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { const formChangeItemPool = [...new Set(party.filter(p => pokemonFormChanges.hasOwnProperty(p.species.speciesId)).map(p => { const formChanges = pokemonFormChanges[p.species.speciesId]; - let formChangeItemTriggers = formChanges.filter(fc => ((fc.formKey.indexOf(SpeciesFormKey.MEGA) === -1 && fc.formKey.indexOf(SpeciesFormKey.PRIMAL) === -1) || party[0].scene.getModifiers(Modifiers.MegaEvolutionAccessModifier).length) - && ((fc.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) === -1 && fc.formKey.indexOf(SpeciesFormKey.ETERNAMAX) === -1) || party[0].scene.getModifiers(Modifiers.GigantamaxAccessModifier).length) + let formChangeItemTriggers = formChanges.filter(fc => ((fc.formKey.indexOf(SpeciesFormKey.MEGA) === -1 && fc.formKey.indexOf(SpeciesFormKey.PRIMAL) === -1) || party[0].scene.getModifiers(MegaEvolutionAccessModifier).length) + && ((fc.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) === -1 && fc.formKey.indexOf(SpeciesFormKey.ETERNAMAX) === -1) || party[0].scene.getModifiers(GigantamaxAccessModifier).length) && (!fc.conditions.length || fc.conditions.filter(cond => cond instanceof SpeciesFormChangeCondition && cond.predicate(p)).length) && (fc.preFormKey === p.getFormKey())) .map(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger) - .filter(t => t && t.active && !p.scene.findModifier(m => m instanceof Modifiers.PokemonFormChangeItemModifier && m.pokemonId === p.id && m.formChangeItem === t.item)); + .filter(t => t && t.active && !p.scene.findModifier(m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === p.id && m.formChangeItem === t.item)); if (p.species.speciesId === Species.NECROZMA) { // technically we could use a simplified version and check for formChanges.length > 3, but in case any code changes later, this might break... @@ -1177,7 +1174,7 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { return null; } - return new FormChangeItemModifierType(formChangeItemPool[Utils.randSeedInt(formChangeItemPool.length)]); + return new FormChangeItemModifierType(formChangeItemPool[randSeedInt(formChangeItemPool.length)]); }); } } @@ -1186,7 +1183,7 @@ export class TerastallizeModifierType extends PokemonHeldItemModifierType implem private teraType: Type; constructor(teraType: Type) { - super("", `${Type[teraType].toLowerCase()}_tera_shard`, (type, args) => new Modifiers.TerastallizeModifier(type as TerastallizeModifierType, (args[0] as Pokemon).id, teraType), "tera_shard"); + super("", `${Type[teraType].toLowerCase()}_tera_shard`, (type, args) => new TerastallizeModifier(type as TerastallizeModifierType, (args[0] as Pokemon).id, teraType), "tera_shard"); this.teraType = teraType; } @@ -1208,7 +1205,7 @@ export class ContactHeldItemTransferChanceModifierType extends PokemonHeldItemMo private chancePercent: integer; constructor(localeKey: string, iconImage: string, chancePercent: integer, group?: string, soundName?: string) { - super(localeKey, iconImage, (type, args) => new Modifiers.ContactHeldItemTransferChanceModifier(type, (args[0] as Pokemon).id, chancePercent), group, soundName); + super(localeKey, iconImage, (type, args) => new ContactHeldItemTransferChanceModifier(type, (args[0] as Pokemon).id, chancePercent), group, soundName); this.chancePercent = chancePercent; } @@ -1220,7 +1217,7 @@ export class ContactHeldItemTransferChanceModifierType extends PokemonHeldItemMo export class TurnHeldItemTransferModifierType extends PokemonHeldItemModifierType { constructor(localeKey: string, iconImage: string, group?: string, soundName?: string) { - super(localeKey, iconImage, (type, args) => new Modifiers.TurnHeldItemTransferModifier(type, (args[0] as Pokemon).id), group, soundName); + super(localeKey, iconImage, (type, args) => new TurnHeldItemTransferModifier(type, (args[0] as Pokemon).id), group, soundName); } getDescription(scene: BattleScene): string { @@ -1233,7 +1230,7 @@ export class EnemyAttackStatusEffectChanceModifierType extends ModifierType { private effect: StatusEffect; constructor(localeKey: string, iconImage: string, chancePercent: integer, effect: StatusEffect, stackCount?: integer) { - super(localeKey, iconImage, (type, args) => new Modifiers.EnemyAttackStatusEffectChanceModifier(type, effect, chancePercent, stackCount), "enemy_status_chance"); + super(localeKey, iconImage, (type, args) => new EnemyAttackStatusEffectChanceModifier(type, effect, chancePercent, stackCount), "enemy_status_chance"); this.chancePercent = chancePercent; this.effect = effect; @@ -1251,7 +1248,7 @@ export class EnemyEndureChanceModifierType extends ModifierType { private chancePercent: number; constructor(localeKey: string, iconImage: string, chancePercent: number) { - super(localeKey, iconImage, (type, _args) => new Modifiers.EnemyEndureChanceModifier(type, chancePercent), "enemy_endure"); + super(localeKey, iconImage, (type, _args) => new EnemyEndureChanceModifier(type, chancePercent), "enemy_endure"); this.chancePercent = chancePercent; } @@ -1298,7 +1295,7 @@ function skipInLastClassicWaveOrDefault(defaultWeight: integer) : WeightedModifi */ function lureWeightFunc(maxBattles: number, weight: number): WeightedModifierTypeWeightFunc { return (party: Pokemon[]) => { - const lures = party[0].scene.getModifiers(Modifiers.DoubleBattleChanceBoosterModifier); + const lures = party[0].scene.getModifiers(DoubleBattleChanceBoosterModifier); return !(party[0].scene.gameMode.isClassic && party[0].scene.currentBattle.waveIndex === 199) && (lures.length === 0 || lures.filter(m => m.getMaxBattles() === maxBattles && m.getBattleCount() >= maxBattles * 0.6).length === 0) ? weight : 0; }; } @@ -1388,13 +1385,13 @@ export const modifierTypes = { RARE_FORM_CHANGE_ITEM: () => new FormChangeItemModifierTypeGenerator(true), EVOLUTION_TRACKER_GIMMIGHOUL: () => new PokemonHeldItemModifierType("modifierType:ModifierType.EVOLUTION_TRACKER_GIMMIGHOUL", "relic_gold", - (type, args) => new Modifiers.EvoTrackerModifier(type, (args[0] as Pokemon).id, Species.GIMMIGHOUL, 10)), + (type, args) => new EvoTrackerModifier(type, (args[0] as Pokemon).id, Species.GIMMIGHOUL, 10)), - MEGA_BRACELET: () => new ModifierType("modifierType:ModifierType.MEGA_BRACELET", "mega_bracelet", (type, _args) => new Modifiers.MegaEvolutionAccessModifier(type)), - DYNAMAX_BAND: () => new ModifierType("modifierType:ModifierType.DYNAMAX_BAND", "dynamax_band", (type, _args) => new Modifiers.GigantamaxAccessModifier(type)), - TERA_ORB: () => new ModifierType("modifierType:ModifierType.TERA_ORB", "tera_orb", (type, _args) => new Modifiers.TerastallizeAccessModifier(type)), + MEGA_BRACELET: () => new ModifierType("modifierType:ModifierType.MEGA_BRACELET", "mega_bracelet", (type, _args) => new MegaEvolutionAccessModifier(type)), + DYNAMAX_BAND: () => new ModifierType("modifierType:ModifierType.DYNAMAX_BAND", "dynamax_band", (type, _args) => new GigantamaxAccessModifier(type)), + TERA_ORB: () => new ModifierType("modifierType:ModifierType.TERA_ORB", "tera_orb", (type, _args) => new TerastallizeAccessModifier(type)), - MAP: () => new ModifierType("modifierType:ModifierType.MAP", "map", (type, _args) => new Modifiers.MapModifier(type)), + MAP: () => new ModifierType("modifierType:ModifierType.MAP", "map", (type, _args) => new MapModifier(type)), POTION: () => new PokemonHpRestoreModifierType("modifierType:ModifierType.POTION", "potion", 20, 10), SUPER_POTION: () => new PokemonHpRestoreModifierType("modifierType:ModifierType.SUPER_POTION", "super_potion", 50, 25), @@ -1409,8 +1406,8 @@ export const modifierTypes = { SACRED_ASH: () => new AllPokemonFullReviveModifierType("modifierType:ModifierType.SACRED_ASH", "sacred_ash"), - REVIVER_SEED: () => new PokemonHeldItemModifierType("modifierType:ModifierType.REVIVER_SEED", "reviver_seed", (type, args) => new Modifiers.PokemonInstantReviveModifier(type, (args[0] as Pokemon).id)), - WHITE_HERB: () => new PokemonHeldItemModifierType("modifierType:ModifierType.WHITE_HERB", "white_herb", (type, args) => new Modifiers.ResetNegativeStatStageModifier(type, (args[0] as Pokemon).id)), + REVIVER_SEED: () => new PokemonHeldItemModifierType("modifierType:ModifierType.REVIVER_SEED", "reviver_seed", (type, args) => new PokemonInstantReviveModifier(type, (args[0] as Pokemon).id)), + WHITE_HERB: () => new PokemonHeldItemModifierType("modifierType:ModifierType.WHITE_HERB", "white_herb", (type, args) => new ResetNegativeStatStageModifier(type, (args[0] as Pokemon).id)), ETHER: () => new PokemonPpRestoreModifierType("modifierType:ModifierType.ETHER", "ether", 10), MAX_ETHER: () => new PokemonPpRestoreModifierType("modifierType:ModifierType.MAX_ETHER", "max_ether", -1), @@ -1440,7 +1437,7 @@ export const modifierTypes = { amount: i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.extra.stage") }); } - }("modifierType:ModifierType.DIRE_HIT", "dire_hit", (type, _args) => new Modifiers.TempCritBoosterModifier(type, 5)), + }("modifierType:ModifierType.DIRE_HIT", "dire_hit", (type, _args) => new TempCritBoosterModifier(type, 5)), BASE_STAT_BOOSTER: () => new BaseStatBoosterModifierTypeGenerator(), @@ -1450,22 +1447,22 @@ export const modifierTypes = { if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in Nature)) { return new PokemonNatureChangeModifierType(pregenArgs[0] as Nature); } - return new PokemonNatureChangeModifierType(Utils.randSeedInt(Utils.getEnumValues(Nature).length) as Nature); + return new PokemonNatureChangeModifierType(randSeedInt(getEnumValues(Nature).length) as Nature); }), TERA_SHARD: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in Type)) { return new TerastallizeModifierType(pregenArgs[0] as Type); } - if (!party[0].scene.getModifiers(Modifiers.TerastallizeAccessModifier).length) { + if (!party[0].scene.getModifiers(TerastallizeAccessModifier).length) { return null; } let type: Type; - if (!Utils.randSeedInt(3)) { + if (!randSeedInt(3)) { const partyMemberTypes = party.map(p => p.getTypes(false, false, true)).flat(); - type = Utils.randSeedItem(partyMemberTypes); + type = randSeedItem(partyMemberTypes); } else { - type = Utils.randSeedInt(64) ? Utils.randSeedInt(18) as Type : Type.STELLAR; + type = randSeedInt(64) ? randSeedInt(18) as Type : Type.STELLAR; } return new TerastallizeModifierType(type); }), @@ -1474,9 +1471,9 @@ export const modifierTypes = { if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in BerryType)) { return new BerryModifierType(pregenArgs[0] as BerryType); } - const berryTypes = Utils.getEnumValues(BerryType); + const berryTypes = getEnumValues(BerryType); let randBerryType: BerryType; - const rand = Utils.randSeedInt(12); + const rand = randSeedInt(12); if (rand < 2) { randBerryType = BerryType.SITRUS; } else if (rand < 4) { @@ -1484,7 +1481,7 @@ export const modifierTypes = { } else if (rand < 6) { randBerryType = BerryType.LEPPA; } else { - randBerryType = berryTypes[Utils.randSeedInt(berryTypes.length - 3) + 2]; + randBerryType = berryTypes[randSeedInt(berryTypes.length - 3) + 2]; } return new BerryModifierType(randBerryType); }), @@ -1495,10 +1492,10 @@ export const modifierTypes = { MEMORY_MUSHROOM: () => new RememberMoveModifierType("modifierType:ModifierType.MEMORY_MUSHROOM", "big_mushroom"), - EXP_SHARE: () => new ModifierType("modifierType:ModifierType.EXP_SHARE", "exp_share", (type, _args) => new Modifiers.ExpShareModifier(type)), - EXP_BALANCE: () => new ModifierType("modifierType:ModifierType.EXP_BALANCE", "exp_balance", (type, _args) => new Modifiers.ExpBalanceModifier(type)), + EXP_SHARE: () => new ModifierType("modifierType:ModifierType.EXP_SHARE", "exp_share", (type, _args) => new ExpShareModifier(type)), + EXP_BALANCE: () => new ModifierType("modifierType:ModifierType.EXP_BALANCE", "exp_balance", (type, _args) => new ExpBalanceModifier(type)), - OVAL_CHARM: () => new ModifierType("modifierType:ModifierType.OVAL_CHARM", "oval_charm", (type, _args) => new Modifiers.MultipleParticipantExpBonusModifier(type)), + OVAL_CHARM: () => new ModifierType("modifierType:ModifierType.OVAL_CHARM", "oval_charm", (type, _args) => new MultipleParticipantExpBonusModifier(type)), EXP_CHARM: () => new ExpBoosterModifierType("modifierType:ModifierType.EXP_CHARM", "exp_charm", 25), SUPER_EXP_CHARM: () => new ExpBoosterModifierType("modifierType:ModifierType.SUPER_EXP_CHARM", "super_exp_charm", 60), @@ -1509,51 +1506,51 @@ export const modifierTypes = { SOOTHE_BELL: () => new PokemonFriendshipBoosterModifierType("modifierType:ModifierType.SOOTHE_BELL", "soothe_bell"), - SCOPE_LENS: () => new PokemonHeldItemModifierType("modifierType:ModifierType.SCOPE_LENS", "scope_lens", (type, args) => new Modifiers.CritBoosterModifier(type, (args[0] as Pokemon).id, 1)), - LEEK: () => new PokemonHeldItemModifierType("modifierType:ModifierType.LEEK", "leek", (type, args) => new Modifiers.SpeciesCritBoosterModifier(type, (args[0] as Pokemon).id, 2, [Species.FARFETCHD, Species.GALAR_FARFETCHD, Species.SIRFETCHD])), + SCOPE_LENS: () => new PokemonHeldItemModifierType("modifierType:ModifierType.SCOPE_LENS", "scope_lens", (type, args) => new CritBoosterModifier(type, (args[0] as Pokemon).id, 1)), + LEEK: () => new PokemonHeldItemModifierType("modifierType:ModifierType.LEEK", "leek", (type, args) => new SpeciesCritBoosterModifier(type, (args[0] as Pokemon).id, 2, [Species.FARFETCHD, Species.GALAR_FARFETCHD, Species.SIRFETCHD])), - EVIOLITE: () => new PokemonHeldItemModifierType("modifierType:ModifierType.EVIOLITE", "eviolite", (type, args) => new Modifiers.EvolutionStatBoosterModifier(type, (args[0] as Pokemon).id, [Stat.DEF, Stat.SPDEF], 1.5)), + EVIOLITE: () => new PokemonHeldItemModifierType("modifierType:ModifierType.EVIOLITE", "eviolite", (type, args) => new EvolutionStatBoosterModifier(type, (args[0] as Pokemon).id, [Stat.DEF, Stat.SPDEF], 1.5)), - SOUL_DEW: () => new PokemonHeldItemModifierType("modifierType:ModifierType.SOUL_DEW", "soul_dew", (type, args) => new Modifiers.PokemonNatureWeightModifier(type, (args[0] as Pokemon).id)), + SOUL_DEW: () => new PokemonHeldItemModifierType("modifierType:ModifierType.SOUL_DEW", "soul_dew", (type, args) => new PokemonNatureWeightModifier(type, (args[0] as Pokemon).id)), NUGGET: () => new MoneyRewardModifierType("modifierType:ModifierType.NUGGET", "nugget", 1, "modifierType:ModifierType.MoneyRewardModifierType.extra.small"), BIG_NUGGET: () => new MoneyRewardModifierType("modifierType:ModifierType.BIG_NUGGET", "big_nugget", 2.5, "modifierType:ModifierType.MoneyRewardModifierType.extra.moderate"), RELIC_GOLD: () => new MoneyRewardModifierType("modifierType:ModifierType.RELIC_GOLD", "relic_gold", 10, "modifierType:ModifierType.MoneyRewardModifierType.extra.large"), - AMULET_COIN: () => new ModifierType("modifierType:ModifierType.AMULET_COIN", "amulet_coin", (type, _args) => new Modifiers.MoneyMultiplierModifier(type)), - GOLDEN_PUNCH: () => new PokemonHeldItemModifierType("modifierType:ModifierType.GOLDEN_PUNCH", "golden_punch", (type, args) => new Modifiers.DamageMoneyRewardModifier(type, (args[0] as Pokemon).id)), - COIN_CASE: () => new ModifierType("modifierType:ModifierType.COIN_CASE", "coin_case", (type, _args) => new Modifiers.MoneyInterestModifier(type)), + AMULET_COIN: () => new ModifierType("modifierType:ModifierType.AMULET_COIN", "amulet_coin", (type, _args) => new MoneyMultiplierModifier(type)), + GOLDEN_PUNCH: () => new PokemonHeldItemModifierType("modifierType:ModifierType.GOLDEN_PUNCH", "golden_punch", (type, args) => new DamageMoneyRewardModifier(type, (args[0] as Pokemon).id)), + COIN_CASE: () => new ModifierType("modifierType:ModifierType.COIN_CASE", "coin_case", (type, _args) => new MoneyInterestModifier(type)), - LOCK_CAPSULE: () => new ModifierType("modifierType:ModifierType.LOCK_CAPSULE", "lock_capsule", (type, _args) => new Modifiers.LockModifierTiersModifier(type)), + LOCK_CAPSULE: () => new ModifierType("modifierType:ModifierType.LOCK_CAPSULE", "lock_capsule", (type, _args) => new LockModifierTiersModifier(type)), GRIP_CLAW: () => new ContactHeldItemTransferChanceModifierType("modifierType:ModifierType.GRIP_CLAW", "grip_claw", 10), WIDE_LENS: () => new PokemonMoveAccuracyBoosterModifierType("modifierType:ModifierType.WIDE_LENS", "wide_lens", 5), MULTI_LENS: () => new PokemonMultiHitModifierType("modifierType:ModifierType.MULTI_LENS", "zoom_lens"), - HEALING_CHARM: () => new ModifierType("modifierType:ModifierType.HEALING_CHARM", "healing_charm", (type, _args) => new Modifiers.HealingBoosterModifier(type, 1.1)), - CANDY_JAR: () => new ModifierType("modifierType:ModifierType.CANDY_JAR", "candy_jar", (type, _args) => new Modifiers.LevelIncrementBoosterModifier(type)), + HEALING_CHARM: () => new ModifierType("modifierType:ModifierType.HEALING_CHARM", "healing_charm", (type, _args) => new HealingBoosterModifier(type, 1.1)), + CANDY_JAR: () => new ModifierType("modifierType:ModifierType.CANDY_JAR", "candy_jar", (type, _args) => new LevelIncrementBoosterModifier(type)), - BERRY_POUCH: () => new ModifierType("modifierType:ModifierType.BERRY_POUCH", "berry_pouch", (type, _args) => new Modifiers.PreserveBerryModifier(type)), + BERRY_POUCH: () => new ModifierType("modifierType:ModifierType.BERRY_POUCH", "berry_pouch", (type, _args) => new PreserveBerryModifier(type)), - FOCUS_BAND: () => new PokemonHeldItemModifierType("modifierType:ModifierType.FOCUS_BAND", "focus_band", (type, args) => new Modifiers.SurviveDamageModifier(type, (args[0] as Pokemon).id)), + FOCUS_BAND: () => new PokemonHeldItemModifierType("modifierType:ModifierType.FOCUS_BAND", "focus_band", (type, args) => new SurviveDamageModifier(type, (args[0] as Pokemon).id)), - QUICK_CLAW: () => new PokemonHeldItemModifierType("modifierType:ModifierType.QUICK_CLAW", "quick_claw", (type, args) => new Modifiers.BypassSpeedChanceModifier(type, (args[0] as Pokemon).id)), + QUICK_CLAW: () => new PokemonHeldItemModifierType("modifierType:ModifierType.QUICK_CLAW", "quick_claw", (type, args) => new BypassSpeedChanceModifier(type, (args[0] as Pokemon).id)), - KINGS_ROCK: () => new PokemonHeldItemModifierType("modifierType:ModifierType.KINGS_ROCK", "kings_rock", (type, args) => new Modifiers.FlinchChanceModifier(type, (args[0] as Pokemon).id)), + KINGS_ROCK: () => new PokemonHeldItemModifierType("modifierType:ModifierType.KINGS_ROCK", "kings_rock", (type, args) => new FlinchChanceModifier(type, (args[0] as Pokemon).id)), - LEFTOVERS: () => new PokemonHeldItemModifierType("modifierType:ModifierType.LEFTOVERS", "leftovers", (type, args) => new Modifiers.TurnHealModifier(type, (args[0] as Pokemon).id)), - SHELL_BELL: () => new PokemonHeldItemModifierType("modifierType:ModifierType.SHELL_BELL", "shell_bell", (type, args) => new Modifiers.HitHealModifier(type, (args[0] as Pokemon).id)), + LEFTOVERS: () => new PokemonHeldItemModifierType("modifierType:ModifierType.LEFTOVERS", "leftovers", (type, args) => new TurnHealModifier(type, (args[0] as Pokemon).id)), + SHELL_BELL: () => new PokemonHeldItemModifierType("modifierType:ModifierType.SHELL_BELL", "shell_bell", (type, args) => new HitHealModifier(type, (args[0] as Pokemon).id)), - TOXIC_ORB: () => new PokemonHeldItemModifierType("modifierType:ModifierType.TOXIC_ORB", "toxic_orb", (type, args) => new Modifiers.TurnStatusEffectModifier(type, (args[0] as Pokemon).id)), - FLAME_ORB: () => new PokemonHeldItemModifierType("modifierType:ModifierType.FLAME_ORB", "flame_orb", (type, args) => new Modifiers.TurnStatusEffectModifier(type, (args[0] as Pokemon).id)), + TOXIC_ORB: () => new PokemonHeldItemModifierType("modifierType:ModifierType.TOXIC_ORB", "toxic_orb", (type, args) => new TurnStatusEffectModifier(type, (args[0] as Pokemon).id)), + FLAME_ORB: () => new PokemonHeldItemModifierType("modifierType:ModifierType.FLAME_ORB", "flame_orb", (type, args) => new TurnStatusEffectModifier(type, (args[0] as Pokemon).id)), - BATON: () => new PokemonHeldItemModifierType("modifierType:ModifierType.BATON", "baton", (type, args) => new Modifiers.SwitchEffectTransferModifier(type, (args[0] as Pokemon).id)), + BATON: () => new PokemonHeldItemModifierType("modifierType:ModifierType.BATON", "baton", (type, args) => new SwitchEffectTransferModifier(type, (args[0] as Pokemon).id)), - SHINY_CHARM: () => new ModifierType("modifierType:ModifierType.SHINY_CHARM", "shiny_charm", (type, _args) => new Modifiers.ShinyRateBoosterModifier(type)), - ABILITY_CHARM: () => new ModifierType("modifierType:ModifierType.ABILITY_CHARM", "ability_charm", (type, _args) => new Modifiers.HiddenAbilityRateBoosterModifier(type)), + SHINY_CHARM: () => new ModifierType("modifierType:ModifierType.SHINY_CHARM", "shiny_charm", (type, _args) => new ShinyRateBoosterModifier(type)), + ABILITY_CHARM: () => new ModifierType("modifierType:ModifierType.ABILITY_CHARM", "ability_charm", (type, _args) => new HiddenAbilityRateBoosterModifier(type)), - IV_SCANNER: () => new ModifierType("modifierType:ModifierType.IV_SCANNER", "scanner", (type, _args) => new Modifiers.IvScannerModifier(type)), + IV_SCANNER: () => new ModifierType("modifierType:ModifierType.IV_SCANNER", "scanner", (type, _args) => new IvScannerModifier(type)), DNA_SPLICERS: () => new FusePokemonModifierType("modifierType:ModifierType.DNA_SPLICERS", "dna_splicers"), @@ -1563,39 +1560,39 @@ export const modifierTypes = { VOUCHER_PLUS: () => new AddVoucherModifierType(VoucherType.PLUS, 1), VOUCHER_PREMIUM: () => new AddVoucherModifierType(VoucherType.PREMIUM, 1), - GOLDEN_POKEBALL: () => new ModifierType("modifierType:ModifierType.GOLDEN_POKEBALL", "pb_gold", (type, _args) => new Modifiers.ExtraModifierModifier(type), undefined, "se/pb_bounce_1"), + GOLDEN_POKEBALL: () => new ModifierType("modifierType:ModifierType.GOLDEN_POKEBALL", "pb_gold", (type, _args) => new ExtraModifierModifier(type), undefined, "se/pb_bounce_1"), - ENEMY_DAMAGE_BOOSTER: () => new ModifierType("modifierType:ModifierType.ENEMY_DAMAGE_BOOSTER", "wl_item_drop", (type, _args) => new Modifiers.EnemyDamageBoosterModifier(type, 5)), - ENEMY_DAMAGE_REDUCTION: () => new ModifierType("modifierType:ModifierType.ENEMY_DAMAGE_REDUCTION", "wl_guard_spec", (type, _args) => new Modifiers.EnemyDamageReducerModifier(type, 2.5)), - //ENEMY_SUPER_EFFECT_BOOSTER: () => new ModifierType('Type Advantage Token', 'Increases damage of super effective attacks by 30%', (type, _args) => new Modifiers.EnemySuperEffectiveDamageBoosterModifier(type, 30), 'wl_custom_super_effective'), - ENEMY_HEAL: () => new ModifierType("modifierType:ModifierType.ENEMY_HEAL", "wl_potion", (type, _args) => new Modifiers.EnemyTurnHealModifier(type, 2, 10)), + ENEMY_DAMAGE_BOOSTER: () => new ModifierType("modifierType:ModifierType.ENEMY_DAMAGE_BOOSTER", "wl_item_drop", (type, _args) => new EnemyDamageBoosterModifier(type, 5)), + ENEMY_DAMAGE_REDUCTION: () => new ModifierType("modifierType:ModifierType.ENEMY_DAMAGE_REDUCTION", "wl_guard_spec", (type, _args) => new EnemyDamageReducerModifier(type, 2.5)), + //ENEMY_SUPER_EFFECT_BOOSTER: () => new ModifierType('Type Advantage Token', 'Increases damage of super effective attacks by 30%', (type, _args) => new EnemySuperEffectiveDamageBoosterModifier(type, 30), 'wl_custom_super_effective'), + ENEMY_HEAL: () => new ModifierType("modifierType:ModifierType.ENEMY_HEAL", "wl_potion", (type, _args) => new EnemyTurnHealModifier(type, 2, 10)), ENEMY_ATTACK_POISON_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType("modifierType:ModifierType.ENEMY_ATTACK_POISON_CHANCE", "wl_antidote", 5, StatusEffect.POISON, 10), ENEMY_ATTACK_PARALYZE_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType("modifierType:ModifierType.ENEMY_ATTACK_PARALYZE_CHANCE", "wl_paralyze_heal", 2.5, StatusEffect.PARALYSIS, 10), ENEMY_ATTACK_BURN_CHANCE: () => new EnemyAttackStatusEffectChanceModifierType("modifierType:ModifierType.ENEMY_ATTACK_BURN_CHANCE", "wl_burn_heal", 5, StatusEffect.BURN, 10), - ENEMY_STATUS_EFFECT_HEAL_CHANCE: () => new ModifierType("modifierType:ModifierType.ENEMY_STATUS_EFFECT_HEAL_CHANCE", "wl_full_heal", (type, _args) => new Modifiers.EnemyStatusEffectHealChanceModifier(type, 2.5, 10)), + ENEMY_STATUS_EFFECT_HEAL_CHANCE: () => new ModifierType("modifierType:ModifierType.ENEMY_STATUS_EFFECT_HEAL_CHANCE", "wl_full_heal", (type, _args) => new EnemyStatusEffectHealChanceModifier(type, 2.5, 10)), ENEMY_ENDURE_CHANCE: () => new EnemyEndureChanceModifierType("modifierType:ModifierType.ENEMY_ENDURE_CHANCE", "wl_reset_urge", 2), - ENEMY_FUSED_CHANCE: () => new ModifierType("modifierType:ModifierType.ENEMY_FUSED_CHANCE", "wl_custom_spliced", (type, _args) => new Modifiers.EnemyFusionChanceModifier(type, 1)), + ENEMY_FUSED_CHANCE: () => new ModifierType("modifierType:ModifierType.ENEMY_FUSED_CHANCE", "wl_custom_spliced", (type, _args) => new EnemyFusionChanceModifier(type, 1)), MYSTERY_ENCOUNTER_SHUCKLE_JUICE: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs) { return new PokemonBaseStatTotalModifierType(pregenArgs[0] as number); } - return new PokemonBaseStatTotalModifierType(Utils.randSeedInt(20)); + return new PokemonBaseStatTotalModifierType(randSeedInt(20)); }), MYSTERY_ENCOUNTER_OLD_GATEAU: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs) { return new PokemonBaseStatFlatModifierType(pregenArgs[0] as number, pregenArgs[1] as Stat[]); } - return new PokemonBaseStatFlatModifierType(Utils.randSeedInt(20), [Stat.HP, Stat.ATK, Stat.DEF]); + return new PokemonBaseStatFlatModifierType(randSeedInt(20), [Stat.HP, Stat.ATK, Stat.DEF]); }), MYSTERY_ENCOUNTER_BLACK_SLUDGE: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs) { - return new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_BLACK_SLUDGE", "black_sludge", (type, _args) => new Modifiers.HealShopCostModifier(type, pregenArgs[0] as number)); + return new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_BLACK_SLUDGE", "black_sludge", (type, _args) => new HealShopCostModifier(type, pregenArgs[0] as number)); } - return new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_BLACK_SLUDGE", "black_sludge", (type, _args) => new Modifiers.HealShopCostModifier(type, 2.5)); + return new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_BLACK_SLUDGE", "black_sludge", (type, _args) => new HealShopCostModifier(type, 2.5)); }), - MYSTERY_ENCOUNTER_MACHO_BRACE: () => new PokemonHeldItemModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_MACHO_BRACE", "macho_brace", (type, args) => new Modifiers.PokemonIncrementingStatModifier(type, (args[0] as Pokemon).id)), - MYSTERY_ENCOUNTER_GOLDEN_BUG_NET: () => new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET", "golden_net", (type, _args) => new Modifiers.BoostBugSpawnModifier(type)), + MYSTERY_ENCOUNTER_MACHO_BRACE: () => new PokemonHeldItemModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_MACHO_BRACE", "macho_brace", (type, args) => new PokemonIncrementingStatModifier(type, (args[0] as Pokemon).id)), + MYSTERY_ENCOUNTER_GOLDEN_BUG_NET: () => new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET", "golden_net", (type, _args) => new BoostBugSpawnModifier(type)), }; interface ModifierPool { @@ -1644,8 +1641,8 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.PP_UP, 2), new WeightedModifierType(modifierTypes.FULL_HEAL, (party: Pokemon[]) => { const statusEffectPartyMemberCount = Math.min(party.filter(p => p.hp && !!p.status && !p.getHeldItems().some(i => { - if (i instanceof Modifiers.TurnStatusEffectModifier) { - return (i as Modifiers.TurnStatusEffectModifier).getStatusEffect() === p.status?.effect; + if (i instanceof TurnStatusEffectModifier) { + return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect; } return false; })).length, 3); @@ -1672,8 +1669,8 @@ const modifierPool: ModifierPool = { }, 3), new WeightedModifierType(modifierTypes.FULL_RESTORE, (party: Pokemon[]) => { const statusEffectPartyMemberCount = Math.min(party.filter(p => p.hp && !!p.status && !p.getHeldItems().some(i => { - if (i instanceof Modifiers.TurnStatusEffectModifier) { - return (i as Modifiers.TurnStatusEffectModifier).getStatusEffect() === p.status?.effect; + if (i instanceof TurnStatusEffectModifier) { + return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect; } return false; })).length, 3); @@ -1723,7 +1720,7 @@ const modifierPool: ModifierPool = { const { gameMode, gameData } = party[0].scene; if (gameMode.isDaily || (!gameMode.isFreshStartChallenge() && gameData.isUnlocked(Unlockables.EVIOLITE))) { return party.some(p => ((p.getSpeciesForm(true).speciesId in pokemonEvolutions) || (p.isFusion() && (p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions))) - && !p.getHeldItems().some(i => i instanceof Modifiers.EvolutionStatBoosterModifier) && !p.isMax()) ? 10 : 0; + && !p.getHeldItems().some(i => i instanceof EvolutionStatBoosterModifier) && !p.isMax()) ? 10 : 0; } return 0; }), @@ -1731,7 +1728,7 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.LEEK, (party: Pokemon[]) => { const checkedSpecies = [ Species.FARFETCHD, Species.GALAR_FARFETCHD, Species.SIRFETCHD ]; // If a party member doesn't already have a Leek and is one of the relevant species, Leek can appear - return party.some(p => !p.getHeldItems().some(i => i instanceof Modifiers.SpeciesCritBoosterModifier) + return party.some(p => !p.getHeldItems().some(i => i instanceof SpeciesCritBoosterModifier) && (checkedSpecies.includes(p.getSpeciesForm(true).speciesId) || (p.isFusion() && checkedSpecies.includes(p.getFusionSpeciesForm(true).speciesId)))) ? 12 : 0; }, 12), @@ -1739,7 +1736,7 @@ const modifierPool: ModifierPool = { const checkedAbilities = [Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.TOXIC_BOOST, Abilities.POISON_HEAL, Abilities.MAGIC_GUARD]; const checkedMoves = [Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT]; // If a party member doesn't already have one of these two orbs and has one of the above moves or abilities, the orb can appear - return party.some(p => !p.getHeldItems().some(i => i instanceof Modifiers.TurnStatusEffectModifier) + return party.some(p => !p.getHeldItems().some(i => i instanceof TurnStatusEffectModifier) && (checkedAbilities.some(a => p.hasAbility(a, false, true)) || p.getMoveset(true).some(m => m && checkedMoves.includes(m.moveId)))) ? 10 : 0; }, 10), @@ -1747,13 +1744,13 @@ const modifierPool: ModifierPool = { const checkedAbilities = [Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.FLARE_BOOST, Abilities.MAGIC_GUARD]; const checkedMoves = [Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT]; // If a party member doesn't already have one of these two orbs and has one of the above moves or abilities, the orb can appear - return party.some(p => !p.getHeldItems().some(i => i instanceof Modifiers.TurnStatusEffectModifier) + return party.some(p => !p.getHeldItems().some(i => i instanceof TurnStatusEffectModifier) && (checkedAbilities.some(a => p.hasAbility(a, false, true)) || p.getMoveset(true).some(m => m && checkedMoves.includes(m.moveId)))) ? 10 : 0; }, 10), new WeightedModifierType(modifierTypes.WHITE_HERB, (party: Pokemon[]) => { const checkedAbilities = [Abilities.WEAK_ARMOR, Abilities.CONTRARY, Abilities.MOODY, Abilities.ANGER_SHELL, Abilities.COMPETITIVE, Abilities.DEFIANT]; const weightMultiplier = party.filter( - p => !p.getHeldItems().some(i => i instanceof Modifiers.ResetNegativeStatStageModifier && i.stackCount >= i.getMaxHeldItemCount(p)) && + p => !p.getHeldItems().some(i => i instanceof ResetNegativeStatStageModifier && i.stackCount >= i.getMaxHeldItemCount(p)) && (checkedAbilities.some(a => p.hasAbility(a, false, true)) || p.getMoveset(true).some(m => m && selfStatLowerMoves.includes(m.moveId)))).length; // If a party member has one of the above moves or abilities and doesn't have max herbs, the herb will appear more frequently return 0 * (weightMultiplier ? 2 : 1) + (weightMultiplier ? weightMultiplier * 0 : 0); @@ -2246,7 +2243,7 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base return options.slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)).flat(); } -export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: Modifiers.PersistentModifier[], scene: BattleScene): Modifiers.EnemyPersistentModifier { +export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: PersistentModifier[], scene: BattleScene): EnemyPersistentModifier { let tierStackCount: number; switch (tier) { case ModifierTier.ULTRA: @@ -2263,30 +2260,30 @@ export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: const retryCount = 50; let candidate = getNewModifierTypeOption([], ModifierPoolType.ENEMY_BUFF, tier); let r = 0; - let matchingModifier: Modifiers.PersistentModifier | undefined; + let matchingModifier: PersistentModifier | undefined; while (++r < retryCount && (matchingModifier = enemyModifiers.find(m => m.type.id === candidate?.type?.id)) && matchingModifier.getMaxStackCount(scene) < matchingModifier.stackCount + (r < 10 ? tierStackCount : 1)) { candidate = getNewModifierTypeOption([], ModifierPoolType.ENEMY_BUFF, tier); } - const modifier = candidate?.type?.newModifier() as Modifiers.EnemyPersistentModifier; + const modifier = candidate?.type?.newModifier() as EnemyPersistentModifier; modifier.stackCount = tierStackCount; return modifier; } export function getEnemyModifierTypesForWave(waveIndex: integer, count: integer, party: EnemyPokemon[], poolType: ModifierPoolType.WILD | ModifierPoolType.TRAINER, upgradeChance: integer = 0): PokemonHeldItemModifierType[] { - const ret = new Array(count).fill(0).map(() => getNewModifierTypeOption(party, poolType, undefined, upgradeChance && !Utils.randSeedInt(upgradeChance) ? 1 : 0)?.type as PokemonHeldItemModifierType); + const ret = new Array(count).fill(0).map(() => getNewModifierTypeOption(party, poolType, undefined, upgradeChance && !randSeedInt(upgradeChance) ? 1 : 0)?.type as PokemonHeldItemModifierType); if (!(waveIndex % 1000)) { ret.push(getModifierType(modifierTypes.MINI_BLACK_HOLE) as PokemonHeldItemModifierType); } return ret; } -export function getDailyRunStarterModifiers(party: PlayerPokemon[]): Modifiers.PokemonHeldItemModifier[] { - const ret: Modifiers.PokemonHeldItemModifier[] = []; +export function getDailyRunStarterModifiers(party: PlayerPokemon[]): PokemonHeldItemModifier[] { + const ret: PokemonHeldItemModifier[] = []; for (const p of party) { for (let m = 0; m < 3; m++) { - const tierValue = Utils.randSeedInt(64); + const tierValue = randSeedInt(64); let tier: ModifierTier; if (tierValue > 25) { @@ -2301,7 +2298,7 @@ export function getDailyRunStarterModifiers(party: PlayerPokemon[]): Modifiers.P tier = ModifierTier.MASTER; } - const modifier = getNewModifierTypeOption(party, ModifierPoolType.DAILY_STARTER, tier)?.type?.newModifier(p) as Modifiers.PokemonHeldItemModifier; + const modifier = getNewModifierTypeOption(party, ModifierPoolType.DAILY_STARTER, tier)?.type?.newModifier(p) as PokemonHeldItemModifier; ret.push(modifier); } } @@ -2340,7 +2337,7 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, break; } if (tier === undefined) { - const tierValue = Utils.randSeedInt(1024); + const tierValue = randSeedInt(1024); if (!upgradeCount) { upgradeCount = 0; } @@ -2349,7 +2346,7 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, const upgradeOdds = Math.floor(128 / ((partyLuckValue + 4) / 4)); let upgraded = false; do { - upgraded = Utils.randSeedInt(upgradeOdds) < 4; + upgraded = randSeedInt(upgradeOdds) < 4; if (upgraded) { upgradeCount++; } @@ -2381,7 +2378,7 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, const partyShinyCount = party.filter(p => p.isShiny() && !p.isFainted()).length; const upgradeOdds = Math.floor(32 / ((partyShinyCount + 2) / 2)); while (modifierPool.hasOwnProperty(tier + upgradeCount + 1) && modifierPool[tier + upgradeCount + 1].length) { - if (!Utils.randSeedInt(upgradeOdds)) { + if (!randSeedInt(upgradeOdds)) { upgradeCount++; } else { break; @@ -2396,7 +2393,7 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, const tierThresholds = Object.keys(thresholds[tier]); const totalWeight = parseInt(tierThresholds[tierThresholds.length - 1]); - const value = Utils.randSeedInt(totalWeight); + const value = randSeedInt(totalWeight); let index: integer | undefined; for (const t of tierThresholds) { const threshold = parseInt(t); @@ -2456,9 +2453,9 @@ export class ModifierTypeOption { */ export function getPartyLuckValue(party: Pokemon[]): integer { if (party[0].scene.gameMode.isDaily) { - const DailyLuck = new Utils.NumberHolder(0); + const DailyLuck = new NumberHolder(0); party[0].scene.executeWithSeedOffset(() => { - DailyLuck.value = Utils.randSeedInt(15); // Random number between 0 and 14 + DailyLuck.value = randSeedInt(15); // Random number between 0 and 14 }, 0, party[0].scene.seed); return DailyLuck.value; } diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 4c20b454081..4f1f9833602 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1,33 +1,35 @@ -import * as ModifierTypes from "#app/modifier/modifier-type"; -import { getModifierType, ModifierType, modifierTypes } from "#app/modifier/modifier-type"; -import BattleScene from "#app/battle-scene"; -import { getLevelTotalExp } from "#app/data/exp"; -import { MAX_PER_TYPE_POKEBALLS, PokeballType } from "#app/data/pokeball"; -import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; -import { addTextObject, TextStyle } from "#app/ui/text"; -import { Type } from "#app/data/type"; -import { EvolutionPhase } from "#app/phases/evolution-phase"; +import type BattleScene from "#app/battle-scene"; import { FusionSpeciesFormEvolution, pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; -import { getPokemonNameWithAffix } from "#app/messages"; -import * as Utils from "#app/utils"; import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry"; -import { BattlerTagType } from "#enums/battler-tag-type"; -import { BerryType } from "#enums/berry-type"; -import { getStatusEffectHealText, StatusEffect } from "#app/data/status-effect"; -import { achvs } from "#app/system/achv"; -import { VoucherType } from "#app/system/voucher"; -import { FormChangeItem, SpeciesFormChangeItemTrigger, SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms"; -import { Nature } from "#app/data/nature"; -import Overrides from "#app/overrides"; -import { Command } from "#app/ui/command-ui-handler"; -import { Species } from "#enums/species"; -import { BATTLE_STATS, type PermanentStat, Stat, TEMP_BATTLE_STATS, type TempBattleStat } from "#enums/stat"; -import i18next from "i18next"; +import { getLevelTotalExp } from "#app/data/exp"; import { allMoves } from "#app/data/move"; -import { Abilities } from "#enums/abilities"; +import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; +import { type FormChangeItem, SpeciesFormChangeItemTrigger, SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms"; +import { getStatusEffectHealText } from "#app/data/status-effect"; +import { Type } from "#app/data/type"; +import Pokemon, { type PlayerPokemon } from "#app/field/pokemon"; +import { getPokemonNameWithAffix } from "#app/messages"; +import Overrides from "#app/overrides"; +import { EvolutionPhase } from "#app/phases/evolution-phase"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { LevelUpPhase } from "#app/phases/level-up-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; +import { achvs } from "#app/system/achv"; +import type { VoucherType } from "#app/system/voucher"; +import { Command } from "#app/ui/command-ui-handler"; +import { addTextObject, TextStyle } from "#app/ui/text"; +import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, toDmgValue } from "#app/utils"; +import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { BerryType } from "#enums/berry-type"; +import type { Nature } from "#enums/nature"; +import type { PokeballType } from "#enums/pokeball"; +import { Species } from "#enums/species"; +import { type PermanentStat, type TempBattleStat, BATTLE_STATS, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; +import { StatusEffect } from "#enums/status-effect"; +import i18next from "i18next"; +import { type DoubleBattleChanceBoosterModifierType, type EvolutionItemModifierType, type FormChangeItemModifierType, type ModifierOverride, type ModifierType, type PokemonBaseStatTotalModifierType, type PokemonExpBoosterModifierType, type PokemonFriendshipBoosterModifierType, type PokemonMoveAccuracyBoosterModifierType, type PokemonMultiHitModifierType, type TerastallizeModifierType, type TmModifierType, getModifierType, ModifierPoolType, ModifierTypeGenerator, modifierTypes, PokemonHeldItemModifierType } from "./modifier-type"; +import { Color, ShadowColor } from "#enums/color"; export type ModifierPredicate = (modifier: Modifier) => boolean; @@ -84,7 +86,7 @@ export class ModifierBar extends Phaser.GameObjects.Container { const thisArg = this; - sortedVisibleIconModifiers.forEach((modifier: PersistentModifier, i: integer) => { + sortedVisibleIconModifiers.forEach((modifier: PersistentModifier, i: number) => { const icon = modifier.getIcon(this.scene as BattleScene); if (i >= iconOverflowIndex) { icon.setVisible(false); @@ -120,8 +122,8 @@ export class ModifierBar extends Phaser.GameObjects.Container { } } - setModifierIconPosition(icon: Phaser.GameObjects.Container, modifierCount: integer) { - const rowIcons: integer = 12 + 6 * Math.max((Math.ceil(Math.min(modifierCount, 24) / 12) - 2), 0); + setModifierIconPosition(icon: Phaser.GameObjects.Container, modifierCount: number) { + const rowIcons: number = 12 + 6 * Math.max((Math.ceil(Math.min(modifierCount, 24) / 12) - 2), 0); const x = (this.getIndex(icon) % rowIcons) * 26 / (rowIcons / 12); const y = Math.floor(this.getIndex(icon) / rowIcons) * 20; @@ -141,18 +143,27 @@ export abstract class Modifier { return false; } - shouldApply(_args: any[]): boolean { + /** + * Checks if {@linkcode Modifier} should be applied + * @param _args parameters passed to {@linkcode Modifier.apply} + * @returns always `true` by default + */ + shouldApply(..._args: Parameters): boolean { return true; } - abstract apply(args: any[]): boolean | Promise; + /** + * Handles applying of {@linkcode Modifier} + * @param args collection of all passed parameters + */ + abstract apply(...args: unknown[]): boolean | Promise; } export abstract class PersistentModifier extends Modifier { - public stackCount: integer; - public virtualStackCount: integer; + public stackCount: number; + public virtualStackCount: number; - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type); this.stackCount = stackCount === undefined ? 1 : stackCount; this.virtualStackCount = 0; @@ -179,7 +190,7 @@ export abstract class PersistentModifier extends Modifier { return []; } - incrementStack(scene: BattleScene, amount: integer, virtual: boolean): boolean { + incrementStack(scene: BattleScene, amount: number, virtual: boolean): boolean { if (this.getStackCount() + amount <= this.getMaxStackCount(scene)) { if (!virtual) { this.stackCount += amount; @@ -192,11 +203,11 @@ export abstract class PersistentModifier extends Modifier { return false; } - getStackCount(): integer { + getStackCount(): number { return this.stackCount + this.virtualStackCount; } - abstract getMaxStackCount(scene: BattleScene, forThreshold?: boolean): integer; + abstract getMaxStackCount(scene: BattleScene, forThreshold?: boolean): number; isIconVisible(scene: BattleScene): boolean { return true; @@ -247,25 +258,26 @@ export abstract class ConsumableModifier extends Modifier { add(_modifiers: Modifier[]): boolean { return true; } - - shouldApply(args: any[]): boolean { - return super.shouldApply(args) && args.length === 1 && args[0] instanceof BattleScene; - } } export class AddPokeballModifier extends ConsumableModifier { private pokeballType: PokeballType; - private count: integer; + private count: number; - constructor(type: ModifierType, pokeballType: PokeballType, count: integer) { + constructor(type: ModifierType, pokeballType: PokeballType, count: number) { super(type); this.pokeballType = pokeballType; this.count = count; } - apply(args: any[]): boolean { - const pokeballCounts = (args[0] as BattleScene).pokeballCounts; + /** + * Applies {@linkcode AddPokeballModifier} + * @param battleScene {@linkcode BattleScene} + * @returns always `true` + */ + override apply(battleScene: BattleScene): boolean { + const pokeballCounts = battleScene.pokeballCounts; pokeballCounts[this.pokeballType] = Math.min(pokeballCounts[this.pokeballType] + this.count, MAX_PER_TYPE_POKEBALLS); return true; @@ -274,17 +286,22 @@ export class AddPokeballModifier extends ConsumableModifier { export class AddVoucherModifier extends ConsumableModifier { private voucherType: VoucherType; - private count: integer; + private count: number; - constructor(type: ModifierType, voucherType: VoucherType, count: integer) { + constructor(type: ModifierType, voucherType: VoucherType, count: number) { super(type); this.voucherType = voucherType; this.count = count; } - apply(args: any[]): boolean { - const voucherCounts = (args[0] as BattleScene).gameData.voucherCounts; + /** + * Applies {@linkcode AddVoucherModifier} + * @param battleScene {@linkcode BattleScene} + * @returns always `true` + */ + override apply(battleScene: BattleScene): boolean { + const voucherCounts = battleScene.gameData.voucherCounts; voucherCounts[this.voucherType] += this.count; return true; @@ -308,7 +325,7 @@ export abstract class LapsingPersistentModifier extends PersistentModifier { /** The current amount of battles the modifier will exist for */ private battleCount: number; - constructor(type: ModifierTypes.ModifierType, maxBattles: number, battleCount?: number, stackCount?: integer) { + constructor(type: ModifierType, maxBattles: number, battleCount?: number, stackCount?: number) { super(type, stackCount); this.maxBattles = maxBattles; @@ -322,7 +339,7 @@ export abstract class LapsingPersistentModifier extends PersistentModifier { * @param modifiers {@linkcode PersistentModifier} array of the player's modifiers * @param _virtual N/A * @param _scene N/A - * @returns true if the modifier was successfully added or applied, false otherwise + * @returns `true` if the modifier was successfully added or applied, false otherwise */ add(modifiers: PersistentModifier[], _virtual: boolean, scene: BattleScene): boolean { for (const modifier of modifiers) { @@ -342,7 +359,12 @@ export abstract class LapsingPersistentModifier extends PersistentModifier { return true; } - lapse(_args: any[]): boolean { + /** + * Lapses the {@linkcode battleCount} by 1. + * @param _args passed arguments (not in use here) + * @returns `true` if the {@linkcode battleCount} is greater than 0 + */ + public lapse(..._args: unknown[]): boolean { this.battleCount--; return this.battleCount > 0; } @@ -354,10 +376,13 @@ export abstract class LapsingPersistentModifier extends PersistentModifier { const hue = Math.floor(120 * (this.battleCount / this.maxBattles) + 5); // Generates the color hex code with a constant saturation and lightness but varying hue - const typeHex = Utils.hslToHex(hue, 0.50, 0.90); - const strokeHex = Utils.hslToHex(hue, 0.70, 0.30); + const typeHex = hslToHex(hue, 0.5, 0.9); + const strokeHex = hslToHex(hue, 0.7, 0.3); - const battleCountText = addTextObject(scene, 27, 0, this.battleCount.toString(), TextStyle.PARTY, { fontSize: "66px", color: typeHex }); + const battleCountText = addTextObject(scene, 27, 0, this.battleCount.toString(), TextStyle.PARTY, { + fontSize: "66px", + color: typeHex, + }); battleCountText.setShadow(0, 0); battleCountText.setStroke(strokeHex, 16); battleCountText.setOrigin(1, 0); @@ -383,7 +408,7 @@ export abstract class LapsingPersistentModifier extends PersistentModifier { } getArgs(): any[] { - return [ this.maxBattles, this.battleCount ]; + return [this.maxBattles, this.battleCount]; } getMaxStackCount(_scene: BattleScene, _forThreshold?: boolean): number { @@ -399,7 +424,9 @@ export abstract class LapsingPersistentModifier extends PersistentModifier { * @see {@linkcode apply} */ export class DoubleBattleChanceBoosterModifier extends LapsingPersistentModifier { - constructor(type: ModifierType, maxBattles:number, battleCount?: number, stackCount?: integer) { + public override type: DoubleBattleChanceBoosterModifierType; + + constructor(type: ModifierType, maxBattles:number, battleCount?: number, stackCount?: number) { super(type, maxBattles, battleCount, stackCount); } @@ -408,17 +435,16 @@ export class DoubleBattleChanceBoosterModifier extends LapsingPersistentModifier } clone(): DoubleBattleChanceBoosterModifier { - return new DoubleBattleChanceBoosterModifier(this.type as ModifierTypes.DoubleBattleChanceBoosterModifierType, this.getMaxBattles(), this.getBattleCount(), this.stackCount); + return new DoubleBattleChanceBoosterModifier(this.type, this.getMaxBattles(), this.getBattleCount(), this.stackCount); } /** * Increases the chance of a double battle occurring - * @param args [0] {@linkcode Utils.NumberHolder} for double battle chance - * @returns true if the modifier was applied + * @param doubleBattleChance {@linkcode NumberHolder} for double battle chance + * @returns true */ - apply(args: any[]): boolean { - const doubleBattleChance = args[0] as Utils.NumberHolder; - // This is divided because the chance is generated as a number from 0 to doubleBattleChance.value using Utils.randSeedInt + override apply(doubleBattleChance: NumberHolder): boolean { + // This is divided because the chance is generated as a number from 0 to doubleBattleChance.value using randSeedInt // A double battle will initiate if the generated number is 0 doubleBattleChance.value = doubleBattleChance.value / 4; @@ -467,21 +493,21 @@ export class TempStatStageBoosterModifier extends LapsingPersistentModifier { /** * Checks if {@linkcode args} contains the necessary elements and if the * incoming stat is matches {@linkcode stat}. - * @param args [0] {@linkcode TempBattleStat} being checked at the time - * [1] {@linkcode Utils.NumberHolder} N/A - * @returns true if the modifier can be applied, false otherwise + * @param tempBattleStat {@linkcode TempBattleStat} being affected + * @param statLevel {@linkcode NumberHolder} that holds the resulting value of the stat stage multiplier + * @returns `true` if the modifier can be applied, false otherwise */ - shouldApply(args: any[]): boolean { - return args && (args.length === 2) && TEMP_BATTLE_STATS.includes(args[0]) && (args[0] === this.stat) && (args[1] instanceof Utils.NumberHolder); + override shouldApply(tempBattleStat?: TempBattleStat, statLevel?: NumberHolder): boolean { + return !!tempBattleStat && !!statLevel && TEMP_BATTLE_STATS.includes(tempBattleStat) && (tempBattleStat === this.stat); } /** * Increases the incoming stat stage matching {@linkcode stat} by {@linkcode boost}. - * @param args [0] {@linkcode TempBattleStat} N/A - * [1] {@linkcode Utils.NumberHolder} that holds the resulting value of the stat stage multiplier + * @param _tempBattleStat {@linkcode TempBattleStat} N/A + * @param statLevel {@linkcode NumberHolder} that holds the resulting value of the stat stage multiplier */ - apply(args: any[]): boolean { - (args[1] as Utils.NumberHolder).value += this.boost; + override apply(_tempBattleStat: TempBattleStat, statLevel: NumberHolder): boolean { + statLevel.value += this.boost; return true; } } @@ -507,26 +533,26 @@ export class TempCritBoosterModifier extends LapsingPersistentModifier { /** * Checks if {@linkcode args} contains the necessary elements. - * @param args [1] {@linkcode Utils.NumberHolder} N/A - * @returns true if the critical-hit stage boost applies successfully + * @param critLevel {@linkcode NumberHolder} that holds the resulting critical-hit level + * @returns `true` if the critical-hit stage boost applies successfully */ - shouldApply(args: any[]): boolean { - return args && (args.length === 1) && (args[0] instanceof Utils.NumberHolder); + override shouldApply(critLevel?: NumberHolder): boolean { + return !!critLevel; } /** * Increases the current critical-hit stage value by 1. - * @param args [0] {@linkcode Utils.IntegerHolder} that holds the resulting critical-hit level - * @returns true if the critical-hit stage boost applies successfully + * @param critLevel {@linkcode NumberHolder} that holds the resulting critical-hit level + * @returns `true` if the critical-hit stage boost applies successfully */ - apply(args: any[]): boolean { - (args[0] as Utils.NumberHolder).value++; + override apply(critLevel: NumberHolder): boolean { + critLevel.value++; return true; } } export class MapModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } @@ -534,17 +560,17 @@ export class MapModifier extends PersistentModifier { return new MapModifier(this.type, this.stackCount); } - apply(args: any[]): boolean { + override apply(..._args: unknown[]): boolean { return true; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 1; } } export class MegaEvolutionAccessModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } @@ -552,17 +578,17 @@ export class MegaEvolutionAccessModifier extends PersistentModifier { return new MegaEvolutionAccessModifier(this.type, this.stackCount); } - apply(args: any[]): boolean { + override apply(..._args: unknown[]): boolean { return true; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 1; } } export class GigantamaxAccessModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } @@ -570,17 +596,22 @@ export class GigantamaxAccessModifier extends PersistentModifier { return new GigantamaxAccessModifier(this.type, this.stackCount); } - apply(args: any[]): boolean { + /** + * Applies {@linkcode GigantamaxAccessModifier} + * @param _args N/A + * @returns always `true` + */ + apply(..._args: unknown[]): boolean { return true; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 1; } } export class TerastallizeAccessModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } @@ -588,20 +619,25 @@ export class TerastallizeAccessModifier extends PersistentModifier { return new TerastallizeAccessModifier(this.type, this.stackCount); } - apply(args: any[]): boolean { + /** + * Applies {@linkcode TerastallizeAccessModifier} + * @param _args N/A + * @returns always `true` + */ + override apply(..._args: unknown[]): boolean { return true; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 1; } } export abstract class PokemonHeldItemModifier extends PersistentModifier { - public pokemonId: integer; + public pokemonId: number; public isTransferable: boolean = true; - constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, stackCount?: number) { super(type, stackCount); this.pokemonId = pokemonId; @@ -617,8 +653,21 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier { return [ this.pokemonId ]; } - shouldApply(args: any[]): boolean { - return super.shouldApply(args) && args.length !== 0 && args[0] instanceof Pokemon && (this.pokemonId === -1 || (args[0] as Pokemon).id === this.pokemonId); + /** + * Applies the {@linkcode PokemonHeldItemModifier} to the given {@linkcode Pokemon}. + * @param pokemon The {@linkcode Pokemon} that holds the held item + * @param args additional parameters + */ + abstract override apply(pokemon: Pokemon, ...args: unknown[]): boolean; + + /** + * Checks if {@linkcode PokemonHeldItemModifier} should be applied. + * @param pokemon The {@linkcode Pokemon} that holds the item + * @param _args N/A + * @returns if {@linkcode PokemonHeldItemModifier} should be applied + */ + override shouldApply(pokemon?: Pokemon, ..._args: unknown[]): boolean { + return !!pokemon && (this.pokemonId === -1 || pokemon.id === this.pokemonId); } isIconVisible(scene: BattleScene): boolean { @@ -667,7 +716,7 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier { } //Applies to items with chance of activating secondary effects ie Kings Rock - getSecondaryChanceMultiplier(pokemon: Pokemon): integer { + getSecondaryChanceMultiplier(pokemon: Pokemon): number { // Temporary quickfix to stop game from freezing when the opponet uses u-turn while holding on to king's rock if (!pokemon.getLastXMoves(0)[0]) { return 1; @@ -682,41 +731,52 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier { return 1; } - getMaxStackCount(scene: BattleScene, forThreshold?: boolean): integer { + getMaxStackCount(scene: BattleScene, forThreshold?: boolean): number { const pokemon = this.getPokemon(scene); if (!pokemon) { return 0; } if (pokemon.isPlayer() && forThreshold) { - return scene.getParty().map(p => this.getMaxHeldItemCount(p)).reduce((stackCount: integer, maxStackCount: integer) => Math.max(stackCount, maxStackCount), 0); + return scene.getParty().map(p => this.getMaxHeldItemCount(p)).reduce((stackCount: number, maxStackCount: number) => Math.max(stackCount, maxStackCount), 0); } return this.getMaxHeldItemCount(pokemon); } - abstract getMaxHeldItemCount(pokemon?: Pokemon): integer; + abstract getMaxHeldItemCount(pokemon?: Pokemon): number; } export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModifier { - protected battlesLeft: integer; + protected battlesLeft: number; public isTransferable: boolean = false; - constructor(type: ModifierTypes.ModifierType, pokemonId: integer, battlesLeft?: integer, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, battlesLeft?: number, stackCount?: number) { super(type, pokemonId, stackCount); this.battlesLeft = battlesLeft!; // TODO: is this bang correct? } - lapse(args: any[]): boolean { + /** + * Lapse the {@linkcode battlesLeft} counter (reduce it by 1) + * @param _args arguments passed (not used here) + * @returns `true` if {@linkcode battlesLeft} is not null + */ + public lapse(..._args: unknown[]): boolean { return !!--this.battlesLeft; } - getIcon(scene: BattleScene, forSummary?: boolean): Phaser.GameObjects.Container { + /** + * Retrieve the {@linkcode Modifier | Modifiers} icon as a {@linkcode Phaser.GameObjects.Container | Container} + * @param scene The {@linkcode BattleScene} + * @param forSummary `true` if the icon is for the summary screen + * @returns the icon as a {@linkcode Phaser.GameObjects.Container | Container} + */ + public getIcon(scene: BattleScene, forSummary?: boolean): Phaser.GameObjects.Container { const container = super.getIcon(scene, forSummary); if (this.getPokemon(scene)?.isPlayer()) { - const battleCountText = addTextObject(scene, 27, 0, this.battlesLeft.toString(), TextStyle.PARTY, { fontSize: "66px", color: "#f89890" }); + const battleCountText = addTextObject(scene, 27, 0, this.battlesLeft.toString(), TextStyle.PARTY, { fontSize: "66px", color: Color.PINK }); battleCountText.setShadow(0, 0); - battleCountText.setStroke("#984038", 16); + battleCountText.setStroke(ShadowColor.RED, 16); battleCountText.setOrigin(1, 0); container.add(battleCountText); } @@ -724,7 +784,7 @@ export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModi return container; } - getBattlesLeft(): integer { + getBattlesLeft(): number { return this.battlesLeft; } @@ -734,10 +794,11 @@ export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModi } export class TerastallizeModifier extends LapsingPokemonHeldItemModifier { + public override type: TerastallizeModifierType; public teraType: Type; public isTransferable: boolean = false; - constructor(type: ModifierTypes.TerastallizeModifierType, pokemonId: integer, teraType: Type, battlesLeft?: integer, stackCount?: integer) { + constructor(type: TerastallizeModifierType, pokemonId: number, teraType: Type, battlesLeft?: number, stackCount?: number) { super(type, pokemonId, battlesLeft || 10, stackCount); this.teraType = teraType; @@ -751,15 +812,19 @@ export class TerastallizeModifier extends LapsingPokemonHeldItemModifier { } clone(): TerastallizeModifier { - return new TerastallizeModifier(this.type as ModifierTypes.TerastallizeModifierType, this.pokemonId, this.teraType, this.battlesLeft, this.stackCount); + return new TerastallizeModifier(this.type, this.pokemonId, this.teraType, this.battlesLeft, this.stackCount); } getArgs(): any[] { return [ this.pokemonId, this.teraType, this.battlesLeft ]; } - apply(args: any[]): boolean { - const pokemon = args[0] as Pokemon; + /** + * Applies the {@linkcode TerastallizeModifier} to the specified {@linkcode Pokemon}. + * @param pokemon the {@linkcode Pokemon} to be terastallized + * @returns always `true` + */ + override apply(pokemon: Pokemon): boolean { if (pokemon.isPlayer()) { pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeTeraTrigger); pokemon.scene.validateAchv(achvs.TERASTALLIZE); @@ -771,10 +836,14 @@ export class TerastallizeModifier extends LapsingPokemonHeldItemModifier { return true; } - lapse(args: any[]): boolean { - const ret = super.lapse(args); + /** + * Triggers {@linkcode LapsingPokemonHeldItemModifier.lapse} and if it returns `0` a form change is triggered. + * @param pokemon THe {@linkcode Pokemon} to be terastallized + * @returns the result of {@linkcode LapsingPokemonHeldItemModifier.lapse} + */ + public override lapse(pokemon: Pokemon): boolean { + const ret = super.lapse(pokemon); if (!ret) { - const pokemon = args[0] as Pokemon; pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeLapseTeraTrigger); pokemon.updateSpritePipelineData(); } @@ -785,7 +854,7 @@ export class TerastallizeModifier extends LapsingPokemonHeldItemModifier { return 1.25; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 1; } } @@ -800,7 +869,7 @@ export class BaseStatModifier extends PokemonHeldItemModifier { protected stat: PermanentStat; public isTransferable: boolean = false; - constructor(type: ModifierType, pokemonId: integer, stat: PermanentStat, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, stat: PermanentStat, stackCount?: number) { super(type, pokemonId, stackCount); this.stat = stat; } @@ -820,12 +889,23 @@ export class BaseStatModifier extends PokemonHeldItemModifier { return super.getArgs().concat(this.stat); } - shouldApply(args: any[]): boolean { - return super.shouldApply(args) && args.length === 2 && Array.isArray(args[1]); + /** + * Checks if {@linkcode BaseStatModifier} should be applied to the specified {@linkcode Pokemon}. + * @param _pokemon the {@linkcode Pokemon} to be modified + * @param baseStats the base stats of the {@linkcode Pokemon} + * @returns `true` if the {@linkcode Pokemon} should be modified + */ + override shouldApply(_pokemon?: Pokemon, baseStats?: number[]): boolean { + return super.shouldApply(_pokemon, baseStats) && Array.isArray(baseStats); } - apply(args: any[]): boolean { - const baseStats = args[1] as number[]; + /** + * Applies the {@linkcode BaseStatModifier} to the specified {@linkcode Pokemon}. + * @param _pokemon the {@linkcode Pokemon} to be modified + * @param baseStats the base stats of the {@linkcode Pokemon} + * @returns always `true` + */ + override apply(_pokemon: Pokemon, baseStats: number[]): boolean { baseStats[this.stat] = Math.floor(baseStats[this.stat] * (1 + this.getStackCount() * 0.1)); return true; } @@ -834,17 +914,17 @@ export class BaseStatModifier extends PokemonHeldItemModifier { return 1.1; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return pokemon.ivs[this.stat]; } } export class EvoTrackerModifier extends PokemonHeldItemModifier { protected species: Species; - protected required: integer; + protected required: number; public isTransferable: boolean = false; - constructor(type: ModifierType, pokemonId: integer, species: Species, required: integer, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, species: Species, required: number, stackCount?: number) { super(type, pokemonId, stackCount); this.species = species; this.required = required; @@ -862,7 +942,11 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier { return super.getArgs().concat([this.species, this.required]); } - apply(args: any[]): boolean { + /** + * Applies the {@linkcode EvoTrackerModifier} + * @returns always `true` + */ + override apply(): boolean { return true; } @@ -888,7 +972,7 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier { return text; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { this.stackCount = pokemon.evoCounter + pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length + pokemon.scene.findModifiers(m => m instanceof MoneyMultiplierModifier || m instanceof ExtraModifierModifier).length; return 999; @@ -899,10 +983,12 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier { * Currently used by Shuckle Juice item */ export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier { - private statModifier: integer; + public override type: PokemonBaseStatTotalModifierType; public isTransferable: boolean = false; - constructor(type: ModifierTypes.PokemonBaseStatTotalModifierType, pokemonId: number, statModifier: number, stackCount?: integer) { + private statModifier: number; + + constructor(type: PokemonBaseStatTotalModifierType, pokemonId: number, statModifier: number, stackCount?: number) { super(type, pokemonId, stackCount); this.statModifier = statModifier; } @@ -912,23 +998,35 @@ export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier { } override clone(): PersistentModifier { - return new PokemonBaseStatTotalModifier(this.type as ModifierTypes.PokemonBaseStatTotalModifierType, this.pokemonId, this.statModifier, this.stackCount); + return new PokemonBaseStatTotalModifier(this.type, this.pokemonId, this.statModifier, this.stackCount); } override getArgs(): any[] { return super.getArgs().concat(this.statModifier); } - override shouldApply(args: any[]): boolean { - return super.shouldApply(args) && args.length === 2 && args[1] instanceof Array; + /** + * Checks if {@linkcode PokemonBaseStatTotalModifier} should be applied to the specified {@linkcode Pokemon}. + * @param pokemon the {@linkcode Pokemon} to be modified + * @param baseStats the base stats of the {@linkcode Pokemon} + * @returns `true` if the {@linkcode Pokemon} should be modified + */ + override shouldApply(pokemon?: Pokemon, baseStats?: number[]): boolean { + return super.shouldApply(pokemon, baseStats) && Array.isArray(baseStats); } - override apply(args: any[]): boolean { + /** + * Applies the {@linkcode PokemonBaseStatTotalModifier} + * @param _pokemon the {@linkcode Pokemon} to be modified + * @param baseStats the base stats of the {@linkcode Pokemon} + * @returns always `true` + */ + override apply(_pokemon: Pokemon, baseStats: number[]): boolean { // Modifies the passed in baseStats[] array - args[1].forEach((v, i) => { + baseStats.forEach((v, i) => { // HP is affected by half as much as other stats const newVal = i === 0 ? Math.floor(v + this.statModifier / 2) : Math.floor(v + this.statModifier); - args[1][i] = Math.min(Math.max(newVal, 1), 999999); + baseStats[i] = Math.min(Math.max(newVal, 1), 999999); }); return true; @@ -938,7 +1036,7 @@ export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier { return 1.2; } - override getMaxHeldItemCount(pokemon: Pokemon): integer { + override getMaxHeldItemCount(pokemon: Pokemon): number { return 2; } } @@ -970,16 +1068,28 @@ export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier { return [ ...super.getArgs(), this.statModifier, this.stats ]; } - override shouldApply(args: any[]): boolean { - return super.shouldApply(args) && args.length === 2 && args[1] instanceof Array; + /** + * Checks if the {@linkcode PokemonBaseStatFlatModifier} should be applied to the {@linkcode Pokemon}. + * @param pokemon The {@linkcode Pokemon} that holds the item + * @param baseStats The base stats of the {@linkcode Pokemon} + * @returns `true` if the {@linkcode PokemonBaseStatFlatModifier} should be applied + */ + override shouldApply(pokemon?: Pokemon, baseStats?: number[]): boolean { + return super.shouldApply(pokemon, baseStats) && Array.isArray(baseStats); } - override apply(args: any[]): boolean { + /** + * Applies the {@linkcode PokemonBaseStatFlatModifier} + * @param _pokemon The {@linkcode Pokemon} that holds the item + * @param baseStats The base stats of the {@linkcode Pokemon} + * @returns always `true` + */ + override apply(_pokemon: Pokemon, baseStats: number[]): boolean { // Modifies the passed in baseStats[] array by a flat value, only if the stat is specified in this.stats - args[1].forEach((v, i) => { + baseStats.forEach((v, i) => { if (this.stats.includes(i)) { const newVal = Math.floor(v + this.statModifier); - args[1][i] = Math.min(Math.max(newVal, 1), 999999); + baseStats[i] = Math.min(Math.max(newVal, 1), 999999); } }); @@ -990,7 +1100,7 @@ export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier { return 1.1; } - override getMaxHeldItemCount(pokemon: Pokemon): integer { + override getMaxHeldItemCount(pokemon: Pokemon): number { return 1; } } @@ -1001,7 +1111,7 @@ export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier { export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier { public isTransferable: boolean = false; - constructor (type: ModifierType, pokemonId: integer, stackCount?: integer) { + constructor (type: ModifierType, pokemonId: number, stackCount?: number) { super(type, pokemonId, stackCount); } @@ -1017,15 +1127,28 @@ export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier { return super.getArgs(); } - shouldApply(args: any[]): boolean { - return super.shouldApply(args) && args.length === 3 && args[2] instanceof Utils.IntegerHolder; + /** + * Checks if the {@linkcode PokemonIncrementingStatModifier} should be applied to the {@linkcode Pokemon}. + * @param pokemon The {@linkcode Pokemon} that holds the item + * @param stat The affected {@linkcode Stat} + * @param statHolder The {@linkcode NumberHolder} that holds the stat + * @returns `true` if the {@linkcode PokemonBaseStatFlatModifier} should be applied + */ + override shouldApply(pokemon?: Pokemon, stat?: Stat, statHolder?: NumberHolder): boolean { + return super.shouldApply(pokemon, stat, statHolder) && !!statHolder; } - apply(args: any[]): boolean { - // Modifies the passed in stat integer holder by +1 per stack for HP, +2 per stack for other stats + /** + * Applies the {@linkcode PokemonIncrementingStatModifier} + * @param _pokemon The {@linkcode Pokemon} that holds the item + * @param stat The affected {@linkcode Stat} + * @param statHolder The {@linkcode NumberHolder} that holds the stat + * @returns always `true` + */ + override apply(_pokemon: Pokemon, stat: Stat, statHolder: NumberHolder): boolean { + // Modifies the passed in stat number holder by +1 per stack for HP, +2 per stack for other stats // If the Macho Brace is at max stacks (50), adds additional 5% to total HP and 10% to other stats - const isHp = args[1] === Stat.HP; - const statHolder = args[2] as Utils.IntegerHolder; + const isHp = stat === Stat.HP; if (isHp) { statHolder.value += this.stackCount; @@ -1046,13 +1169,13 @@ export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier { return 1.2; } - getMaxHeldItemCount(pokemon?: Pokemon): integer { + getMaxHeldItemCount(pokemon?: Pokemon): number { return 50; } } /** - * Modifier used for held items that apply {@linkcode Stat} boost(s) + * Modifier used for held items that Applies {@linkcode Stat} boost(s) * using a multiplier. * @extends PokemonHeldItemModifier * @see {@linkcode apply} @@ -1063,7 +1186,7 @@ export class StatBoosterModifier extends PokemonHeldItemModifier { /** The multiplier used to increase the relevant stat(s) */ protected multiplier: number; - constructor(type: ModifierType, pokemonId: integer, stats: Stat[], multiplier: number, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, stats: Stat[], multiplier: number, stackCount?: number) { super(type, pokemonId, stackCount); this.stats = stats; @@ -1091,27 +1214,25 @@ export class StatBoosterModifier extends PokemonHeldItemModifier { /** * Checks if the incoming stat is listed in {@linkcode stats} - * @param args [0] {@linkcode Pokemon} N/A - * [1] {@linkcode Stat} being checked at the time - * [2] {@linkcode Utils.NumberHolder} N/A - * @returns true if the stat could be boosted, false otherwise + * @param _pokemon the {@linkcode Pokemon} that holds the item + * @param _stat the {@linkcode Stat} to be boosted + * @param statValue {@linkcode NumberHolder} that holds the resulting value of the stat + * @returns `true` if the stat could be boosted, false otherwise */ - shouldApply(args: any[]): boolean { - return super.shouldApply(args) && this.stats.includes(args[1] as Stat); + override shouldApply(pokemon: Pokemon, stat: Stat, statValue: NumberHolder): boolean { + return super.shouldApply(pokemon, stat, statValue) && this.stats.includes(stat); } /** * Boosts the incoming stat by a {@linkcode multiplier} if the stat is listed * in {@linkcode stats}. - * @param args [0] {@linkcode Pokemon} N/A - * [1] {@linkcode Stat} N/A - * [2] {@linkcode Utils.NumberHolder} that holds the resulting value of the stat - * @returns true if the stat boost applies successfully, false otherwise + * @param _pokemon the {@linkcode Pokemon} that holds the item + * @param _stat the {@linkcode Stat} to be boosted + * @param statValue {@linkcode NumberHolder} that holds the resulting value of the stat + * @returns `true` if the stat boost applies successfully, false otherwise * @see shouldApply */ - apply(args: any[]): boolean { - const statValue = args[2] as Utils.NumberHolder; - + override apply(_pokemon: Pokemon, _stat: Stat, statValue: NumberHolder): boolean { statValue.value *= this.multiplier; return true; } @@ -1139,39 +1260,37 @@ export class EvolutionStatBoosterModifier extends StatBoosterModifier { /** * Checks if the stat boosts can apply and if the holder is not currently * Gigantamax'd. - * @param args [0] {@linkcode Pokemon} that holds the held item - * [1] {@linkcode Stat} N/A - * [2] {@linkcode Utils.NumberHolder} N/A - * @returns true if the stat boosts can be applied, false otherwise + * @param pokemon {@linkcode Pokemon} that holds the held item + * @param stat {@linkcode Stat} The {@linkcode Stat} to be boosted + * @param statValue {@linkcode NumberHolder} that holds the resulting value of the stat + * @returns `true` if the stat boosts can be applied, false otherwise */ - shouldApply(args: any[]): boolean { - return super.shouldApply(args) && !(args[0] as Pokemon).isMax(); + override shouldApply(pokemon: Pokemon, stat: Stat, statValue: NumberHolder): boolean { + return super.shouldApply(pokemon, stat, statValue) && !pokemon.isMax(); } /** - * Boosts the incoming stat value by a {@linkcode multiplier} if the holder + * Boosts the incoming stat value by a {@linkcode EvolutionStatBoosterModifier.multiplier} if the holder * can evolve. Note that, if the holder is a fusion, they will receive * only half of the boost if either of the fused members are fully * evolved. However, if they are both unevolved, the full boost * will apply. - * @param args [0] {@linkcode Pokemon} that holds the held item - * [1] {@linkcode Stat} N/A - * [2] {@linkcode Utils.NumberHolder} that holds the resulting value of the stat - * @returns true if the stat boost applies successfully, false otherwise + * @param pokemon {@linkcode Pokemon} that holds the item + * @param _stat {@linkcode Stat} The {@linkcode Stat} to be boosted + * @param statValue{@linkcode NumberHolder} that holds the resulting value of the stat + * @returns `true` if the stat boost applies successfully, false otherwise * @see shouldApply */ - apply(args: any[]): boolean { - const holder = args[0] as Pokemon; - const statValue = args[2] as Utils.NumberHolder; - const isUnevolved = holder.getSpeciesForm(true).speciesId in pokemonEvolutions; + override apply(pokemon: Pokemon, stat: Stat, statValue: NumberHolder): boolean { + const isUnevolved = pokemon.getSpeciesForm(true).speciesId in pokemonEvolutions; - if (holder.isFusion() && (holder.getFusionSpeciesForm(true).speciesId in pokemonEvolutions) !== isUnevolved) { - // Half boost applied if holder is fused and either part of fusion is fully evolved + if (pokemon.isFusion() && (pokemon.getFusionSpeciesForm(true).speciesId in pokemonEvolutions) !== isUnevolved) { + // Half boost applied if pokemon is fused and either part of fusion is fully evolved statValue.value *= 1 + (this.multiplier - 1) / 2; return true; } else if (isUnevolved) { // Full boost applied if holder is unfused and unevolved or, if fused, both parts of fusion are unevolved - return super.apply(args); + return super.apply(pokemon, stat, statValue); } return false; @@ -1179,7 +1298,7 @@ export class EvolutionStatBoosterModifier extends StatBoosterModifier { } /** - * Modifier used for held items that apply {@linkcode Stat} boost(s) using a + * Modifier used for held items that Applies {@linkcode Stat} boost(s) using a * multiplier if the holder is of a specific {@linkcode Species}. * @extends StatBoosterModifier * @see {@linkcode apply} @@ -1188,7 +1307,7 @@ export class SpeciesStatBoosterModifier extends StatBoosterModifier { /** The species that the held item's stat boost(s) apply to */ private species: Species[]; - constructor(type: ModifierType, pokemonId: integer, stats: Stat[], multiplier: number, species: Species[], stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, stats: Stat[], multiplier: number, species: Species[], stackCount?: number) { super(type, pokemonId, stats, multiplier, stackCount); this.species = species; @@ -1216,21 +1335,20 @@ export class SpeciesStatBoosterModifier extends StatBoosterModifier { /** * Checks if the incoming stat is listed in {@linkcode stats} and if the holder's {@linkcode Species} * (or its fused species) is listed in {@linkcode species}. - * @param args [0] {@linkcode Pokemon} that holds the held item - * [1] {@linkcode Stat} being checked at the time - * [2] {@linkcode Utils.NumberHolder} N/A - * @returns true if the stat could be boosted, false otherwise + * @param pokemon {@linkcode Pokemon} that holds the item + * @param stat {@linkcode Stat} being checked at the time + * @param statValue {@linkcode NumberHolder} that holds the resulting value of the stat + * @returns `true` if the stat could be boosted, false otherwise */ - shouldApply(args: any[]): boolean { - const holder = args[0] as Pokemon; - return super.shouldApply(args) && (this.species.includes(holder.getSpeciesForm(true).speciesId) || (holder.isFusion() && this.species.includes(holder.getFusionSpeciesForm(true).speciesId))); + override shouldApply(pokemon: Pokemon, stat: Stat, statValue: NumberHolder): boolean { + return super.shouldApply(pokemon, stat, statValue) && (this.species.includes(pokemon.getSpeciesForm(true).speciesId) || (pokemon.isFusion() && this.species.includes(pokemon.getFusionSpeciesForm(true).speciesId))); } /** * Checks if either parameter is included in the corresponding lists * @param speciesId {@linkcode Species} being checked * @param stat {@linkcode Stat} being checked - * @returns true if both parameters are in {@linkcode species} and {@linkcode stats} respectively, false otherwise + * @returns `true` if both parameters are in {@linkcode species} and {@linkcode stats} respectively, false otherwise */ contains(speciesId: Species, stat: Stat): boolean { return this.species.includes(speciesId) && this.stats.includes(stat); @@ -1246,7 +1364,7 @@ export class CritBoosterModifier extends PokemonHeldItemModifier { /** The amount of stages by which the held item increases the current critical-hit stage value */ protected stageIncrement: number; - constructor(type: ModifierType, pokemonId: integer, stageIncrement: number, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, stageIncrement: number, stackCount?: number) { super(type, pokemonId, stackCount); this.stageIncrement = stageIncrement; @@ -1270,13 +1388,11 @@ export class CritBoosterModifier extends PokemonHeldItemModifier { /** * Increases the current critical-hit stage value by {@linkcode stageIncrement}. - * @param args [0] {@linkcode Pokemon} N/A - * [1] {@linkcode Utils.IntegerHolder} that holds the resulting critical-hit level - * @returns true if the critical-hit stage boost applies successfully, false otherwise + * @param _pokemon {@linkcode Pokemon} N/A + * @param critStage {@linkcode NumberHolder} that holds the resulting critical-hit level + * @returns always `true` */ - apply(args: any[]): boolean { - const critStage = args[1] as Utils.NumberHolder; - + override apply(_pokemon: Pokemon, critStage: NumberHolder): boolean { critStage.value += this.stageIncrement; return true; } @@ -1296,7 +1412,7 @@ export class SpeciesCritBoosterModifier extends CritBoosterModifier { /** The species that the held item's critical-hit stage boost applies to */ private species: Species[]; - constructor(type: ModifierType, pokemonId: integer, stageIncrement: number, species: Species[], stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, stageIncrement: number, species: Species[], stackCount?: number) { super(type, pokemonId, stageIncrement, stackCount); this.species = species; @@ -1317,14 +1433,12 @@ export class SpeciesCritBoosterModifier extends CritBoosterModifier { /** * Checks if the holder's {@linkcode Species} (or its fused species) is listed * in {@linkcode species}. - * @param args [0] {@linkcode Pokemon} that holds the held item - * [1] {@linkcode Utils.IntegerHolder} N/A - * @returns true if the critical-hit level can be incremented, false otherwise + * @param pokemon {@linkcode Pokemon} that holds the held item + * @param critStage {@linkcode NumberHolder} that holds the resulting critical-hit level + * @returns `true` if the critical-hit level can be incremented, false otherwise */ - shouldApply(args: any[]) { - const holder = args[0] as Pokemon; - - return super.shouldApply(args) && (this.species.includes(holder.getSpeciesForm(true).speciesId) || (holder.isFusion() && this.species.includes(holder.getFusionSpeciesForm(true).speciesId))); + override shouldApply(pokemon: Pokemon, critStage: NumberHolder): boolean { + return super.shouldApply(pokemon, critStage) && (this.species.includes(pokemon.getSpeciesForm(true).speciesId) || (pokemon.isFusion() && this.species.includes(pokemon.getFusionSpeciesForm(true).speciesId))); } } @@ -1335,7 +1449,7 @@ export class AttackTypeBoosterModifier extends PokemonHeldItemModifier { public moveType: Type; private boostMultiplier: number; - constructor(type: ModifierType, pokemonId: integer, moveType: Type, boostPercent: number, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, moveType: Type, boostPercent: number, stackCount?: number) { super(type, pokemonId, stackCount); this.moveType = moveType; @@ -1359,20 +1473,27 @@ export class AttackTypeBoosterModifier extends PokemonHeldItemModifier { return super.getArgs().concat([ this.moveType, this.boostMultiplier * 100 ]); } - shouldApply(args: any[]): boolean { - return super.shouldApply(args) && args.length === 3 && typeof args[1] === "number" && args[2] instanceof Utils.NumberHolder; + /** + * Checks if {@linkcode AttackTypeBoosterModifier} should be applied + * @param pokemon the {@linkcode Pokemon} that holds the held item + * @param moveType the {@linkcode Type} of the move being used + * @param movePower the {@linkcode NumberHolder} that holds the power of the move + * @returns `true` if boosts should be applied to the move. + */ + override shouldApply(pokemon?: Pokemon, moveType?: Type, movePower?: NumberHolder): boolean { + return super.shouldApply(pokemon, moveType, movePower) && typeof moveType === "number" && movePower instanceof NumberHolder; } /** - * @param {Array} args Array - * - Index 0: {Pokemon} Pokemon - * - Index 1: {number} Move type - * - Index 2: {Utils.NumberHolder} Move power - * @returns {boolean} Returns true if boosts have been applied to the move. - */ - apply(args: any[]): boolean { - if (args[1] === this.moveType && (args[2] as Utils.NumberHolder).value >= 1) { - (args[2] as Utils.NumberHolder).value = Math.floor((args[2] as Utils.NumberHolder).value * (1 + (this.getStackCount() * this.boostMultiplier))); + * Applies {@linkcode AttackTypeBoosterModifier} + * @param pokemon {@linkcode Pokemon} that holds the held item + * @param moveType {@linkcode Type} of the move being used + * @param movePower {@linkcode NumberHolder} that holds the power of the move + * @returns `true` if boosts have been applied to the move. + */ + override apply(_pokemon: Pokemon, moveType: Type, movePower: NumberHolder): boolean { + if (moveType === this.moveType && movePower.value >= 1) { + (movePower as NumberHolder).value = Math.floor((movePower as NumberHolder).value * (1 + (this.getStackCount() * this.boostMultiplier))); return true; } @@ -1383,13 +1504,13 @@ export class AttackTypeBoosterModifier extends PokemonHeldItemModifier { return 1.2; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 99; } } export class SurviveDamageModifier extends PokemonHeldItemModifier { - constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, stackCount?: number) { super(type, pokemonId, stackCount); } @@ -1401,14 +1522,23 @@ export class SurviveDamageModifier extends PokemonHeldItemModifier { return new SurviveDamageModifier(this.type, this.pokemonId, this.stackCount); } - shouldApply(args: any[]): boolean { - return super.shouldApply(args) && args.length === 2 && args[1] instanceof Utils.BooleanHolder; + /** + * Checks if the {@linkcode SurviveDamageModifier} should be applied + * @param pokemon the {@linkcode Pokemon} that holds the item + * @param surviveDamage {@linkcode BooleanHolder} that holds the survive damage + * @returns `true` if the {@linkcode SurviveDamageModifier} should be applied + */ + override shouldApply(pokemon?: Pokemon, surviveDamage?: BooleanHolder): boolean { + return super.shouldApply(pokemon, surviveDamage) && !!surviveDamage; } - apply(args: any[]): boolean { - const pokemon = args[0] as Pokemon; - const surviveDamage = args[1] as Utils.BooleanHolder; - + /** + * Applies {@linkcode SurviveDamageModifier} + * @param pokemon the {@linkcode Pokemon} that holds the item + * @param surviveDamage {@linkcode BooleanHolder} that holds the survive damage + * @returns `true` if the survive damage has been applied + */ + override apply(pokemon: Pokemon, surviveDamage: BooleanHolder): boolean { if (!surviveDamage.value && pokemon.randSeedInt(10) < this.getStackCount()) { surviveDamage.value = true; @@ -1419,13 +1549,13 @@ export class SurviveDamageModifier extends PokemonHeldItemModifier { return false; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 5; } } export class BypassSpeedChanceModifier extends PokemonHeldItemModifier { - constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, stackCount?: number) { super(type, pokemonId, stackCount); } @@ -1437,18 +1567,27 @@ export class BypassSpeedChanceModifier extends PokemonHeldItemModifier { return new BypassSpeedChanceModifier(this.type, this.pokemonId, this.stackCount); } - shouldApply(args: any[]): boolean { - return super.shouldApply(args) && args.length === 2 && args[1] instanceof Utils.BooleanHolder; + /** + * Checks if {@linkcode BypassSpeedChanceModifier} should be applied + * @param pokemon the {@linkcode Pokemon} that holds the item + * @param doBypassSpeed {@linkcode BooleanHolder} that is `true` if speed should be bypassed + * @returns `true` if {@linkcode BypassSpeedChanceModifier} should be applied + */ + override shouldApply(pokemon?: Pokemon, doBypassSpeed?: BooleanHolder): boolean { + return super.shouldApply(pokemon, doBypassSpeed) && !!doBypassSpeed; } - apply(args: any[]): boolean { - const pokemon = args[0] as Pokemon; - const bypassSpeed = args[1] as Utils.BooleanHolder; - - if (!bypassSpeed.value && pokemon.randSeedInt(10) < this.getStackCount()) { - bypassSpeed.value = true; + /** + * Applies {@linkcode BypassSpeedChanceModifier} + * @param pokemon the {@linkcode Pokemon} that holds the item + * @param doBypassSpeed {@linkcode BooleanHolder} that is `true` if speed should be bypassed + * @returns `true` if {@linkcode BypassSpeedChanceModifier} has been applied + */ + override apply(pokemon: Pokemon, doBypassSpeed: BooleanHolder): boolean { + if (!doBypassSpeed.value && pokemon.randSeedInt(10) < this.getStackCount()) { + doBypassSpeed.value = true; const isCommandFight = pokemon.scene.currentBattle.turnCommands[pokemon.getBattlerIndex()]?.command === Command.FIGHT; - const hasQuickClaw = this.type instanceof ModifierTypes.PokemonHeldItemModifierType && this.type.id === "QUICK_CLAW"; + const hasQuickClaw = this.type instanceof PokemonHeldItemModifierType && this.type.id === "QUICK_CLAW"; if (isCommandFight && hasQuickClaw) { pokemon.scene.queueMessage(i18next.t("modifier:bypassSpeedChanceApply", { pokemonName: getPokemonNameWithAffix(pokemon), itemName: i18next.t("modifierType:ModifierType.QUICK_CLAW.name") })); @@ -1459,13 +1598,13 @@ export class BypassSpeedChanceModifier extends PokemonHeldItemModifier { return false; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 3; } } export class FlinchChanceModifier extends PokemonHeldItemModifier { - constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, stackCount?: number) { super(type, pokemonId, stackCount); } @@ -1477,14 +1616,23 @@ export class FlinchChanceModifier extends PokemonHeldItemModifier { return new FlinchChanceModifier(this.type, this.pokemonId, this.stackCount); } - shouldApply(args: any[]): boolean { - return super.shouldApply(args) && args.length === 2 && args[1] instanceof Utils.BooleanHolder; + /** + * Checks if {@linkcode FlinchChanceModifier} should be applied + * @param pokemon the {@linkcode Pokemon} that holds the item + * @param flinched {@linkcode BooleanHolder} that is `true` if the pokemon flinched + * @returns `true` if {@linkcode FlinchChanceModifier} should be applied + */ + override shouldApply(pokemon?: Pokemon, flinched?: BooleanHolder): boolean { + return super.shouldApply(pokemon, flinched) && !!flinched; } - apply(args: any[]): boolean { - const pokemon = args[0] as Pokemon; - const flinched = args[1] as Utils.BooleanHolder; - + /** + * Applies {@linkcode FlinchChanceModifier} + * @param pokemon the {@linkcode Pokemon} that holds the item + * @param flinched {@linkcode BooleanHolder} that is `true` if the pokemon flinched + * @returns `true` if {@linkcode FlinchChanceModifier} has been applied + */ + override apply(pokemon: Pokemon, flinched: BooleanHolder): boolean { if (!flinched.value && pokemon.randSeedInt(10) < (this.getStackCount() * this.getSecondaryChanceMultiplier(pokemon))) { flinched.value = true; return true; @@ -1493,13 +1641,13 @@ export class FlinchChanceModifier extends PokemonHeldItemModifier { return false; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 3; } } export class TurnHealModifier extends PokemonHeldItemModifier { - constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, stackCount?: number) { super(type, pokemonId, stackCount); } @@ -1511,20 +1659,23 @@ export class TurnHealModifier extends PokemonHeldItemModifier { return new TurnHealModifier(this.type, this.pokemonId, this.stackCount); } - apply(args: any[]): boolean { - const pokemon = args[0] as Pokemon; - + /** + * Applies {@linkcode TurnHealModifier} + * @param pokemon The {@linkcode Pokemon} that holds the item + * @returns `true` if the {@linkcode Pokemon} was healed + */ + override apply(pokemon: Pokemon): boolean { if (!pokemon.isFullHp()) { const scene = pokemon.scene; scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), - Utils.toDmgValue(pokemon.getMaxHp() / 16) * this.stackCount, i18next.t("modifier:turnHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true)); + toDmgValue(pokemon.getMaxHp() / 16) * this.stackCount, i18next.t("modifier:turnHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true)); return true; } return false; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 4; } } @@ -1539,7 +1690,7 @@ export class TurnStatusEffectModifier extends PokemonHeldItemModifier { /** The status effect to be applied by the held item */ private effect: StatusEffect; - constructor (type: ModifierType, pokemonId: integer, stackCount?: integer) { + constructor (type: ModifierType, pokemonId: number, stackCount?: number) { super(type, pokemonId, stackCount); switch (type.id) { @@ -1559,7 +1710,7 @@ export class TurnStatusEffectModifier extends PokemonHeldItemModifier { * would be the only item able to {@linkcode apply} successfully. * @override * @param modifier {@linkcode Modifier} being type tested - * @return true if {@linkcode modifier} is an instance of + * @return `true` if {@linkcode modifier} is an instance of * TurnStatusEffectModifier, false otherwise */ matchType(modifier: Modifier): boolean { @@ -1572,15 +1723,14 @@ export class TurnStatusEffectModifier extends PokemonHeldItemModifier { /** * Tries to inflicts the holder with the associated {@linkcode StatusEffect}. - * @param args [0] {@linkcode Pokemon} that holds the held item - * @returns true if the status effect was applied successfully, false if - * otherwise + * @param pokemon {@linkcode Pokemon} that holds the held item + * @returns `true` if the status effect was applied successfully */ - apply(args: any[]): boolean { - return (args[0] as Pokemon).trySetStatus(this.effect, true, undefined, undefined, this.type.name); + override apply(pokemon: Pokemon): boolean { + return pokemon.trySetStatus(this.effect, true, undefined, undefined, this.type.name); } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 1; } @@ -1590,7 +1740,7 @@ export class TurnStatusEffectModifier extends PokemonHeldItemModifier { } export class HitHealModifier extends PokemonHeldItemModifier { - constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, stackCount?: number) { super(type, pokemonId, stackCount); } @@ -1602,25 +1752,28 @@ export class HitHealModifier extends PokemonHeldItemModifier { return new HitHealModifier(this.type, this.pokemonId, this.stackCount); } - apply(args: any[]): boolean { - const pokemon = args[0] as Pokemon; - + /** + * Applies {@linkcode HitHealModifier} + * @param pokemon The {@linkcode Pokemon} that holds the item + * @returns `true` if the {@linkcode Pokemon} was healed + */ + override apply(pokemon: Pokemon): boolean { if (pokemon.turnData.damageDealt && !pokemon.isFullHp()) { const scene = pokemon.scene; scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), - Utils.toDmgValue(pokemon.turnData.damageDealt / 8) * this.stackCount, i18next.t("modifier:hitHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true)); + toDmgValue(pokemon.turnData.damageDealt / 8) * this.stackCount, i18next.t("modifier:hitHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true)); } return true; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 4; } } export class LevelIncrementBoosterModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } @@ -1632,12 +1785,22 @@ export class LevelIncrementBoosterModifier extends PersistentModifier { return new LevelIncrementBoosterModifier(this.type, this.stackCount); } - shouldApply(args: any[]): boolean { - return super.shouldApply(args) && args[0] instanceof Utils.IntegerHolder; + /** + * Checks if {@linkcode LevelIncrementBoosterModifier} should be applied + * @param count {@linkcode NumberHolder} holding the level increment count + * @returns `true` if {@linkcode LevelIncrementBoosterModifier} should be applied + */ + override shouldApply(count: NumberHolder): boolean { + return !!count; } - apply(args: any[]): boolean { - (args[0] as Utils.IntegerHolder).value += this.getStackCount(); + /** + * Applies {@linkcode LevelIncrementBoosterModifier} + * @param count {@linkcode NumberHolder} holding the level increment count + * @returns always `true` + */ + override apply(count: NumberHolder): boolean { + count.value += this.getStackCount(); return true; } @@ -1651,7 +1814,7 @@ export class BerryModifier extends PokemonHeldItemModifier { public berryType: BerryType; public consumed: boolean; - constructor(type: ModifierType, pokemonId: integer, berryType: BerryType, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, berryType: BerryType, stackCount?: number) { super(type, pokemonId, stackCount); this.berryType = berryType; @@ -1670,14 +1833,22 @@ export class BerryModifier extends PokemonHeldItemModifier { return super.getArgs().concat(this.berryType); } - shouldApply(args: any[]): boolean { - return !this.consumed && super.shouldApply(args) && getBerryPredicate(this.berryType)(args[0] as Pokemon); + /** + * Checks if {@linkcode BerryModifier} should be applied + * @param pokemon The {@linkcode Pokemon} that holds the berry + * @returns `true` if {@linkcode BerryModifier} should be applied + */ + override shouldApply(pokemon: Pokemon): boolean { + return !this.consumed && super.shouldApply(pokemon) && getBerryPredicate(this.berryType)(pokemon); } - apply(args: any[]): boolean { - const pokemon = args[0] as Pokemon; - - const preserve = new Utils.BooleanHolder(false); + /** + * Applies {@linkcode BerryModifier} + * @param pokemon The {@linkcode Pokemon} that holds the berry + * @returns always `true` + */ + override apply(pokemon: Pokemon): boolean { + const preserve = new BooleanHolder(false); pokemon.scene.applyModifiers(PreserveBerryModifier, pokemon.isPlayer(), pokemon, preserve); getBerryEffectFunc(this.berryType)(pokemon); @@ -1688,7 +1859,7 @@ export class BerryModifier extends PokemonHeldItemModifier { return true; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { if ([BerryType.LUM, BerryType.LEPPA, BerryType.SITRUS, BerryType.ENIGMA].includes(this.berryType)) { return 2; } @@ -1697,7 +1868,7 @@ export class BerryModifier extends PokemonHeldItemModifier { } export class PreserveBerryModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } @@ -1709,25 +1880,37 @@ export class PreserveBerryModifier extends PersistentModifier { return new PreserveBerryModifier(this.type, this.stackCount); } - shouldApply(args: any[]): boolean { - return super.shouldApply(args) && args[0] instanceof Pokemon && args[1] instanceof Utils.BooleanHolder; + /** + * Checks if all prequired conditions are met to apply {@linkcode PreserveBerryModifier} + * @param pokemon {@linkcode Pokemon} that holds the berry + * @param doPreserve {@linkcode BooleanHolder} that is `true` if the berry should be preserved + * @returns `true` if {@linkcode PreserveBerryModifier} should be applied + */ + override shouldApply(pokemon?: Pokemon, doPreserve?: BooleanHolder): boolean { + return !!pokemon && !!doPreserve; } - apply(args: any[]): boolean { - if (!(args[1] as Utils.BooleanHolder).value) { - (args[1] as Utils.BooleanHolder).value = (args[0] as Pokemon).randSeedInt(10) < this.getStackCount() * 3; + /** + * Applies {@linkcode PreserveBerryModifier} + * @param pokemon The {@linkcode Pokemon} that holds the berry + * @param doPreserve {@linkcode BooleanHolder} that is `true` if the berry should be preserved + * @returns always `true` + */ + override apply(pokemon: Pokemon, doPreserve: BooleanHolder): boolean { + if (!doPreserve.value) { + doPreserve.value = pokemon.randSeedInt(10) < this.getStackCount() * 3; } return true; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 3; } } export class PokemonInstantReviveModifier extends PokemonHeldItemModifier { - constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, stackCount?: number) { super(type, pokemonId, stackCount); } @@ -1739,17 +1922,20 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier { return new PokemonInstantReviveModifier(this.type, this.pokemonId, this.stackCount); } - apply(args: any[]): boolean { - const pokemon = args[0] as Pokemon; - + /** + * Applies {@linkcode PokemonInstantReviveModifier} + * @param pokemon The {@linkcode Pokemon} that holds the item + * @returns always `true` + */ + override apply(pokemon: Pokemon): boolean { pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), - Utils.toDmgValue(pokemon.getMaxHp() / 2), i18next.t("modifier:pokemonInstantReviveApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), false, false, true)); + toDmgValue(pokemon.getMaxHp() / 2), i18next.t("modifier:pokemonInstantReviveApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), false, false, true)); pokemon.resetStatus(true, false, true); return true; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 1; } } @@ -1761,7 +1947,7 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier { * @see {@linkcode apply} */ export class ResetNegativeStatStageModifier extends PokemonHeldItemModifier { - constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, stackCount?: number) { super(type, pokemonId, stackCount); } @@ -1776,11 +1962,10 @@ export class ResetNegativeStatStageModifier extends PokemonHeldItemModifier { /** * Goes through the holder's stat stages and, if any are negative, resets that * stat stage back to 0. - * @param args [0] {@linkcode Pokemon} that holds the held item - * @returns true if any stat stages were reset, false otherwise + * @param pokemon {@linkcode Pokemon} that holds the item + * @returns `true` if any stat stages were reset, false otherwise */ - apply(args: any[]): boolean { - const pokemon = args[0] as Pokemon; + override apply(pokemon: Pokemon): boolean { let statRestored = false; for (const s of BATTLE_STATS) { @@ -1796,36 +1981,49 @@ export class ResetNegativeStatStageModifier extends PokemonHeldItemModifier { return statRestored; } - getMaxHeldItemCount(_pokemon: Pokemon): integer { + getMaxHeldItemCount(_pokemon: Pokemon): number { return 2; } } export abstract class ConsumablePokemonModifier extends ConsumableModifier { - public pokemonId: integer; + public pokemonId: number; - constructor(type: ModifierType, pokemonId: integer) { + constructor(type: ModifierType, pokemonId: number) { super(type); this.pokemonId = pokemonId; } - shouldApply(args: any[]): boolean { - return args.length !== 0 && args[0] instanceof PlayerPokemon && (this.pokemonId === -1 || (args[0] as PlayerPokemon).id === this.pokemonId); + /** + * Checks if {@linkcode ConsumablePokemonModifier} should be applied + * @param playerPokemon The {@linkcode PlayerPokemon} that consumes the item + * @param _args N/A + * @returns `true` if {@linkcode ConsumablePokemonModifier} should be applied + */ + override shouldApply(playerPokemon?: PlayerPokemon, ..._args: unknown[]): boolean { + return !!playerPokemon && (this.pokemonId === -1 || playerPokemon.id === this.pokemonId); } + /** + * Applies {@linkcode ConsumablePokemonModifier} + * @param playerPokemon The {@linkcode PlayerPokemon} that consumes the item + * @param args Additional arguments passed to {@linkcode ConsumablePokemonModifier.apply} + */ + abstract override apply(playerPokemon: PlayerPokemon, ...args: unknown[]): boolean | Promise; + getPokemon(scene: BattleScene) { return scene.getParty().find(p => p.id === this.pokemonId); } } export class PokemonHpRestoreModifier extends ConsumablePokemonModifier { - private restorePoints: integer; + private restorePoints: number; private restorePercent: number; private healStatus: boolean; public fainted: boolean; - constructor(type: ModifierType, pokemonId: integer, restorePoints: integer, restorePercent: number, healStatus: boolean, fainted?: boolean) { + constructor(type: ModifierType, pokemonId: number, restorePoints: number, restorePercent: number, healStatus: boolean, fainted?: boolean) { super(type, pokemonId); this.restorePoints = restorePoints; @@ -1834,16 +2032,27 @@ export class PokemonHpRestoreModifier extends ConsumablePokemonModifier { this.fainted = !!fainted; } - shouldApply(args: any[]): boolean { - return super.shouldApply(args) && (this.fainted || (args.length > 1 && typeof(args[1]) === "number")); + /** + * Checks if {@linkcode PokemonHpRestoreModifier} should be applied + * @param playerPokemon The {@linkcode PlayerPokemon} that consumes the item + * @param multiplier The multiplier of the hp restore + * @returns `true` if the {@linkcode PokemonHpRestoreModifier} should be applied + */ + override shouldApply(playerPokemon?: PlayerPokemon, multiplier?: number): boolean { + return super.shouldApply(playerPokemon) && (this.fainted || (!isNullOrUndefined(multiplier) && typeof(multiplier) === "number")); } - apply(args: any[]): boolean { - const pokemon = args[0] as Pokemon; + /** + * Applies {@linkcode PokemonHpRestoreModifier} + * @param pokemon The {@linkcode PlayerPokemon} that consumes the item + * @param multiplier The multiplier of the hp restore + * @returns `true` if hp was restored + */ + override apply(pokemon: Pokemon, multiplier: number): boolean { if (!pokemon.hp === this.fainted) { let restorePoints = this.restorePoints; if (!this.fainted) { - restorePoints = Math.floor(restorePoints * (args[1] as number)); + restorePoints = Math.floor(restorePoints * multiplier); } if (this.fainted || this.healStatus) { pokemon.resetStatus(true, true); @@ -1856,21 +2065,25 @@ export class PokemonHpRestoreModifier extends ConsumablePokemonModifier { } export class PokemonStatusHealModifier extends ConsumablePokemonModifier { - constructor(type: ModifierType, pokemonId: integer) { + constructor(type: ModifierType, pokemonId: number) { super(type, pokemonId); } - apply(args: any[]): boolean { - const pokemon = args[0] as Pokemon; - pokemon.resetStatus(true, true); + /** + * Applies {@linkcode PokemonStatusHealModifier} + * @param playerPokemon The {@linkcode PlayerPokemon} that gets healed from the status + * @returns always `true` + */ + override apply(playerPokemon: PlayerPokemon): boolean { + playerPokemon.resetStatus(true, true); return true; } } export abstract class ConsumablePokemonMoveModifier extends ConsumablePokemonModifier { - public moveIndex: integer; + public moveIndex: number; - constructor(type: ModifierType, pokemonId: integer, moveIndex: integer) { + constructor(type: ModifierType, pokemonId: number, moveIndex: number) { super(type, pokemonId); this.moveIndex = moveIndex; @@ -1878,36 +2091,49 @@ export abstract class ConsumablePokemonMoveModifier extends ConsumablePokemonMod } export class PokemonPpRestoreModifier extends ConsumablePokemonMoveModifier { - private restorePoints: integer; + private restorePoints: number; - constructor(type: ModifierType, pokemonId: integer, moveIndex: integer, restorePoints: integer) { + constructor(type: ModifierType, pokemonId: number, moveIndex: number, restorePoints: number) { super(type, pokemonId, moveIndex); this.restorePoints = restorePoints; } - apply(args: any[]): boolean { - const pokemon = args[0] as Pokemon; - const move = pokemon.getMoveset()[this.moveIndex]!; //TODO: is the bang correct? - move.ppUsed = this.restorePoints > -1 ? Math.max(move.ppUsed - this.restorePoints, 0) : 0; + /** + * Applies {@linkcode PokemonPpRestoreModifier} + * @param playerPokemon The {@linkcode PlayerPokemon} that should get move pp restored + * @returns always `true` + */ + override apply(playerPokemon: PlayerPokemon): boolean { + const move = playerPokemon.getMoveset()[this.moveIndex]; + + if (move) { + move.ppUsed = this.restorePoints > -1 ? Math.max(move.ppUsed - this.restorePoints, 0) : 0; + } return true; } } export class PokemonAllMovePpRestoreModifier extends ConsumablePokemonModifier { - private restorePoints: integer; + private restorePoints: number; - constructor(type: ModifierType, pokemonId: integer, restorePoints: integer) { + constructor(type: ModifierType, pokemonId: number, restorePoints: number) { super(type, pokemonId); this.restorePoints = restorePoints; } - apply(args: any[]): boolean { - const pokemon = args[0] as Pokemon; - for (const move of pokemon.getMoveset()) { - move!.ppUsed = this.restorePoints > -1 ? Math.max(move!.ppUsed - this.restorePoints, 0) : 0; // TODO: are those bangs correct? + /** + * Applies {@linkcode PokemonAllMovePpRestoreModifier} + * @param playerPokemon The {@linkcode PlayerPokemon} that should get all move pp restored + * @returns always `true` + */ + override apply(playerPokemon: PlayerPokemon): boolean { + for (const move of playerPokemon.getMoveset()) { + if (move) { + move.ppUsed = this.restorePoints > -1 ? Math.max(move.ppUsed - this.restorePoints, 0) : 0; + } } return true; @@ -1915,18 +2141,25 @@ export class PokemonAllMovePpRestoreModifier extends ConsumablePokemonModifier { } export class PokemonPpUpModifier extends ConsumablePokemonMoveModifier { - private upPoints: integer; + private upPoints: number; - constructor(type: ModifierType, pokemonId: integer, moveIndex: integer, upPoints: integer) { + constructor(type: ModifierType, pokemonId: number, moveIndex: number, upPoints: number) { super(type, pokemonId, moveIndex); this.upPoints = upPoints; } - apply(args: any[]): boolean { - const pokemon = args[0] as Pokemon; - const move = pokemon.getMoveset()[this.moveIndex]!; // TODO: is the bang correct? - move.ppUp = Math.min(move.ppUp + this.upPoints, 3); + /** + * Applies {@linkcode PokemonPpUpModifier} + * @param playerPokemon The {@linkcode PlayerPokemon} that gets a pp up on move-slot {@linkcode moveIndex} + * @returns + */ + override apply(playerPokemon: PlayerPokemon): boolean { + const move = playerPokemon.getMoveset()[this.moveIndex]; + + if (move) { + move.ppUp = Math.min(move.ppUp + this.upPoints, 3); + } return true; } @@ -1935,21 +2168,25 @@ export class PokemonPpUpModifier extends ConsumablePokemonMoveModifier { export class PokemonNatureChangeModifier extends ConsumablePokemonModifier { public nature: Nature; - constructor(type: ModifierType, pokemonId: integer, nature: Nature) { + constructor(type: ModifierType, pokemonId: number, nature: Nature) { super(type, pokemonId); this.nature = nature; } - apply(args: any[]): boolean { - const pokemon = args[0] as Pokemon; - pokemon.natureOverride = this.nature; - let speciesId = pokemon.species.speciesId; - pokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1); + /** + * Applies {@linkcode PokemonNatureChangeModifier} + * @param playerPokemon {@linkcode PlayerPokemon} to apply the {@linkcode Nature} change to + * @returns + */ + override apply(playerPokemon: PlayerPokemon): boolean { + playerPokemon.natureOverride = this.nature; + let speciesId = playerPokemon.species.speciesId; + playerPokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1); while (pokemonPrevolutions.hasOwnProperty(speciesId)) { speciesId = pokemonPrevolutions[speciesId]; - pokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1); + playerPokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1); } return true; @@ -1957,86 +2194,104 @@ export class PokemonNatureChangeModifier extends ConsumablePokemonModifier { } export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier { - constructor(type: ModifierType, pokemonId: integer) { + constructor(type: ModifierType, pokemonId: number) { super(type, pokemonId); } - apply(args: any[]): boolean { - const pokemon = args[0] as PlayerPokemon; - const levelCount = new Utils.IntegerHolder(1); - pokemon.scene.applyModifiers(LevelIncrementBoosterModifier, true, levelCount); + /** + * Applies {@linkcode PokemonLevelIncrementModifier} + * @param playerPokemon The {@linkcode PlayerPokemon} that should get levels incremented + * @param levelCount The amount of levels to increment + * @returns always `true` + */ + override apply(playerPokemon: PlayerPokemon, levelCount: NumberHolder): boolean { + playerPokemon.scene.applyModifiers(LevelIncrementBoosterModifier, true, levelCount); - pokemon.level += levelCount.value; - if (pokemon.level <= pokemon.scene.getMaxExpLevel(true)) { - pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate); - pokemon.levelExp = 0; + playerPokemon.level += levelCount.value; + if (playerPokemon.level <= playerPokemon.scene.getMaxExpLevel(true)) { + playerPokemon.exp = getLevelTotalExp(playerPokemon.level, playerPokemon.species.growthRate); + playerPokemon.levelExp = 0; } - pokemon.addFriendship(5); + playerPokemon.addFriendship(5); - pokemon.scene.unshiftPhase(new LevelUpPhase(pokemon.scene, pokemon.scene.getParty().indexOf(pokemon), pokemon.level - levelCount.value, pokemon.level)); + playerPokemon.scene.unshiftPhase(new LevelUpPhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), playerPokemon.level - levelCount.value, playerPokemon.level)); return true; } } export class TmModifier extends ConsumablePokemonModifier { - constructor(type: ModifierTypes.TmModifierType, pokemonId: integer) { + public override type: TmModifierType; + + constructor(type: TmModifierType, pokemonId: number) { super(type, pokemonId); } - apply(args: any[]): boolean { - const pokemon = args[0] as PlayerPokemon; + /** + * Applies {@linkcode TmModifier} + * @param playerPokemon The {@linkcode PlayerPokemon} that should learn the TM + * @returns always `true` + */ + override apply(playerPokemon: PlayerPokemon): boolean { - pokemon.scene.unshiftPhase(new LearnMovePhase(pokemon.scene, pokemon.scene.getParty().indexOf(pokemon), (this.type as ModifierTypes.TmModifierType).moveId, true)); + playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), this.type.moveId, true)); return true; } } export class RememberMoveModifier extends ConsumablePokemonModifier { - public levelMoveIndex: integer; + public levelMoveIndex: number; - constructor(type: ModifierTypes.ModifierType, pokemonId: integer, levelMoveIndex: integer) { + constructor(type: ModifierType, pokemonId: number, levelMoveIndex: number) { super(type, pokemonId); this.levelMoveIndex = levelMoveIndex; } - apply(args: any[]): boolean { - const pokemon = args[0] as PlayerPokemon; - - pokemon.scene.unshiftPhase(new LearnMovePhase(pokemon.scene, pokemon.scene.getParty().indexOf(pokemon), pokemon.getLearnableLevelMoves()[this.levelMoveIndex])); + /** + * Applies {@linkcode RememberMoveModifier} + * @param playerPokemon The {@linkcode PlayerPokemon} that should remember the move + * @returns always `true` + */ + override apply(playerPokemon: PlayerPokemon): boolean { + playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex])); return true; } } export class EvolutionItemModifier extends ConsumablePokemonModifier { - constructor(type: ModifierTypes.EvolutionItemModifierType, pokemonId: integer) { + public override type: EvolutionItemModifierType; + + constructor(type: EvolutionItemModifierType, pokemonId: number) { super(type, pokemonId); } - apply(args: any[]): boolean { - const pokemon = args[0] as PlayerPokemon; - - let matchingEvolution = pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) - ? pokemonEvolutions[pokemon.species.speciesId].find(e => e.item === (this.type as ModifierTypes.EvolutionItemModifierType).evolutionItem - && (e.evoFormKey === null || (e.preFormKey || "") === pokemon.getFormKey()) - && (!e.condition || e.condition.predicate(pokemon))) + /** + * Applies {@linkcode EvolutionItemModifier} + * @param playerPokemon The {@linkcode PlayerPokemon} that should evolve via item + * @returns `true` if the evolution was successful + */ + override apply(playerPokemon: PlayerPokemon): boolean { + let matchingEvolution = pokemonEvolutions.hasOwnProperty(playerPokemon.species.speciesId) + ? pokemonEvolutions[playerPokemon.species.speciesId].find(e => e.item === this.type.evolutionItem + && (e.evoFormKey === null || (e.preFormKey || "") === playerPokemon.getFormKey()) + && (!e.condition || e.condition.predicate(playerPokemon))) : null; - if (!matchingEvolution && pokemon.isFusion()) { - matchingEvolution = pokemonEvolutions[pokemon.fusionSpecies!.speciesId].find(e => e.item === (this.type as ModifierTypes.EvolutionItemModifierType).evolutionItem // TODO: is the bang correct? - && (e.evoFormKey === null || (e.preFormKey || "") === pokemon.getFusionFormKey()) - && (!e.condition || e.condition.predicate(pokemon))); + if (!matchingEvolution && playerPokemon.isFusion()) { + matchingEvolution = pokemonEvolutions[playerPokemon.fusionSpecies!.speciesId].find(e => e.item === this.type.evolutionItem // TODO: is the bang correct? + && (e.evoFormKey === null || (e.preFormKey || "") === playerPokemon.getFusionFormKey()) + && (!e.condition || e.condition.predicate(playerPokemon))); if (matchingEvolution) { - matchingEvolution = new FusionSpeciesFormEvolution(pokemon.species.speciesId, matchingEvolution); + matchingEvolution = new FusionSpeciesFormEvolution(playerPokemon.species.speciesId, matchingEvolution); } } if (matchingEvolution) { - pokemon.scene.unshiftPhase(new EvolutionPhase(pokemon.scene, pokemon, matchingEvolution, pokemon.level - 1)); + playerPokemon.scene.unshiftPhase(new EvolutionPhase(playerPokemon.scene, playerPokemon, matchingEvolution, playerPokemon.level - 1)); return true; } @@ -2045,27 +2300,38 @@ export class EvolutionItemModifier extends ConsumablePokemonModifier { } export class FusePokemonModifier extends ConsumablePokemonModifier { - public fusePokemonId: integer; + public fusePokemonId: number; - constructor(type: ModifierType, pokemonId: integer, fusePokemonId: integer) { + constructor(type: ModifierType, pokemonId: number, fusePokemonId: number) { super(type, pokemonId); this.fusePokemonId = fusePokemonId; } - shouldApply(args: any[]): boolean { - return super.shouldApply(args) && args[1] instanceof PlayerPokemon && this.fusePokemonId === (args[1] as PlayerPokemon).id; + /** + * Checks if {@linkcode FusePokemonModifier} should be applied + * @param playerPokemon {@linkcode PlayerPokemon} that should be fused + * @param playerPokemon2 {@linkcode PlayerPokemon} that should be fused with {@linkcode playerPokemon} + * @returns `true` if {@linkcode FusePokemonModifier} should be applied + */ + override shouldApply(playerPokemon?: PlayerPokemon, playerPokemon2?: PlayerPokemon): boolean { + return super.shouldApply(playerPokemon, playerPokemon2) && !!playerPokemon2 && this.fusePokemonId === playerPokemon2.id; } - apply(args: any[]): Promise { - return new Promise(resolve => { - (args[0] as PlayerPokemon).fuse(args[1] as PlayerPokemon).then(() => resolve(true)); - }); + /** + * Applies {@linkcode FusePokemonModifier} + * @param playerPokemon {@linkcode PlayerPokemon} that should be fused + * @param playerPokemon2 {@linkcode PlayerPokemon} that should be fused with {@linkcode playerPokemon} + * @returns always Promise + */ + override async apply(playerPokemon: PlayerPokemon, playerPokemon2: PlayerPokemon): Promise { + await playerPokemon.fuse(playerPokemon2); + return true; } } export class MultipleParticipantExpBonusModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } @@ -2073,7 +2339,11 @@ export class MultipleParticipantExpBonusModifier extends PersistentModifier { return modifier instanceof MultipleParticipantExpBonusModifier; } - apply(_args: any[]): boolean { + /** + * Applies {@linkcode MultipleParticipantExpBonusModifier} + * @returns always `true` + */ + apply(): boolean { return true; } @@ -2081,7 +2351,7 @@ export class MultipleParticipantExpBonusModifier extends PersistentModifier { return new MultipleParticipantExpBonusModifier(this.type, this.stackCount); } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 5; } } @@ -2089,7 +2359,7 @@ export class MultipleParticipantExpBonusModifier extends PersistentModifier { export class HealingBoosterModifier extends PersistentModifier { private multiplier: number; - constructor(type: ModifierType, multiplier: number, stackCount?: integer) { + constructor(type: ModifierType, multiplier: number, stackCount?: number) { super(type, stackCount); this.multiplier = multiplier; @@ -2107,22 +2377,26 @@ export class HealingBoosterModifier extends PersistentModifier { return [ this.multiplier ]; } - apply(args: any[]): boolean { - const healingMultiplier = args[0] as Utils.IntegerHolder; + /** + * Applies {@linkcode HealingBoosterModifier} + * @param healingMultiplier the multiplier to apply to the healing + * @returns always `true` + */ + override apply(healingMultiplier: NumberHolder): boolean { healingMultiplier.value *= 1 + ((this.multiplier - 1) * this.getStackCount()); return true; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 5; } } export class ExpBoosterModifier extends PersistentModifier { - private boostMultiplier: integer; + private boostMultiplier: number; - constructor(type: ModifierType, boostPercent: number, stackCount?: integer) { + constructor(type: ModifierType, boostPercent: number, stackCount?: number) { super(type, stackCount); this.boostMultiplier = boostPercent * 0.01; @@ -2144,21 +2418,28 @@ export class ExpBoosterModifier extends PersistentModifier { return [ this.boostMultiplier * 100 ]; } - apply(args: any[]): boolean { - (args[0] as Utils.NumberHolder).value = Math.floor((args[0] as Utils.NumberHolder).value * (1 + (this.getStackCount() * this.boostMultiplier))); + /** + * Applies {@linkcode ExpBoosterModifier} + * @param boost {@linkcode NumberHolder} holding the boost value + * @returns always `true` + */ + override apply(boost: NumberHolder): boolean { + boost.value = Math.floor(boost.value * (1 + (this.getStackCount() * this.boostMultiplier))); return true; } - getMaxStackCount(scene: BattleScene, forThreshold?: boolean): integer { + getMaxStackCount(scene: BattleScene, forThreshold?: boolean): number { return this.boostMultiplier < 1 ? this.boostMultiplier < 0.6 ? 99 : 30 : 10; } } export class PokemonExpBoosterModifier extends PokemonHeldItemModifier { - private boostMultiplier: integer; + public override type: PokemonExpBoosterModifierType; - constructor(type: ModifierTypes.PokemonExpBoosterModifierType, pokemonId: integer, boostPercent: number, stackCount?: integer) { + private boostMultiplier: number; + + constructor(type: PokemonExpBoosterModifierType, pokemonId: number, boostPercent: number, stackCount?: number) { super(type, pokemonId, stackCount); this.boostMultiplier = boostPercent * 0.01; } @@ -2172,30 +2453,42 @@ export class PokemonExpBoosterModifier extends PokemonHeldItemModifier { } clone(): PersistentModifier { - return new PokemonExpBoosterModifier(this.type as ModifierTypes.PokemonExpBoosterModifierType, this.pokemonId, this.boostMultiplier * 100, this.stackCount); + return new PokemonExpBoosterModifier(this.type, this.pokemonId, this.boostMultiplier * 100, this.stackCount); } getArgs(): any[] { return super.getArgs().concat(this.boostMultiplier * 100); } - shouldApply(args: any[]): boolean { - return super.shouldApply(args) && args.length === 2 && args[1] instanceof Utils.NumberHolder; + /** + * Checks if {@linkcode PokemonExpBoosterModifier} should be applied + * @param pokemon The {@linkcode Pokemon} to apply the exp boost to + * @param boost {@linkcode NumberHolder} holding the exp boost value + * @returns `true` if {@linkcode PokemonExpBoosterModifier} should be applied + */ + override shouldApply(pokemon: Pokemon, boost: NumberHolder): boolean { + return super.shouldApply(pokemon, boost) && !!boost; } - apply(args: any[]): boolean { - (args[1] as Utils.NumberHolder).value = Math.floor((args[1] as Utils.NumberHolder).value * (1 + (this.getStackCount() * this.boostMultiplier))); + /** + * Applies {@linkcode PokemonExpBoosterModifier} + * @param _pokemon The {@linkcode Pokemon} to apply the exp boost to + * @param boost {@linkcode NumberHolder} holding the exp boost value + * @returns always `true` + */ + override apply(_pokemon: Pokemon, boost: NumberHolder): boolean { + boost.value = Math.floor(boost.value * (1 + (this.getStackCount() * this.boostMultiplier))); return true; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 99; } } export class ExpShareModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } @@ -2207,17 +2500,21 @@ export class ExpShareModifier extends PersistentModifier { return new ExpShareModifier(this.type, this.stackCount); } - apply(_args: any[]): boolean { + /** + * Applies {@linkcode ExpShareModifier} + * @returns always `true` + */ + override apply(): boolean { return true; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 5; } } export class ExpBalanceModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } @@ -2229,17 +2526,23 @@ export class ExpBalanceModifier extends PersistentModifier { return new ExpBalanceModifier(this.type, this.stackCount); } - apply(_args: any[]): boolean { + /** + * Applies {@linkcode ExpBalanceModifier} + * @returns always `true` + */ + override apply(): boolean { return true; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 4; } } export class PokemonFriendshipBoosterModifier extends PokemonHeldItemModifier { - constructor(type: ModifierTypes.PokemonFriendshipBoosterModifierType, pokemonId: integer, stackCount?: integer) { + public override type: PokemonFriendshipBoosterModifierType; + + constructor(type: PokemonFriendshipBoosterModifierType, pokemonId: number, stackCount?: number) { super(type, pokemonId, stackCount); } @@ -2248,23 +2551,28 @@ export class PokemonFriendshipBoosterModifier extends PokemonHeldItemModifier { } clone(): PersistentModifier { - return new PokemonFriendshipBoosterModifier(this.type as ModifierTypes.PokemonFriendshipBoosterModifierType, this.pokemonId, this.stackCount); + return new PokemonFriendshipBoosterModifier(this.type, this.pokemonId, this.stackCount); } - apply(args: any[]): boolean { - const friendship = args[1] as Utils.IntegerHolder; + /** + * Applies {@linkcode PokemonFriendshipBoosterModifier} + * @param _pokemon The {@linkcode Pokemon} to apply the friendship boost to + * @param friendship {@linkcode NumberHolder} holding the friendship boost value + * @returns always `true` + */ + override apply(_pokemon: Pokemon, friendship: NumberHolder): boolean { friendship.value = Math.floor(friendship.value * (1 + 0.5 * this.getStackCount())); return true; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 3; } } export class PokemonNatureWeightModifier extends PokemonHeldItemModifier { - constructor(type: ModifierTypes.ModifierType, pokemonId: integer, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, stackCount?: number) { super(type, pokemonId, stackCount); } @@ -2276,8 +2584,13 @@ export class PokemonNatureWeightModifier extends PokemonHeldItemModifier { return new PokemonNatureWeightModifier(this.type, this.pokemonId, this.stackCount); } - apply(args: any[]): boolean { - const multiplier = args[1] as Utils.IntegerHolder; + /** + * Applies {@linkcode PokemonNatureWeightModifier} + * @param _pokemon The {@linkcode Pokemon} to apply the nature weight to + * @param multiplier {@linkcode NumberHolder} holding the nature weight + * @returns `true` if multiplier was applied + */ + override apply(_pokemon: Pokemon, multiplier: NumberHolder): boolean { if (multiplier.value !== 1) { multiplier.value += 0.1 * this.getStackCount() * (multiplier.value > 1 ? 1 : -1); return true; @@ -2286,15 +2599,16 @@ export class PokemonNatureWeightModifier extends PokemonHeldItemModifier { return false; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 10; } } export class PokemonMoveAccuracyBoosterModifier extends PokemonHeldItemModifier { - private accuracyAmount: integer; + public override type: PokemonMoveAccuracyBoosterModifierType; + private accuracyAmount: number; - constructor(type: ModifierTypes.PokemonMoveAccuracyBoosterModifierType, pokemonId: integer, accuracy: integer, stackCount?: integer) { + constructor(type: PokemonMoveAccuracyBoosterModifierType, pokemonId: number, accuracy: number, stackCount?: number) { super(type, pokemonId, stackCount); this.accuracyAmount = accuracy; } @@ -2308,31 +2622,44 @@ export class PokemonMoveAccuracyBoosterModifier extends PokemonHeldItemModifier } clone(): PersistentModifier { - return new PokemonMoveAccuracyBoosterModifier(this.type as ModifierTypes.PokemonMoveAccuracyBoosterModifierType, this.pokemonId, this.accuracyAmount, this.stackCount); + return new PokemonMoveAccuracyBoosterModifier(this.type, this.pokemonId, this.accuracyAmount, this.stackCount); } getArgs(): any[] { return super.getArgs().concat(this.accuracyAmount); } - shouldApply(args: any[]): boolean { - return super.shouldApply(args) && args.length === 2 && args[1] instanceof Utils.NumberHolder; + /** + * Checks if {@linkcode PokemonMoveAccuracyBoosterModifier} should be applied + * @param pokemon The {@linkcode Pokemon} to apply the move accuracy boost to + * @param moveAccuracy {@linkcode NumberHolder} holding the move accuracy boost + * @returns `true` if {@linkcode PokemonMoveAccuracyBoosterModifier} should be applied + */ + override shouldApply(pokemon?: Pokemon, moveAccuracy?: NumberHolder): boolean { + return super.shouldApply(pokemon, moveAccuracy) && !!moveAccuracy; } - apply(args: any[]): boolean { - const moveAccuracy = (args[1] as Utils.IntegerHolder); + /** + * Applies {@linkcode PokemonMoveAccuracyBoosterModifier} + * @param _pokemon The {@linkcode Pokemon} to apply the move accuracy boost to + * @param moveAccuracy {@linkcode NumberHolder} holding the move accuracy boost + * @returns always `true` + */ + override apply(_pokemon: Pokemon, moveAccuracy: NumberHolder): boolean { moveAccuracy.value = Math.min(moveAccuracy.value + this.accuracyAmount * this.getStackCount(), 100); return true; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 3; } } export class PokemonMultiHitModifier extends PokemonHeldItemModifier { - constructor(type: ModifierTypes.PokemonMultiHitModifierType, pokemonId: integer, stackCount?: integer) { + public override type: PokemonMultiHitModifierType; + + constructor(type: PokemonMultiHitModifierType, pokemonId: number, stackCount?: number) { super(type, pokemonId, stackCount); } @@ -2341,13 +2668,19 @@ export class PokemonMultiHitModifier extends PokemonHeldItemModifier { } clone(): PersistentModifier { - return new PokemonMultiHitModifier(this.type as ModifierTypes.PokemonMultiHitModifierType, this.pokemonId, this.stackCount); + return new PokemonMultiHitModifier(this.type, this.pokemonId, this.stackCount); } - apply(args: any[]): boolean { - (args[1] as Utils.IntegerHolder).value *= (this.getStackCount() + 1); + /** + * Applies {@linkcode PokemonMultiHitModifier} + * @param _pokemon The {@linkcode Pokemon} using the move + * @param count {@linkcode NumberHolder} holding the number of items + * @param power {@linkcode NumberHolder} holding the power of the move + * @returns always `true` + */ + override apply(_pokemon: Pokemon, count: NumberHolder, power: NumberHolder): boolean { + count.value *= (this.getStackCount() + 1); - const power = args[2] as Utils.NumberHolder; switch (this.getStackCount()) { case 1: power.value *= 0.4; @@ -2363,17 +2696,18 @@ export class PokemonMultiHitModifier extends PokemonHeldItemModifier { return true; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 3; } } export class PokemonFormChangeItemModifier extends PokemonHeldItemModifier { + public override type: FormChangeItemModifierType; public formChangeItem: FormChangeItem; public active: boolean; public isTransferable: boolean = false; - constructor(type: ModifierTypes.FormChangeItemModifierType, pokemonId: integer, formChangeItem: FormChangeItem, active: boolean, stackCount?: integer) { + constructor(type: FormChangeItemModifierType, pokemonId: number, formChangeItem: FormChangeItem, active: boolean, stackCount?: number) { super(type, pokemonId, stackCount); this.formChangeItem = formChangeItem; this.active = active; @@ -2384,17 +2718,20 @@ export class PokemonFormChangeItemModifier extends PokemonHeldItemModifier { } clone(): PersistentModifier { - return new PokemonFormChangeItemModifier(this.type as ModifierTypes.FormChangeItemModifierType, this.pokemonId, this.formChangeItem, this.active, this.stackCount); + return new PokemonFormChangeItemModifier(this.type, this.pokemonId, this.formChangeItem, this.active, this.stackCount); } getArgs(): any[] { return super.getArgs().concat(this.formChangeItem, this.active); } - apply(args: any[]): boolean { - const pokemon = args[0] as Pokemon; - const active = args[1] as boolean; - + /** + * Applies {@linkcode PokemonFormChangeItemModifier} + * @param pokemon The {@linkcode Pokemon} to apply the form change item to + * @param active `true` if the form change item is active + * @returns `true` if the form change item was applied + */ + override apply(pokemon: Pokemon, active: boolean): boolean { const switchActive = this.active && !active; if (switchActive) { @@ -2410,7 +2747,7 @@ export class PokemonFormChangeItemModifier extends PokemonHeldItemModifier { return ret; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 1; } } @@ -2424,19 +2761,23 @@ export class MoneyRewardModifier extends ConsumableModifier { this.moneyMultiplier = moneyMultiplier; } - apply(args: any[]): boolean { - const scene = args[0] as BattleScene; - const moneyAmount = new Utils.IntegerHolder(scene.getWaveMoneyAmount(this.moneyMultiplier)); + /** + * Applies {@linkcode MoneyRewardModifier} + * @param battleScene The current {@linkcode BattleScene} + * @returns always `true` + */ + override apply(battleScene: BattleScene): boolean { + const moneyAmount = new NumberHolder(battleScene.getWaveMoneyAmount(this.moneyMultiplier)); - scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); + battleScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); - scene.addMoney(moneyAmount.value); + battleScene.addMoney(moneyAmount.value); - scene.getParty().map(p => { + battleScene.getParty().map(p => { if (p.species?.speciesId === Species.GIMMIGHOUL || p.fusionSpecies?.speciesId === Species.GIMMIGHOUL) { p.evoCounter ? p.evoCounter++ : p.evoCounter = 1; const modifier = getModifierType(modifierTypes.EVOLUTION_TRACKER_GIMMIGHOUL).newModifier(p) as EvoTrackerModifier; - scene.addModifier(modifier); + battleScene.addModifier(modifier); } }); @@ -2445,7 +2786,7 @@ export class MoneyRewardModifier extends ConsumableModifier { } export class MoneyMultiplierModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } @@ -2457,19 +2798,24 @@ export class MoneyMultiplierModifier extends PersistentModifier { return new MoneyMultiplierModifier(this.type, this.stackCount); } - apply(args: any[]): boolean { - (args[0] as Utils.IntegerHolder).value += Math.floor((args[0] as Utils.IntegerHolder).value * 0.2 * this.getStackCount()); + /** + * Applies {@linkcode MoneyMultiplierModifier} + * @param multiplier {@linkcode NumberHolder} holding the money multiplier value + * @returns always `true` + */ + override apply(multiplier: NumberHolder): boolean { + multiplier.value += Math.floor(multiplier.value * 0.2 * this.getStackCount()); return true; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 5; } } export class DamageMoneyRewardModifier extends PokemonHeldItemModifier { - constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, stackCount?: number) { super(type, pokemonId, stackCount); } @@ -2481,22 +2827,28 @@ export class DamageMoneyRewardModifier extends PokemonHeldItemModifier { return new DamageMoneyRewardModifier(this.type, this.pokemonId, this.stackCount); } - apply(args: any[]): boolean { - const scene = (args[0] as Pokemon).scene; - const moneyAmount = new Utils.IntegerHolder(Math.floor((args[1] as Utils.IntegerHolder).value * (0.5 * this.getStackCount()))); - scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); - scene.addMoney(moneyAmount.value); + /** + * Applies {@linkcode DamageMoneyRewardModifier} + * @param pokemon The {@linkcode Pokemon} attacking + * @param multiplier {@linkcode NumberHolder} holding the multiplier value + * @returns always `true` + */ + override apply(pokemon: Pokemon, multiplier: NumberHolder): boolean { + const battleScene = pokemon.scene; + const moneyAmount = new NumberHolder(Math.floor(multiplier.value * (0.5 * this.getStackCount()))); + battleScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); + battleScene.addMoney(moneyAmount.value); return true; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 5; } } export class MoneyInterestModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } @@ -2504,15 +2856,19 @@ export class MoneyInterestModifier extends PersistentModifier { return modifier instanceof MoneyInterestModifier; } - apply(args: any[]): boolean { - const scene = args[0] as BattleScene; - const interestAmount = Math.floor(scene.money * 0.1 * this.getStackCount()); - scene.addMoney(interestAmount); + /** + * Applies {@linkcode MoneyInterestModifier} + * @param battleScene The current {@linkcode BattleScene} + * @returns always `true` + */ + override apply(battleScene: BattleScene): boolean { + const interestAmount = Math.floor(battleScene.money * 0.1 * this.getStackCount()); + battleScene.addMoney(interestAmount); const userLocale = navigator.language || "en-US"; const formattedMoneyAmount = interestAmount.toLocaleString(userLocale); const message = i18next.t("modifier:moneyInterestApply", { moneyAmount: formattedMoneyAmount, typeName: this.type.name }); - scene.queueMessage(message, undefined, true); + battleScene.queueMessage(message, undefined, true); return true; } @@ -2521,13 +2877,13 @@ export class MoneyInterestModifier extends PersistentModifier { return new MoneyInterestModifier(this.type, this.stackCount); } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 5; } } export class HiddenAbilityRateBoosterModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } @@ -2539,19 +2895,24 @@ export class HiddenAbilityRateBoosterModifier extends PersistentModifier { return new HiddenAbilityRateBoosterModifier(this.type, this.stackCount); } - apply(args: any[]): boolean { - (args[0] as Utils.IntegerHolder).value *= Math.pow(2, -1 - this.getStackCount()); + /** + * Applies {@linkcode HiddenAbilityRateBoosterModifier} + * @param boost {@linkcode NumberHolder} holding the boost value + * @returns always `true` + */ + override apply(boost: NumberHolder): boolean { + boost.value *= Math.pow(2, -1 - this.getStackCount()); return true; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 4; } } export class ShinyRateBoosterModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } @@ -2563,19 +2924,24 @@ export class ShinyRateBoosterModifier extends PersistentModifier { return new ShinyRateBoosterModifier(this.type, this.stackCount); } - apply(args: any[]): boolean { - (args[0] as Utils.IntegerHolder).value *= Math.pow(2, 1 + this.getStackCount()); + /** + * Applies {@linkcode ShinyRateBoosterModifier} + * @param boost {@linkcode NumberHolder} holding the boost value + * @returns always `true` + */ + override apply(boost: NumberHolder): boolean { + boost.value *= Math.pow(2, 1 + this.getStackCount()); return true; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 4; } } export class LockModifierTiersModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } @@ -2583,7 +2949,11 @@ export class LockModifierTiersModifier extends PersistentModifier { return modifier instanceof LockModifierTiersModifier; } - apply(args: any[]): boolean { + /** + * Applies {@linkcode LockModifierTiersModifier} + * @returns always `true` + */ + override apply(): boolean { return true; } @@ -2591,7 +2961,7 @@ export class LockModifierTiersModifier extends PersistentModifier { return new LockModifierTiersModifier(this.type, this.stackCount); } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 1; } } @@ -2602,7 +2972,7 @@ export class LockModifierTiersModifier extends PersistentModifier { export class HealShopCostModifier extends PersistentModifier { public readonly shopMultiplier: number; - constructor(type: ModifierType, shopMultiplier: number, stackCount?: integer) { + constructor(type: ModifierType, shopMultiplier: number, stackCount?: number) { super(type, stackCount); this.shopMultiplier = shopMultiplier ?? 2.5; @@ -2616,8 +2986,12 @@ export class HealShopCostModifier extends PersistentModifier { return new HealShopCostModifier(this.type, this.shopMultiplier, this.stackCount); } - apply(args: any[]): boolean { - const moneyCost = args[0] as Utils.NumberHolder; + /** + * Applies {@linkcode HealShopCostModifier} + * @param cost {@linkcode NumberHolder} holding the heal shop cost + * @returns always `true` + */ + apply(moneyCost: NumberHolder): boolean { moneyCost.value = Math.floor(moneyCost.value * this.shopMultiplier); return true; @@ -2627,13 +3001,13 @@ export class HealShopCostModifier extends PersistentModifier { return super.getArgs().concat(this.shopMultiplier); } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 1; } } export class BoostBugSpawnModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } @@ -2645,17 +3019,21 @@ export class BoostBugSpawnModifier extends PersistentModifier { return new BoostBugSpawnModifier(this.type, this.stackCount); } - apply(args: any[]): boolean { + /** + * Applies {@linkcode BoostBugSpawnModifier} + * @returns always `true` + */ + override apply(): boolean { return true; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 1; } } export class SwitchEffectTransferModifier extends PokemonHeldItemModifier { - constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, stackCount?: number) { super(type, pokemonId, stackCount); } @@ -2667,11 +3045,15 @@ export class SwitchEffectTransferModifier extends PokemonHeldItemModifier { return new SwitchEffectTransferModifier(this.type, this.pokemonId, this.stackCount); } - apply(args: any[]): boolean { + /** + * Applies {@linkcode SwitchEffectTransferModifier} + * @returns always `true` + */ + override apply(): boolean { return true; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 1; } } @@ -2682,18 +3064,17 @@ export class SwitchEffectTransferModifier extends PokemonHeldItemModifier { * @see {@linkcode ContactHeldItemTransferChanceModifier} */ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { - constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, stackCount?: number) { super(type, pokemonId, stackCount); } /** * Determines the targets to transfer items from when this applies. - * @param args\[0\] the {@linkcode Pokemon} holding this item + * @param pokemon the {@linkcode Pokemon} holding this item + * @param _args N/A * @returns the opponents of the source {@linkcode Pokemon} */ - getTargets(args: any[]): Pokemon[] { - const pokemon = args[0]; - + getTargets(pokemon?: Pokemon, ..._args: unknown[]): Pokemon[] { return pokemon instanceof Pokemon ? pokemon.getOpponents() : []; @@ -2702,12 +3083,12 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { /** * Steals an item from a set of target Pokemon. * This prioritizes high-tier held items when selecting the item to steal. - * @param args \[0\] The {@linkcode Pokemon} holding this item - * @returns true if an item was stolen; false otherwise. + * @param pokemon The {@linkcode Pokemon} holding this item + * @param _args N/A + * @returns `true` if an item was stolen; false otherwise. */ - apply(args: any[]): boolean { - const pokemon = args[0] as Pokemon; - const opponents = this.getTargets(args); + override apply(pokemon: Pokemon, ..._args: unknown[]): boolean { + const opponents = this.getTargets(pokemon); if (!opponents.length) { return false; @@ -2720,9 +3101,9 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { return false; } - const poolType = pokemon.isPlayer() ? ModifierTypes.ModifierPoolType.PLAYER : pokemon.hasTrainer() ? ModifierTypes.ModifierPoolType.TRAINER : ModifierTypes.ModifierPoolType.WILD; + const poolType = pokemon.isPlayer() ? ModifierPoolType.PLAYER : pokemon.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD; - const transferredModifierTypes: ModifierTypes.ModifierType[] = []; + const transferredModifierTypes: ModifierType[] = []; const itemModifiers = pokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === targetPokemon.id && m.isTransferable, targetPokemon.isPlayer()) as PokemonHeldItemModifier[]; let highestItemTier = itemModifiers.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is this bang correct? @@ -2758,9 +3139,9 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { return !!transferredModifierTypes.length; } - abstract getTransferredItemCount(): integer; + abstract getTransferredItemCount(): number; - abstract getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierTypes.ModifierType): string; + abstract getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierType): string; } /** @@ -2770,7 +3151,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { */ export class TurnHeldItemTransferModifier extends HeldItemTransferModifier { isTransferable: boolean = true; - constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, stackCount?: number) { super(type, pokemonId, stackCount); } @@ -2782,15 +3163,15 @@ export class TurnHeldItemTransferModifier extends HeldItemTransferModifier { return new TurnHeldItemTransferModifier(this.type, this.pokemonId, this.stackCount); } - getTransferredItemCount(): integer { + getTransferredItemCount(): number { return this.getStackCount(); } - getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierTypes.ModifierType): string { + getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierType): string { return i18next.t("modifier:turnHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: pokemon.getNameToRender(), typeName: this.type.name }); } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 1; } @@ -2808,7 +3189,7 @@ export class TurnHeldItemTransferModifier extends HeldItemTransferModifier { export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModifier { private chance: number; - constructor(type: ModifierType, pokemonId: integer, chancePercent: number, stackCount?: integer) { + constructor(type: ModifierType, pokemonId: number, chancePercent: number, stackCount?: number) { super(type, pokemonId, stackCount); this.chance = chancePercent / 100; @@ -2816,16 +3197,12 @@ export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModif /** * Determines the target to steal items from when this applies. - * @param args\[0\] The {@linkcode Pokemon} holding this item - * @param args\[1\] The {@linkcode Pokemon} the holder is targeting with an attack - * @returns The target (args[1]) stored in array format for use in {@linkcode HeldItemTransferModifier.apply} + * @param _holderPokemon The {@linkcode Pokemon} holding this item + * @param targetPokemon The {@linkcode Pokemon} the holder is targeting with an attack + * @returns The target {@linkcode Pokemon} as array for further use in `apply` implementations */ - getTargets(args: any[]): Pokemon[] { - const target = args[1]; - - return target instanceof Pokemon - ? [ target ] - : []; + override getTargets(_holderPokemon: Pokemon, targetPokemon: Pokemon): Pokemon[] { + return !!targetPokemon ? [ targetPokemon ] : []; } matchType(modifier: Modifier): boolean { @@ -2840,21 +3217,21 @@ export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModif return super.getArgs().concat(this.chance * 100); } - getTransferredItemCount(): integer { + getTransferredItemCount(): number { return Phaser.Math.RND.realInRange(0, 1) < (this.chance * this.getStackCount()) ? 1 : 0; } - getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierTypes.ModifierType): string { + getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierType): string { return i18next.t("modifier:contactHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: getPokemonNameWithAffix(pokemon), typeName: this.type.name }); } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 5; } } export class IvScannerModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } @@ -2866,17 +3243,21 @@ export class IvScannerModifier extends PersistentModifier { return new IvScannerModifier(this.type, this.stackCount); } - apply(args: any[]): boolean { + /** + * Applies {@linkcode IvScannerModifier} + * @returns always `true` + */ + override apply(): boolean { return true; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 3; } } export class ExtraModifierModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } @@ -2888,23 +3269,28 @@ export class ExtraModifierModifier extends PersistentModifier { return new ExtraModifierModifier(this.type, this.stackCount); } - apply(args: any[]): boolean { - (args[0] as Utils.IntegerHolder).value += this.getStackCount(); + /** + * Applies {@linkcode ExtraModifierModifier} + * @param count {NumberHolder} holding the count value + * @returns always `true` + */ + override apply(count: NumberHolder): boolean { + count.value += this.getStackCount(); return true; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 3; } } export abstract class EnemyPersistentModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 5; } } @@ -2912,25 +3298,30 @@ export abstract class EnemyPersistentModifier extends PersistentModifier { abstract class EnemyDamageMultiplierModifier extends EnemyPersistentModifier { protected damageMultiplier: number; - constructor(type: ModifierType, damageMultiplier: number, stackCount?: integer) { + constructor(type: ModifierType, damageMultiplier: number, stackCount?: number) { super(type, stackCount); this.damageMultiplier = damageMultiplier; } - apply(args: any[]): boolean { - (args[0] as Utils.NumberHolder).value = Math.floor((args[0] as Utils.NumberHolder).value * Math.pow(this.damageMultiplier, this.getStackCount())); + /** + * Applies {@linkcode EnemyDamageMultiplierModifier} + * @param multiplier {NumberHolder} holding the multiplier value + * @returns always `true` + */ + override apply(multiplier: NumberHolder): boolean { + multiplier.value = Math.floor(multiplier.value * Math.pow(this.damageMultiplier, this.getStackCount())); return true; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 99; } } export class EnemyDamageBoosterModifier extends EnemyDamageMultiplierModifier { - constructor(type: ModifierType, boostPercent: number, stackCount?: integer) { + constructor(type: ModifierType, boostPercent: number, stackCount?: number) { //super(type, 1 + ((boostPercent || 10) * 0.01), stackCount); super(type, 1.05, stackCount); // Hardcode multiplier temporarily } @@ -2947,13 +3338,13 @@ export class EnemyDamageBoosterModifier extends EnemyDamageMultiplierModifier { return [ (this.damageMultiplier - 1) * 100 ]; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 999; } } export class EnemyDamageReducerModifier extends EnemyDamageMultiplierModifier { - constructor(type: ModifierType, reductionPercent: number, stackCount?: integer) { + constructor(type: ModifierType, reductionPercent: number, stackCount?: number) { //super(type, 1 - ((reductionPercent || 5) * 0.01), stackCount); super(type, 0.975, stackCount); // Hardcode multiplier temporarily } @@ -2970,7 +3361,7 @@ export class EnemyDamageReducerModifier extends EnemyDamageMultiplierModifier { return [ (1 - this.damageMultiplier) * 100 ]; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return scene.currentBattle.waveIndex < 2000 ? super.getMaxStackCount(scene) : 999; } } @@ -2978,7 +3369,7 @@ export class EnemyDamageReducerModifier extends EnemyDamageMultiplierModifier { export class EnemyTurnHealModifier extends EnemyPersistentModifier { public healPercent: number; - constructor(type: ModifierType, healPercent: number, stackCount?: integer) { + constructor(type: ModifierType, healPercent: number, stackCount?: number) { super(type, stackCount); // Hardcode temporarily @@ -2997,20 +3388,23 @@ export class EnemyTurnHealModifier extends EnemyPersistentModifier { return [ this.healPercent ]; } - apply(args: any[]): boolean { - const pokemon = args[0] as Pokemon; - - if (!pokemon.isFullHp()) { - const scene = pokemon.scene; - scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), - Math.max(Math.floor(pokemon.getMaxHp() / (100 / this.healPercent)) * this.stackCount, 1), i18next.t("modifier:enemyTurnHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), true, false, false, false, true)); + /** + * Applies {@linkcode EnemyTurnHealModifier} + * @param enemyPokemon The {@linkcode Pokemon} to heal + * @returns `true` if the {@linkcode Pokemon} was healed + */ + override apply(enemyPokemon: Pokemon): boolean { + if (!enemyPokemon.isFullHp()) { + const scene = enemyPokemon.scene; + scene.unshiftPhase(new PokemonHealPhase(scene, enemyPokemon.getBattlerIndex(), + Math.max(Math.floor(enemyPokemon.getMaxHp() / (100 / this.healPercent)) * this.stackCount, 1), i18next.t("modifier:enemyTurnHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(enemyPokemon) }), true, false, false, false, true)); return true; } return false; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 10; } } @@ -3019,7 +3413,7 @@ export class EnemyAttackStatusEffectChanceModifier extends EnemyPersistentModifi public effect: StatusEffect; public chance: number; - constructor(type: ModifierType, effect: StatusEffect, chancePercent: number, stackCount?: integer) { + constructor(type: ModifierType, effect: StatusEffect, chancePercent: number, stackCount?: number) { super(type, stackCount); this.effect = effect; @@ -3039,16 +3433,20 @@ export class EnemyAttackStatusEffectChanceModifier extends EnemyPersistentModifi return [ this.effect, this.chance * 100 ]; } - apply(args: any[]): boolean { - const target = (args[0] as Pokemon); + /** + * Applies {@linkcode EnemyAttackStatusEffectChanceModifier} + * @param enemyPokemon {@linkcode Pokemon} to apply the status effect to + * @returns `true` if the {@linkcode Pokemon} was affected + */ + override apply(enemyPokemon: Pokemon): boolean { if (Phaser.Math.RND.realInRange(0, 1) < (this.chance * this.getStackCount())) { - return target.trySetStatus(this.effect, true); + return enemyPokemon.trySetStatus(this.effect, true); } return false; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 10; } } @@ -3056,7 +3454,7 @@ export class EnemyAttackStatusEffectChanceModifier extends EnemyPersistentModifi export class EnemyStatusEffectHealChanceModifier extends EnemyPersistentModifier { public chance: number; - constructor(type: ModifierType, chancePercent: number, stackCount?: integer) { + constructor(type: ModifierType, chancePercent: number, stackCount?: number) { super(type, stackCount); //Hardcode temporarily @@ -3075,19 +3473,23 @@ export class EnemyStatusEffectHealChanceModifier extends EnemyPersistentModifier return [ this.chance * 100 ]; } - apply(args: any[]): boolean { - const target = (args[0] as Pokemon); - if (target.status && Phaser.Math.RND.realInRange(0, 1) < (this.chance * this.getStackCount())) { - target.scene.queueMessage(getStatusEffectHealText(target.status.effect, getPokemonNameWithAffix(target))); - target.resetStatus(); - target.updateInfo(); + /** + * Applies {@linkcode EnemyStatusEffectHealChanceModifier} + * @param enemyPokemon The {@linkcode Pokemon} to heal + * @returns `true` if the {@linkcode Pokemon} was healed + */ + override apply(enemyPokemon: Pokemon): boolean { + if (enemyPokemon.status && Phaser.Math.RND.realInRange(0, 1) < (this.chance * this.getStackCount())) { + enemyPokemon.scene.queueMessage(getStatusEffectHealText(enemyPokemon.status.effect, getPokemonNameWithAffix(enemyPokemon))); + enemyPokemon.resetStatus(); + enemyPokemon.updateInfo(); return true; } return false; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 10; } } @@ -3095,7 +3497,7 @@ export class EnemyStatusEffectHealChanceModifier extends EnemyPersistentModifier export class EnemyEndureChanceModifier extends EnemyPersistentModifier { public chance: number; - constructor(type: ModifierType, chancePercent?: number, stackCount?: integer) { + constructor(type: ModifierType, chancePercent?: number, stackCount?: number) { super(type, stackCount || 10); //Hardcode temporarily @@ -3114,9 +3516,12 @@ export class EnemyEndureChanceModifier extends EnemyPersistentModifier { return [ this.chance * 100 ]; } - apply(args: any[]): boolean { - const target = (args[0] as Pokemon); - + /** + * Applies {@linkcode EnemyEndureChanceModifier} + * @param target {@linkcode Pokemon} to apply the {@linkcode BattlerTagType.ENDURING} chance to + * @returns `true` if {@linkcode Pokemon} endured + */ + override apply(target: Pokemon): boolean { if (target.battleData.endured || Phaser.Math.RND.realInRange(0, 1) >= (this.chance * this.getStackCount())) { return false; } @@ -3128,7 +3533,7 @@ export class EnemyEndureChanceModifier extends EnemyPersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 10; } } @@ -3136,7 +3541,7 @@ export class EnemyEndureChanceModifier extends EnemyPersistentModifier { export class EnemyFusionChanceModifier extends EnemyPersistentModifier { private chance: number; - constructor(type: ModifierType, chancePercent: number, stackCount?: integer) { + constructor(type: ModifierType, chancePercent: number, stackCount?: number) { super(type, stackCount); this.chance = chancePercent / 100; @@ -3154,17 +3559,22 @@ export class EnemyFusionChanceModifier extends EnemyPersistentModifier { return [ this.chance * 100 ]; } - apply(args: any[]): boolean { + /** + * Applies {@linkcode EnemyFusionChanceModifier} + * @param isFusion {@linkcode BooleanHolder} that will be set to `true` if the {@linkcode EnemyPokemon} is a fusion + * @returns `true` if the {@linkcode EnemyPokemon} is a fusion + */ + override apply(isFusion: BooleanHolder): boolean { if (Phaser.Math.RND.realInRange(0, 1) >= (this.chance * this.getStackCount())) { return false; } - (args[0] as Utils.BooleanHolder).value = true; + isFusion.value = true; return true; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 10; } } @@ -3177,7 +3587,7 @@ export class EnemyFusionChanceModifier extends EnemyPersistentModifier { * @param isPlayer {@linkcode boolean} for whether the player (`true`) or enemy (`false`) is being overridden */ export function overrideModifiers(scene: BattleScene, isPlayer: boolean = true): void { - const modifiersOverride: ModifierTypes.ModifierOverride[] = isPlayer ? Overrides.STARTING_MODIFIER_OVERRIDE : Overrides.OPP_MODIFIER_OVERRIDE; + const modifiersOverride: ModifierOverride[] = isPlayer ? Overrides.STARTING_MODIFIER_OVERRIDE : Overrides.OPP_MODIFIER_OVERRIDE; if (!modifiersOverride || modifiersOverride.length === 0 || !scene) { return; } @@ -3191,7 +3601,7 @@ export function overrideModifiers(scene: BattleScene, isPlayer: boolean = true): const modifierFunc = modifierTypes[item.name]; let modifierType: ModifierType | null = modifierFunc(); - if (modifierType instanceof ModifierTypes.ModifierTypeGenerator) { + if (modifierType instanceof ModifierTypeGenerator) { const pregenArgs = ("type" in item) && (item.type !== null) ? [item.type] : undefined; modifierType = modifierType.generateType([], pregenArgs); } @@ -3218,7 +3628,7 @@ export function overrideModifiers(scene: BattleScene, isPlayer: boolean = true): * @param isPlayer {@linkcode boolean} for whether the {@linkcode pokemon} is the player's (`true`) or an enemy (`false`) */ export function overrideHeldItems(scene: BattleScene, pokemon: Pokemon, isPlayer: boolean = true): void { - const heldItemsOverride: ModifierTypes.ModifierOverride[] = isPlayer ? Overrides.STARTING_HELD_ITEMS_OVERRIDE : Overrides.OPP_HELD_ITEMS_OVERRIDE; + const heldItemsOverride: ModifierOverride[] = isPlayer ? Overrides.STARTING_HELD_ITEMS_OVERRIDE : Overrides.OPP_HELD_ITEMS_OVERRIDE; if (!heldItemsOverride || heldItemsOverride.length === 0 || !scene) { return; } @@ -3232,7 +3642,7 @@ export function overrideHeldItems(scene: BattleScene, pokemon: Pokemon, isPlayer let modifierType: ModifierType | null = modifierFunc(); const qty = item.count || 1; - if (modifierType instanceof ModifierTypes.ModifierTypeGenerator) { + if (modifierType instanceof ModifierTypeGenerator) { const pregenArgs = ("type" in item) && (item.type !== null) ? [item.type] : undefined; modifierType = modifierType.generateType([], pregenArgs); } diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index eaa458af904..bae61aa2288 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -57,7 +57,7 @@ export class BattleEndPhase extends BattlePhase { if (m instanceof LapsingPokemonHeldItemModifier) { args.push(this.scene.getPokemonById(m.pokemonId)); } - if (!m.lapse(args)) { + if (!m.lapse(...args)) { this.scene.removeModifier(m); } } diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index 66ecaa6c7f8..e419aa6692d 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -15,7 +15,7 @@ export class BerryPhase extends FieldPhase { this.executeForAll((pokemon) => { const hasUsableBerry = !!this.scene.findModifier((m) => { - return m instanceof BerryModifier && m.shouldApply([pokemon]); + return m instanceof BerryModifier && m.shouldApply(pokemon); }, pokemon.isPlayer()); if (hasUsableBerry) { @@ -29,7 +29,7 @@ export class BerryPhase extends FieldPhase { new CommonAnimPhase(this.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM) ); - for (const berryModifier of this.scene.applyModifiers(BerryModifier, pokemon.isPlayer(), pokemon) as BerryModifier[]) { + for (const berryModifier of this.scene.applyModifiers(BerryModifier, pokemon.isPlayer(), pokemon)) { if (berryModifier.consumed) { if (!--berryModifier.stackCount) { this.scene.removeModifier(berryModifier); From c58b5e943b986f084b3592889e8eac352ed9b06a Mon Sep 17 00:00:00 2001 From: schmidtc1 <62030095+schmidtc1@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:49:33 -0400 Subject: [PATCH 12/16] [P2] Fixes party status cure moves only curing the player's pokemon, even when used by enemy pokemon (#3369) * Fixes bug with Status Cure moves only curing player pokemon, refactors PartyStatusCureAttr, removes PartyStatusCurePhase * Adds check for user ID, since user always cures its own status regardless of ability * Adds unit tests for sparkly swirl * Merge and fix conflicts * Fix conflicts with SPLASH_ONLY * Fix failing sparkly swirl test due to splash_only * Adds unit tests for heal bell and aromatherapy * Update src/data/move.ts --------- Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> --- src/data/move.ts | 27 ++++++- src/phases/party-status-cure-phase.ts | 48 ------------ src/test/moves/aromatherapy.test.ts | 101 ++++++++++++++++++++++++++ src/test/moves/heal_bell.test.ts | 101 ++++++++++++++++++++++++++ src/test/moves/sparkly_swirl.test.ts | 86 ++++++++++++++++++++++ 5 files changed, 311 insertions(+), 52 deletions(-) delete mode 100644 src/phases/party-status-cure-phase.ts create mode 100644 src/test/moves/aromatherapy.test.ts create mode 100644 src/test/moves/heal_bell.test.ts create mode 100644 src/test/moves/sparkly_swirl.test.ts diff --git a/src/data/move.ts b/src/data/move.ts index 13ab84e8898..d547cd56524 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -25,7 +25,6 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { MoveUsedEvent } from "#app/events/battle-scene"; import { BATTLE_STATS, type BattleStat, EFFECTIVE_STATS, type EffectiveStat, getStatKey, Stat } from "#app/enums/stat"; -import { PartyStatusCurePhase } from "#app/phases/party-status-cure-phase"; import { BattleEndPhase } from "#app/phases/battle-end-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { MovePhase } from "#app/phases/move-phase"; @@ -34,6 +33,7 @@ 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 { GameMode } from "#app/game-mode"; import { applyChallenges, ChallengeType } from "./challenge"; @@ -1585,12 +1585,31 @@ export class PartyStatusCureAttr extends MoveEffectAttr { if (!this.canApply(user, target, move, args)) { return false; } - this.addPartyCurePhase(user); + const partyPokemon = user.isPlayer() ? user.scene.getParty() : user.scene.getEnemyParty(); + partyPokemon.forEach(p => this.cureStatus(p, user.id)); + + if (this.message) { + user.scene.queueMessage(this.message); + } + return true; } - addPartyCurePhase(user: Pokemon) { - user.scene.unshiftPhase(new PartyStatusCurePhase(user.scene, user, this.message, this.abilityCondition)); + /** + * Tries to cure the status of the given {@linkcode Pokemon} + * @param pokemon The {@linkcode Pokemon} to cure. + * @param userId The ID of the (move) {@linkcode Pokemon | user}. + */ + public cureStatus(pokemon: Pokemon, userId: number) { + if (!pokemon.isOnField() || pokemon.id === userId) { // user always cures its own status, regardless of ability + pokemon.resetStatus(false); + pokemon.updateInfo(); + } else if (!pokemon.hasAbility(this.abilityCondition)) { + pokemon.resetStatus(); + pokemon.updateInfo(); + } else { + pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.id, pokemon.getPassiveAbility()?.id === this.abilityCondition)); + } } } diff --git a/src/phases/party-status-cure-phase.ts b/src/phases/party-status-cure-phase.ts deleted file mode 100644 index e4903c7fc1f..00000000000 --- a/src/phases/party-status-cure-phase.ts +++ /dev/null @@ -1,48 +0,0 @@ -import BattleScene from "#app/battle-scene"; -import { Abilities } from "#app/enums/abilities"; -import Pokemon from "#app/field/pokemon"; -import { BattlePhase } from "./battle-phase"; -import { ShowAbilityPhase } from "./show-ability-phase"; - -/** - * Cures the party of all non-volatile status conditions, shows a message - * @param {BattleScene} scene The current scene - * @param {Pokemon} user The user of the move that cures the party - * @param {string} message The message that should be displayed - * @param {Abilities} abilityCondition Pokemon with this ability will not be affected ie. Soundproof - */ -export class PartyStatusCurePhase extends BattlePhase { - private user: Pokemon; - private message: string; - private abilityCondition: Abilities; - - constructor(scene: BattleScene, user: Pokemon, message: string, abilityCondition: Abilities) { - super(scene); - - this.user = user; - this.message = message; - this.abilityCondition = abilityCondition; - } - - start() { - super.start(); - for (const pokemon of this.scene.getParty()) { - if (!pokemon.isOnField() || pokemon === this.user) { - pokemon.resetStatus(false); - pokemon.updateInfo(true); - } else { - if (!pokemon.hasAbility(this.abilityCondition)) { - pokemon.resetStatus(); - pokemon.updateInfo(true); - } else { - // Manually show ability bar, since we're not hooked into the targeting system - pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.id, pokemon.getPassiveAbility()?.id === this.abilityCondition)); - } - } - } - if (this.message) { - this.scene.queueMessage(this.message); - } - this.end(); - } -} diff --git a/src/test/moves/aromatherapy.test.ts b/src/test/moves/aromatherapy.test.ts new file mode 100644 index 00000000000..acc2e9c5fae --- /dev/null +++ b/src/test/moves/aromatherapy.test.ts @@ -0,0 +1,101 @@ +import { StatusEffect } from "#app/enums/status-effect"; +import { CommandPhase } from "#app/phases/command-phase"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; + +describe("Moves - Aromatherapy", () => { + 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.AROMATHERAPY, Moves.SPLASH]) + .statusEffect(StatusEffect.BURN) + .battleType("double") + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should cure status effect of the user, its ally, and all party pokemon", async () => { + await game.classicMode.startBattle([Species.RATTATA, Species.RATTATA, Species.RATTATA]); + const [leftPlayer, rightPlayer, partyPokemon] = game.scene.getParty(); + + vi.spyOn(leftPlayer, "resetStatus"); + vi.spyOn(rightPlayer, "resetStatus"); + vi.spyOn(partyPokemon, "resetStatus"); + + game.move.select(Moves.AROMATHERAPY, 0); + await game.phaseInterceptor.to(CommandPhase); + game.move.select(Moves.SPLASH, 1); + await game.toNextTurn(); + + expect(leftPlayer.resetStatus).toHaveBeenCalledOnce(); + expect(rightPlayer.resetStatus).toHaveBeenCalledOnce(); + expect(partyPokemon.resetStatus).toHaveBeenCalledOnce(); + + expect(leftPlayer.status?.effect).toBeUndefined(); + expect(rightPlayer.status?.effect).toBeUndefined(); + expect(partyPokemon.status?.effect).toBeUndefined(); + }); + + it("should not cure status effect of the target/target's allies", async () => { + game.override.enemyStatusEffect(StatusEffect.BURN); + await game.classicMode.startBattle([Species.RATTATA, Species.RATTATA]); + const [leftOpp, rightOpp] = game.scene.getEnemyField(); + + vi.spyOn(leftOpp, "resetStatus"); + vi.spyOn(rightOpp, "resetStatus"); + + game.move.select(Moves.AROMATHERAPY, 0); + await game.phaseInterceptor.to(CommandPhase); + game.move.select(Moves.SPLASH, 1); + await game.toNextTurn(); + + expect(leftOpp.resetStatus).toHaveBeenCalledTimes(0); + expect(rightOpp.resetStatus).toHaveBeenCalledTimes(0); + + expect(leftOpp.status?.effect).toBeTruthy(); + expect(rightOpp.status?.effect).toBeTruthy(); + + expect(leftOpp.status?.effect).toBe(StatusEffect.BURN); + expect(rightOpp.status?.effect).toBe(StatusEffect.BURN); + }); + + it("should not cure status effect of allies ON FIELD with Sap Sipper, should still cure allies in party", async () => { + game.override.ability(Abilities.SAP_SIPPER); + await game.classicMode.startBattle([Species.RATTATA, Species.RATTATA, Species.RATTATA]); + const [leftPlayer, rightPlayer, partyPokemon] = game.scene.getParty(); + + vi.spyOn(leftPlayer, "resetStatus"); + vi.spyOn(rightPlayer, "resetStatus"); + vi.spyOn(partyPokemon, "resetStatus"); + + game.move.select(Moves.AROMATHERAPY, 0); + await game.phaseInterceptor.to(CommandPhase); + game.move.select(Moves.SPLASH, 1); + await game.toNextTurn(); + + expect(leftPlayer.resetStatus).toHaveBeenCalledOnce(); + expect(rightPlayer.resetStatus).toHaveBeenCalledTimes(0); + expect(partyPokemon.resetStatus).toHaveBeenCalledOnce(); + + expect(leftPlayer.status?.effect).toBeUndefined(); + expect(rightPlayer.status?.effect).toBe(StatusEffect.BURN); + expect(partyPokemon.status?.effect).toBeUndefined(); + }); +}); diff --git a/src/test/moves/heal_bell.test.ts b/src/test/moves/heal_bell.test.ts new file mode 100644 index 00000000000..1421809cf6c --- /dev/null +++ b/src/test/moves/heal_bell.test.ts @@ -0,0 +1,101 @@ +import { StatusEffect } from "#app/enums/status-effect"; +import { CommandPhase } from "#app/phases/command-phase"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; + +describe("Moves - Heal Bell", () => { + 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.HEAL_BELL, Moves.SPLASH]) + .statusEffect(StatusEffect.BURN) + .battleType("double") + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should cure status effect of the user, its ally, and all party pokemon", async () => { + await game.classicMode.startBattle([Species.RATTATA, Species.RATTATA, Species.RATTATA]); + const [leftPlayer, rightPlayer, partyPokemon] = game.scene.getParty(); + + vi.spyOn(leftPlayer, "resetStatus"); + vi.spyOn(rightPlayer, "resetStatus"); + vi.spyOn(partyPokemon, "resetStatus"); + + game.move.select(Moves.HEAL_BELL, 0); + await game.phaseInterceptor.to(CommandPhase); + game.move.select(Moves.SPLASH, 1); + await game.toNextTurn(); + + expect(leftPlayer.resetStatus).toHaveBeenCalledOnce(); + expect(rightPlayer.resetStatus).toHaveBeenCalledOnce(); + expect(partyPokemon.resetStatus).toHaveBeenCalledOnce(); + + expect(leftPlayer.status?.effect).toBeUndefined(); + expect(rightPlayer.status?.effect).toBeUndefined(); + expect(partyPokemon.status?.effect).toBeUndefined(); + }); + + it("should not cure status effect of the target/target's allies", async () => { + game.override.enemyStatusEffect(StatusEffect.BURN); + await game.classicMode.startBattle([Species.RATTATA, Species.RATTATA]); + const [leftOpp, rightOpp] = game.scene.getEnemyField(); + + vi.spyOn(leftOpp, "resetStatus"); + vi.spyOn(rightOpp, "resetStatus"); + + game.move.select(Moves.HEAL_BELL, 0); + await game.phaseInterceptor.to(CommandPhase); + game.move.select(Moves.SPLASH, 1); + await game.toNextTurn(); + + expect(leftOpp.resetStatus).toHaveBeenCalledTimes(0); + expect(rightOpp.resetStatus).toHaveBeenCalledTimes(0); + + expect(leftOpp.status?.effect).toBeTruthy(); + expect(rightOpp.status?.effect).toBeTruthy(); + + expect(leftOpp.status?.effect).toBe(StatusEffect.BURN); + expect(rightOpp.status?.effect).toBe(StatusEffect.BURN); + }); + + it("should not cure status effect of allies ON FIELD with Soundproof, should still cure allies in party", async () => { + game.override.ability(Abilities.SOUNDPROOF); + await game.classicMode.startBattle([Species.RATTATA, Species.RATTATA, Species.RATTATA]); + const [leftPlayer, rightPlayer, partyPokemon] = game.scene.getParty(); + + vi.spyOn(leftPlayer, "resetStatus"); + vi.spyOn(rightPlayer, "resetStatus"); + vi.spyOn(partyPokemon, "resetStatus"); + + game.move.select(Moves.HEAL_BELL, 0); + await game.phaseInterceptor.to(CommandPhase); + game.move.select(Moves.SPLASH, 1); + await game.toNextTurn(); + + expect(leftPlayer.resetStatus).toHaveBeenCalledOnce(); + expect(rightPlayer.resetStatus).toHaveBeenCalledTimes(0); + expect(partyPokemon.resetStatus).toHaveBeenCalledOnce(); + + expect(leftPlayer.status?.effect).toBeUndefined(); + expect(rightPlayer.status?.effect).toBe(StatusEffect.BURN); + expect(partyPokemon.status?.effect).toBeUndefined(); + }); +}); diff --git a/src/test/moves/sparkly_swirl.test.ts b/src/test/moves/sparkly_swirl.test.ts new file mode 100644 index 00000000000..83c154e57e7 --- /dev/null +++ b/src/test/moves/sparkly_swirl.test.ts @@ -0,0 +1,86 @@ +import { allMoves } from "#app/data/move"; +import { StatusEffect } from "#app/enums/status-effect"; +import { CommandPhase } from "#app/phases/command-phase"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Moves - Sparkly Swirl", () => { + 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 + .enemySpecies(Species.SHUCKLE) + .enemyLevel(100) + .enemyMoveset(Moves.SPLASH) + .enemyAbility(Abilities.BALL_FETCH) + .moveset([Moves.SPARKLY_SWIRL, Moves.SPLASH]) + .ability(Abilities.BALL_FETCH); + + vi.spyOn(allMoves[Moves.SPARKLY_SWIRL], "accuracy", "get").mockReturnValue(100); + }); + + it("should cure status effect of the user, its ally, and all party pokemon", async () => { + game.override + .battleType("double") + .statusEffect(StatusEffect.BURN); + await game.classicMode.startBattle([Species.RATTATA, Species.RATTATA, Species.RATTATA]); + const [leftPlayer, rightPlayer, partyPokemon] = game.scene.getParty(); + const leftOpp = game.scene.getEnemyPokemon()!; + + vi.spyOn(leftPlayer, "resetStatus"); + vi.spyOn(rightPlayer, "resetStatus"); + vi.spyOn(partyPokemon, "resetStatus"); + + game.move.select(Moves.SPARKLY_SWIRL, 0, leftOpp.getBattlerIndex()); + await game.phaseInterceptor.to(CommandPhase); + game.move.select(Moves.SPLASH, 1); + await game.toNextTurn(); + + expect(leftPlayer.resetStatus).toHaveBeenCalledOnce(); + expect(rightPlayer.resetStatus).toHaveBeenCalledOnce(); + expect(partyPokemon.resetStatus).toHaveBeenCalledOnce(); + + expect(leftPlayer.status?.effect).toBeUndefined(); + expect(rightPlayer.status?.effect).toBeUndefined(); + expect(partyPokemon.status?.effect).toBeUndefined(); + }); + + it("should not cure status effect of the target/target's allies", async () => { + game.override + .battleType("double") + .enemyStatusEffect(StatusEffect.BURN); + await game.classicMode.startBattle([Species.RATTATA, Species.RATTATA]); + const [leftOpp, rightOpp] = game.scene.getEnemyField(); + + vi.spyOn(leftOpp, "resetStatus"); + vi.spyOn(rightOpp, "resetStatus"); + + game.move.select(Moves.SPARKLY_SWIRL, 0, leftOpp.getBattlerIndex()); + await game.phaseInterceptor.to(CommandPhase); + game.move.select(Moves.SPLASH, 1); + await game.toNextTurn(); + + expect(leftOpp.resetStatus).toHaveBeenCalledTimes(0); + expect(rightOpp.resetStatus).toHaveBeenCalledTimes(0); + + expect(leftOpp.status?.effect).toBeTruthy(); + expect(rightOpp.status?.effect).toBeTruthy(); + + expect(leftOpp.status?.effect).toBe(StatusEffect.BURN); + expect(rightOpp.status?.effect).toBe(StatusEffect.BURN); + }); +}); From 76e25a6d6f316dd78e59a3792f39d3b8f999478c Mon Sep 17 00:00:00 2001 From: "Adrian T." <68144167+torranx@users.noreply.github.com> Date: Fri, 4 Oct 2024 00:58:21 +0800 Subject: [PATCH 13/16] [Move] Update Tera Starstorm (still Partial), Readd Partial tag to Tera Blast (#4549) * fully implement tera starstorm * add docs * add tests * add override keyword * account for fusion * swap party positions * add partial tag to tera blast * address comments --- src/data/move.ts | 46 +++++++++++-- src/field/pokemon.ts | 9 +++ src/test/moves/tera_starstorm.test.ts | 98 +++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 src/test/moves/tera_starstorm.test.ts diff --git a/src/data/move.ts b/src/data/move.ts index d547cd56524..8c9e8b0fb99 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -3942,7 +3942,14 @@ export class PhotonGeyserCategoryAttr extends VariableMoveCategoryAttr { } } -export class TeraBlastCategoryAttr extends VariableMoveCategoryAttr { +/** + * Attribute used for tera moves that change category based on the user's Atk and SpAtk stats + * Note: Currently, `getEffectiveStat` does not ignore all abilities that affect stats except those + * with the attribute of `StatMultiplierAbAttr` + * TODO: Remove the `.partial()` tag from Tera Blast and Tera Starstorm when the above issue is resolved + * @extends VariableMoveCategoryAttr + */ +export class TeraMoveCategoryAttr extends VariableMoveCategoryAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const category = (args[0] as Utils.NumberHolder); @@ -4031,6 +4038,30 @@ export class VariableMoveTypeAttr extends MoveAttr { } } +/** + * Attribute used for Tera Starstorm that changes the move type to Stellar + * @extends VariableMoveTypeAttr + */ +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 + * @returns `true` if the move type is changed to {@linkcode Type.STELLAR}, `false` otherwise + */ + override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + if (user.isTerastallized() && (user.hasFusionSpecies(Species.TERAPAGOS) || user.species.speciesId === Species.TERAPAGOS)) { + const moveType = args[0] as Utils.NumberHolder; + + moveType.value = Type.STELLAR; + return true; + } + return false; + } +} + export class FormChangeItemTypeAttr extends VariableMoveTypeAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const moveType = args[0]; @@ -9190,7 +9221,7 @@ export function initMoves() { .attr(HalfSacrificialAttr), new AttackMove(Moves.EXPANDING_FORCE, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 8) .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.PSYCHIC && user.isGrounded() ? 1.5 : 1) - .attr(VariableTargetAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.PSYCHIC && user.isGrounded() ? 6 : 3), + .attr(VariableTargetAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.PSYCHIC && user.isGrounded() ? MoveTarget.ALL_NEAR_ENEMIES : MoveTarget.NEAR_OTHER), new AttackMove(Moves.STEEL_ROLLER, Type.STEEL, MoveCategory.PHYSICAL, 130, 100, 5, -1, 0, 8) .attr(ClearTerrainAttr) .condition((user, target, move) => !!user.scene.arena.terrain), @@ -9464,10 +9495,11 @@ export function initMoves() { .unimplemented(), End Unused */ new AttackMove(Moves.TERA_BLAST, Type.NORMAL, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 9) - .attr(TeraBlastCategoryAttr) + .attr(TeraMoveCategoryAttr) .attr(TeraBlastTypeAttr) .attr(TeraBlastPowerAttr) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, (user, target, move) => user.isTerastallized() && user.isOfType(Type.STELLAR)), + .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, (user, target, move) => user.isTerastallized() && user.isOfType(Type.STELLAR)) + .partial(), /** Does not ignore abilities that affect stats, relevant in determining the move's category {@see TeraMoveCategoryAttr} */ new SelfStatusMove(Moves.SILK_TRAP, Type.BUG, -1, 10, -1, 4, 9) .attr(ProtectAttr, BattlerTagType.SILK_TRAP) .condition(failIfLastCondition), @@ -9657,8 +9689,10 @@ export function initMoves() { .attr(ElectroShotChargeAttr) .ignoresVirtual(), new AttackMove(Moves.TERA_STARSTORM, Type.NORMAL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9) - .attr(TeraBlastCategoryAttr) - .partial(), + .attr(TeraMoveCategoryAttr) + .attr(TeraStarstormTypeAttr) + .attr(VariableTargetAttr, (user, target, move) => (user.hasFusionSpecies(Species.TERAPAGOS) || user.species.speciesId === Species.TERAPAGOS) && user.isTerastallized() ? MoveTarget.ALL_NEAR_ENEMIES : MoveTarget.NEAR_OTHER) + .partial(), /** Does not ignore abilities that affect stats, relevant in determining the move's category {@see TeraMoveCategoryAttr} */ new AttackMove(Moves.FICKLE_BEAM, Type.DRAGON, MoveCategory.SPECIAL, 80, 100, 5, 30, 0, 9) .attr(PreMoveMessageAttr, doublePowerChanceMessageFunc) .attr(DoublePowerChanceAttr), diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index d5411496223..de18dfb74eb 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1087,6 +1087,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return !!this.fusionSpecies; } + /** + * Checks if the {@linkcode Pokemon} has a fusion with the specified {@linkcode Species}. + * @param species the pokemon {@linkcode Species} to check + * @returns `true` if the {@linkcode Pokemon} has a fusion with the specified {@linkcode Species}, `false` otherwise + */ + hasFusionSpecies(species: Species): boolean { + return this.fusionSpecies?.speciesId === species; + } + abstract isBoss(): boolean; getMoveset(ignoreOverride?: boolean): (PokemonMove | null)[] { diff --git a/src/test/moves/tera_starstorm.test.ts b/src/test/moves/tera_starstorm.test.ts new file mode 100644 index 00000000000..20dbc0b77d6 --- /dev/null +++ b/src/test/moves/tera_starstorm.test.ts @@ -0,0 +1,98 @@ +import { BattlerIndex } from "#app/battle"; +import { Type } from "#app/data/type"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; + +describe("Moves - Tera Starstorm", () => { + 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.TERA_STARSTORM, Moves.SPLASH]) + .battleType("double") + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH) + .enemyLevel(30) + .enemySpecies(Species.MAGIKARP) + .startingHeldItems([{ name: "TERA_SHARD", type: Type.FIRE }]); + }); + + it("changes type to Stellar when used by Terapagos in its Stellar Form", async () => { + game.override.battleType("single"); + await game.classicMode.startBattle([Species.TERAPAGOS]); + + const terapagos = game.scene.getPlayerPokemon()!; + + vi.spyOn(terapagos, "getMoveType"); + + game.move.select(Moves.TERA_STARSTORM); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(terapagos.isTerastallized()).toBe(true); + expect(terapagos.getMoveType).toHaveReturnedWith(Type.STELLAR); + }); + + it("targets both opponents in a double battle when used by Terapagos in its Stellar Form", async () => { + await game.classicMode.startBattle([Species.MAGIKARP, Species.TERAPAGOS]); + + game.move.select(Moves.TERA_STARSTORM, 0, BattlerIndex.ENEMY); + game.move.select(Moves.TERA_STARSTORM, 1); + + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); + + const enemyField = game.scene.getEnemyField(); + + // Pokemon other than Terapagos should not be affected - only hits one target + await game.phaseInterceptor.to("MoveEndPhase"); + expect(enemyField.some(pokemon => pokemon.isFullHp())).toBe(true); + + // Terapagos in Stellar Form should hit both targets + await game.phaseInterceptor.to("MoveEndPhase"); + expect(enemyField.every(pokemon => pokemon.isFullHp())).toBe(false); + }); + + it("applies the effects when Terapagos in Stellar Form is fused with another Pokemon", async () => { + await game.classicMode.startBattle([Species.TERAPAGOS, Species.CHARMANDER, Species.MAGIKARP]); + + const fusionedMon = game.scene.getParty()[0]; + const magikarp = game.scene.getParty()[2]; + + // Fuse party members (taken from PlayerPokemon.fuse(...) function) + fusionedMon.fusionSpecies = magikarp.species; + fusionedMon.fusionFormIndex = magikarp.formIndex; + fusionedMon.fusionAbilityIndex = magikarp.abilityIndex; + fusionedMon.fusionShiny = magikarp.shiny; + fusionedMon.fusionVariant = magikarp.variant; + fusionedMon.fusionGender = magikarp.gender; + fusionedMon.fusionLuck = magikarp.luck; + + vi.spyOn(fusionedMon, "getMoveType"); + + game.move.select(Moves.TERA_STARSTORM, 0); + game.move.select(Moves.SPLASH, 1); + await game.phaseInterceptor.to("TurnEndPhase"); + + // Fusion and terastallized + expect(fusionedMon.isFusion()).toBe(true); + expect(fusionedMon.isTerastallized()).toBe(true); + // Move effects should be applied + expect(fusionedMon.getMoveType).toHaveReturnedWith(Type.STELLAR); + expect(game.scene.getEnemyField().every(pokemon => pokemon.isFullHp())).toBe(false); + }); +}); From 46c84155b3a881e0bb6f5b97a340b7b2d82c03cd Mon Sep 17 00:00:00 2001 From: flx-sta <50131232+flx-sta@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:53:35 -0700 Subject: [PATCH 14/16] [Beta P1] Fix rare candy crashing (#4561) --- 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 4f1f9833602..b8905bc9bf1 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -2204,7 +2204,7 @@ export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier { * @param levelCount The amount of levels to increment * @returns always `true` */ - override apply(playerPokemon: PlayerPokemon, levelCount: NumberHolder): boolean { + override apply(playerPokemon: PlayerPokemon, levelCount: NumberHolder = new NumberHolder(1)): boolean { playerPokemon.scene.applyModifiers(LevelIncrementBoosterModifier, true, levelCount); playerPokemon.level += levelCount.value; From af51c1f2f0b6eb1ccebe6183833c2693ec2b8172 Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:56:35 -0700 Subject: [PATCH 15/16] [Move] Unique message for heal block, taunt, torment, and imprison (#4530) Co-authored-by: frutescens --- src/data/battler-tags.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index d8094f96368..42775775ac4 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2267,7 +2267,7 @@ export class HealBlockTag extends MoveRestrictionBattlerTag { * Uses DisabledTag's selectionDeniedText() message */ override selectionDeniedText(pokemon: Pokemon, move: Moves): string { - return i18next.t("battle:moveDisabled", { moveName: allMoves[move].name }); + return i18next.t("battle:moveDisabledHealBlock", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name, healBlockName: allMoves[Moves.HEAL_BLOCK].name }); } /** @@ -2277,7 +2277,7 @@ export class HealBlockTag extends MoveRestrictionBattlerTag { * @returns {string} text to display when the move is interrupted */ override interruptedText(pokemon: Pokemon, move: Moves): string { - return i18next.t("battle:disableInterruptedMove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name }); + return i18next.t("battle:moveDisabledHealBlock", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name, healBlockName: allMoves[Moves.HEAL_BLOCK].name }); } override onRemove(pokemon: Pokemon): void { @@ -2530,8 +2530,8 @@ export class TormentTag extends MoveRestrictionBattlerTag { return false; } - override selectionDeniedText(_pokemon: Pokemon, move: Moves): string { - return i18next.t("battle:moveCannotBeSelected", { moveName: allMoves[move].name }); + override selectionDeniedText(pokemon: Pokemon, _move: Moves): string { + return i18next.t("battle:moveDisabledTorment", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); } } @@ -2559,12 +2559,12 @@ export class TauntTag extends MoveRestrictionBattlerTag { return allMoves[move].category === MoveCategory.STATUS; } - override selectionDeniedText(_pokemon: Pokemon, move: Moves): string { - return i18next.t("battle:moveCannotBeSelected", { moveName: allMoves[move].name }); + override selectionDeniedText(pokemon: Pokemon, move: Moves): string { + return i18next.t("battle:moveDisabledTaunt", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name }); } override interruptedText(pokemon: Pokemon, move: Moves): string { - return i18next.t("battle:disableInterruptedMove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name }); + return i18next.t("battle:moveDisabledTaunt", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name }); } } @@ -2609,12 +2609,12 @@ export class ImprisonTag extends MoveRestrictionBattlerTag { return false; } - override selectionDeniedText(_pokemon: Pokemon, move: Moves): string { - return i18next.t("battle:moveCannotBeSelected", { moveName: allMoves[move].name }); + override selectionDeniedText(pokemon: Pokemon, move: Moves): string { + return i18next.t("battle:moveDisabledImprison", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name }); } override interruptedText(pokemon: Pokemon, move: Moves): string { - return i18next.t("battle:disableInterruptedMove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name }); + return i18next.t("battle:moveDisabledImprison", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name }); } } From 9c56c15a6c2f6a3096601213e37bdcaed583cdd3 Mon Sep 17 00:00:00 2001 From: Acelynn Zhang <102631387+acelynnzhang@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:23:04 -0500 Subject: [PATCH 16/16] [P3] Fix persisting sleep animation when sprite is already loaded (#4562) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ensure that a Pokémon's animation speed is reset properly after saving and quitting. Previously, if a Pokémon was put to sleep, which slows its framerate, saving and quitting would result in the slower framerate persisting even though the Pokémon was no longer asleep. This fix adds an else condition to reset the frameRate to 12 if the sprite is already loaded upon resuming the game. Fixes #4465 --- src/data/pokemon-species.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index b710c40e1d5..469e400a551 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -484,6 +484,8 @@ export abstract class PokemonSpeciesForm { frameRate: 12, repeat: -1 }); + } else { + scene.anims.get(spriteKey).frameRate = 12; } let spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant).replace("variant/", "").replace(/_[1-3]$/, ""); const useExpSprite = scene.experimentalSprites && scene.hasExpSprite(spriteKey);