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/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); } diff --git a/public/images/ui/friendship.png b/public/images/ui/friendship.png new file mode 100644 index 00000000000..073adcadc76 Binary files /dev/null and b/public/images/ui/friendship.png differ diff --git a/public/images/ui/friendship_overlay.png b/public/images/ui/friendship_overlay.png new file mode 100644 index 00000000000..4a4724fbdc9 Binary files /dev/null and b/public/images/ui/friendship_overlay.png differ diff --git a/public/images/ui/legacy/friendship.png b/public/images/ui/legacy/friendship.png new file mode 100644 index 00000000000..073adcadc76 Binary files /dev/null and b/public/images/ui/legacy/friendship.png differ diff --git a/public/images/ui/legacy/friendship_overlay.png b/public/images/ui/legacy/friendship_overlay.png new file mode 100644 index 00000000000..4a4724fbdc9 Binary files /dev/null and b/public/images/ui/legacy/friendship_overlay.png differ 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 diff --git a/src/battle-scene.ts b/src/battle-scene.ts index c5283b091d4..1bb0d6bfc4b 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"; @@ -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/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/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 aac48b57e90..7d0d86fadbf 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/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 }); } } 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/move.ts b/src/data/move.ts index 748f81cdd8b..8c9e8b0fb99 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)); + } } } @@ -2668,20 +2687,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; @@ -3901,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); @@ -3990,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]; @@ -9149,16 +9221,15 @@ 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), 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) @@ -9424,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), @@ -9617,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/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 6c475070dc6..ceb33ee2b8f 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"; @@ -72,7 +73,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 19b58a7f76e..af34ea368fc 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 4eb15d31df8..f6f577b8d4e 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..8fde06f3b14 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 @@ -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); } diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 6f7e6c259f3..23713660daa 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 f6d4b8c38ae..05725f67184 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"; @@ -208,8 +209,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..469e400a551 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; @@ -483,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); @@ -929,6 +932,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 +2751,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 764cac5e901..462d06b2a28 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 93a971cc3ef..a3034f64f8c 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 { CustomPokemonData } from "#app/data/mystery-encounters/custom-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, @@ -1086,6 +1083,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)[] { @@ -1534,6 +1540,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; } @@ -1922,9 +1932,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 @@ -4049,7 +4059,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; } @@ -5014,6 +5024,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/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..3d4f4d165e6 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"; @@ -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/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index cbc842f2d9d..bfd8ad41c7c 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1,38 +1,35 @@ -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 BattleScene from "#app/battle-scene"; +import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; +import { tmPoolTiers, tmSpecies } from "#app/data/balance/tms"; +import { getBerryEffectDescription, getBerryName } from "#app/data/berry"; +import { allMoves, 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 "#app/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,40 +1560,40 @@ 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"), - SILVER_POKEBALL: () => new ModifierType("modifierType:ModifierType.SILVER_POKEBALL", "pb_silver", (type, _args) => new Modifiers.TempExtraModifierModifier(type, 100), 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"), + SILVER_POKEBALL: () => new ModifierType("modifierType:ModifierType.SILVER_POKEBALL", "pb_silver", (type, _args) => new TempExtraModifierModifier(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 { @@ -1645,8 +1642,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); @@ -1673,8 +1670,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); @@ -1724,7 +1721,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; }), @@ -1732,7 +1729,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), @@ -1740,7 +1737,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), @@ -1748,13 +1745,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); @@ -2247,7 +2244,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: @@ -2264,30 +2261,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) { @@ -2302,7 +2299,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); } } @@ -2341,7 +2338,7 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, break; } if (tier === undefined) { - const tierValue = Utils.randSeedInt(1024); + const tierValue = randSeedInt(1024); if (!upgradeCount) { upgradeCount = 0; } @@ -2350,7 +2347,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++; } @@ -2382,7 +2379,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; @@ -2397,7 +2394,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); @@ -2457,9 +2454,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 fcd9cafba85..af2f0f4ac7f 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1,34 +1,35 @@ -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 { 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 { 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 i18next from "i18next"; - +import type BattleScene from "#app/battle-scene"; +import { FusionSpeciesFormEvolution, pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; +import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry"; +import { getLevelTotalExp } from "#app/data/exp"; import { allMoves } from "#app/data/move"; -import { Abilities } from "#app/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; @@ -85,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); @@ -121,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; @@ -142,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; @@ -180,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; @@ -193,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; @@ -248,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; @@ -275,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; @@ -309,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; @@ -323,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) { @@ -343,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; } @@ -355,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); @@ -393,7 +417,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 { @@ -409,7 +433,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); } @@ -418,17 +444,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; @@ -477,21 +502,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; } } @@ -517,26 +542,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); } @@ -544,17 +569,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); } @@ -562,17 +587,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); } @@ -580,17 +605,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); } @@ -598,20 +628,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; @@ -627,8 +662,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 { @@ -677,7 +725,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; @@ -692,41 +740,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); } @@ -734,7 +793,7 @@ export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModi return container; } - getBattlesLeft(): integer { + getBattlesLeft(): number { return this.battlesLeft; } @@ -744,10 +803,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; @@ -761,15 +821,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); @@ -781,10 +845,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(); } @@ -795,7 +863,7 @@ export class TerastallizeModifier extends LapsingPokemonHeldItemModifier { return 1.25; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 1; } } @@ -810,7 +878,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; } @@ -830,12 +898,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; } @@ -844,17 +923,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; @@ -872,7 +951,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; } @@ -898,7 +981,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 || m instanceof TempExtraModifierModifier).length; return 999; @@ -909,10 +992,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; } @@ -922,23 +1007,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; @@ -948,7 +1045,7 @@ export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier { return 1.2; } - override getMaxHeldItemCount(pokemon: Pokemon): integer { + override getMaxHeldItemCount(pokemon: Pokemon): number { return 2; } } @@ -980,16 +1077,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); } }); @@ -1000,7 +1109,7 @@ export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier { return 1.1; } - override getMaxHeldItemCount(pokemon: Pokemon): integer { + override getMaxHeldItemCount(pokemon: Pokemon): number { return 1; } } @@ -1011,7 +1120,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); } @@ -1027,15 +1136,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; @@ -1056,13 +1178,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} @@ -1073,7 +1195,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; @@ -1101,27 +1223,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; } @@ -1149,39 +1269,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; @@ -1189,7 +1307,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} @@ -1198,7 +1316,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; @@ -1226,21 +1344,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); @@ -1256,7 +1373,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; @@ -1280,13 +1397,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; } @@ -1306,7 +1421,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; @@ -1327,14 +1442,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))); } } @@ -1345,7 +1458,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; @@ -1369,20 +1482,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; } @@ -1393,13 +1513,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); } @@ -1411,14 +1531,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; @@ -1429,13 +1558,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); } @@ -1447,18 +1576,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") })); @@ -1469,13 +1607,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); } @@ -1487,14 +1625,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; @@ -1503,13 +1650,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); } @@ -1521,20 +1668,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; } } @@ -1549,7 +1699,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) { @@ -1569,7 +1719,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 { @@ -1582,15 +1732,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; } @@ -1600,7 +1749,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); } @@ -1612,25 +1761,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); } @@ -1642,12 +1794,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; } @@ -1661,7 +1823,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; @@ -1680,14 +1842,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); @@ -1698,7 +1868,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; } @@ -1707,7 +1877,7 @@ export class BerryModifier extends PokemonHeldItemModifier { } export class PreserveBerryModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } @@ -1719,25 +1889,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); } @@ -1749,17 +1931,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; } } @@ -1771,7 +1956,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); } @@ -1786,11 +1971,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) { @@ -1806,36 +1990,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; @@ -1844,16 +2041,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); @@ -1866,21 +2074,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; @@ -1888,36 +2100,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; @@ -1925,18 +2150,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; } @@ -1945,21 +2177,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.customPokemonData.nature = 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.customPokemonData.nature = 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; @@ -1967,86 +2203,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 = new NumberHolder(1)): 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; } @@ -2055,27 +2309,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); } @@ -2083,7 +2348,11 @@ export class MultipleParticipantExpBonusModifier extends PersistentModifier { return modifier instanceof MultipleParticipantExpBonusModifier; } - apply(_args: any[]): boolean { + /** + * Applies {@linkcode MultipleParticipantExpBonusModifier} + * @returns always `true` + */ + apply(): boolean { return true; } @@ -2091,7 +2360,7 @@ export class MultipleParticipantExpBonusModifier extends PersistentModifier { return new MultipleParticipantExpBonusModifier(this.type, this.stackCount); } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 5; } } @@ -2099,7 +2368,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; @@ -2117,22 +2386,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; @@ -2154,21 +2427,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; } @@ -2182,30 +2462,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); } @@ -2217,17 +2509,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); } @@ -2239,17 +2535,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); } @@ -2258,23 +2560,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); } @@ -2286,8 +2593,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; @@ -2296,15 +2608,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; } @@ -2318,31 +2631,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); } @@ -2351,13 +2677,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; @@ -2373,17 +2705,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; @@ -2394,17 +2727,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) { @@ -2420,7 +2756,7 @@ export class PokemonFormChangeItemModifier extends PokemonHeldItemModifier { return ret; } - getMaxHeldItemCount(pokemon: Pokemon): integer { + getMaxHeldItemCount(pokemon: Pokemon): number { return 1; } } @@ -2434,19 +2770,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); } }); @@ -2455,7 +2795,7 @@ export class MoneyRewardModifier extends ConsumableModifier { } export class MoneyMultiplierModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); } @@ -2467,19 +2807,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); } @@ -2491,22 +2836,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); } @@ -2514,15 +2865,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; } @@ -2531,13 +2886,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); } @@ -2549,19 +2904,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); } @@ -2573,19 +2933,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); } @@ -2593,7 +2958,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; } @@ -2601,7 +2970,7 @@ export class LockModifierTiersModifier extends PersistentModifier { return new LockModifierTiersModifier(this.type, this.stackCount); } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 1; } } @@ -2612,7 +2981,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; @@ -2626,8 +2995,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; @@ -2637,13 +3010,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); } @@ -2655,17 +3028,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); } @@ -2677,11 +3054,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; } } @@ -2692,18 +3073,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() : []; @@ -2712,12 +3092,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; @@ -2730,9 +3110,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? @@ -2768,9 +3148,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; } /** @@ -2780,7 +3160,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); } @@ -2792,15 +3172,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; } @@ -2818,7 +3198,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; @@ -2826,16 +3206,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 { @@ -2850,21 +3226,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); } @@ -2876,17 +3252,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); } @@ -2898,13 +3278,18 @@ 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; } } @@ -2952,32 +3337,23 @@ export class TempExtraModifierModifier extends LapsingPersistentModifier { return (modifier instanceof TempExtraModifierModifier); } - /** - * Checks if {@linkcode args} contains the necessary elements. - * @param args [1] {@linkcode Utils.NumberHolder} that holds the resulting shop item reward count - * @returns true if the shop reward number modifier applies successfully - */ - shouldApply(args: any[]): boolean { - return args && (args.length === 1) && (args[0] instanceof Utils.NumberHolder); - } - /** * Increases the current rewards in the battle by the stackCount. - * @param args [0] {@linkcode Utils.IntegerHolder} that holds the resulting shop item reward count * @returns true if the shop reward number modifier applies successfully + * @param count {@linkcode Utils.IntegerHolder} that holds the resulting shop item reward count */ - apply(args: any[]): boolean { - (args[0] as Utils.IntegerHolder).value += this.getStackCount(); + apply(count: NumberHolder): boolean { + count.value += this.getStackCount(); return true; } } 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; } } @@ -2985,25 +3361,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 } @@ -3020,13 +3401,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 } @@ -3043,7 +3424,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; } } @@ -3051,7 +3432,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 @@ -3070,20 +3451,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; } } @@ -3092,7 +3476,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; @@ -3112,16 +3496,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; } } @@ -3129,7 +3517,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 @@ -3148,19 +3536,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; } } @@ -3168,7 +3560,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 @@ -3187,9 +3579,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; } @@ -3201,7 +3596,7 @@ export class EnemyEndureChanceModifier extends EnemyPersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): integer { + getMaxStackCount(scene: BattleScene): number { return 10; } } @@ -3209,7 +3604,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; @@ -3227,17 +3622,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; } } @@ -3250,7 +3650,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; } @@ -3264,7 +3664,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); } @@ -3291,7 +3691,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; } @@ -3305,7 +3705,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); 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/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 263a576c4f0..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; @@ -354,12 +355,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); } } @@ -402,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/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/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/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/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/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); + } + ); }); 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); }); }); 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/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/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); + }); +}); 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); + }); +}); 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); + }); +}); 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(); + }); +}); 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/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/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 3ecc7da42e3..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 @@ -358,8 +336,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); @@ -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..d3985b225ff 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, @@ -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)); @@ -333,7 +357,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) { @@ -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;